Мониторинг хранилища в Kubernetes опирается на три потока данных: kubelet отдаёт фактическое заполнение примонтированных томов, kube-state-metrics описывает состояние объектов PV, PVC и StorageClass, а StorageClass задают, какие квоты, лимиты и расширение доступны в кластере. Связка этих источников закрывает учёт места по каждому тому, прогноз переполнения и выгрузку цифр во внешние системы отчётности.
Минимальный набор метрик, с которого начинают почти все: kubelet_volume_stats_used_bytes, kubelet_volume_stats_capacity_bytes, kubelet_volume_stats_available_bytes и kube_persistentvolumeclaim_status_phase. Процент заполнения считается делением used на capacity, свободное место берётся из available, а запрошенный объём и фаза привязки видны только в метриках kube-state-metrics. Этого достаточно для алертов на 80 и 90 процентов заполнения и для прогноза, когда том закончится.
Дальше по порядку: устройство объектов хранилища, настройка kubelet и kube-state-metrics, дашборд и алерты, квоты на пространство, выгрузка метрик наружу и разбор причин переполнения с командами для диагностики.
Как устроено хранилище в Kubernetes: PV, PVC и StorageClass
PersistentVolume (PV) - участок хранилища, зарегистрированный в кластере как объект с ёмкостью, режимом доступа и политикой очистки. PersistentVolumeClaim (PVC) - запрос приложения на место. StorageClass описывает, откуда это место взять и как его создать.
Цепочка работает так: разработчик создаёт PVC с нужным объёмом и классом, контроллер провижининга через CSI-драйвер выделяет том у провайдера, в кластере появляется PV, PVC переходит в статус Bound, а под монтирует его как обычную директорию. При статическом провижининге администратор создаёт PV вручную, и контроллер подбирает подходящий по ёмкости, режиму доступа и имени класса.
Ключевые объекты и их роли
PV живёт на уровне кластера и не привязан к неймспейсу. PVC живёт в неймспейсе и потребляет ёмкость PV целиком: остаток от одного тома другому запросу не достаётся. StorageClass - шаблон, по которому создаются PV, и единственный объект из трёх, где заданы параметры бэкенда.
Аналогия для быстрого запоминания: PV похож на уже нарезанный и отформатированный раздел, PVC - на запись в конфигурации монтирования с требованием объёма, StorageClass - на профиль, по которому раздел нарезают. Роли не пересекаются, поэтому и метрики у них разные.
Современный провижининг идёт через CSI (Container Storage Interface). Встроенные in-tree плагины выведены из API, поэтому в provisioner указывают адреса драйверов вида ebs.csi.aws.com, pd.csi.storage.gke.io, driver.longhorn.io, rook-ceph.rbd.csi.ceph.com. Если в кластере остались StorageClass со старыми provisioner вида kubernetes.io/aws-ebs, они не будут работать на актуальных версиях Kubernetes, начиная с 1.29 такие объекты нужно переписать на CSI-драйвер.
Пример запроса на хранилище:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
namespace: production
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 50Gi
Ключевые поля PVC: accessModes (ReadWriteOnce, ReadWriteMany, ReadWriteOncePod), volumeMode (Filesystem или Block) и resources.requests.storage. Если storageClassName не указан, применяется класс с аннотацией storageclass.kubernetes.io/is-default-class: "true".
Пример класса с расширением и отложенной привязкой:
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: fast-ssd provisioner: ebs.csi.aws.com parameters: type: gp3 iops: "6000" throughput: "250" reclaimPolicy: Retain allowVolumeExpansion: true volumeBindingMode: WaitForFirstConsumer
volumeBindingMode определяет момент выделения тома. Immediate создаёт PV сразу после появления PVC, WaitForFirstConsumer ждёт планирования пода и учитывает топологию зоны и ноды. Для учёта это важно: при Immediate том может быть выделен и оплачен, но так и не смонтирован, потому что под не поместился на ноды в этой зоне.
reclaimPolicy управляет судьбой PV после удаления PVC. Delete возвращает ёмкость провайдеру, Retain оставляет том и данные, переводя PV в статус Released. Recycle исключён из API, выбирать нужно между Delete и Retain. Тома в статусе Released продолжают занимать место в квоте бэкенда, поэтому их стоит отслеживать отдельной панелью.
Как StorageClass влияет на учёт и мониторинг
Блок parameters задаёт физические характеристики тома: тип диска, IOPS, репликацию, пул. Метрики PVC считаются одинаково для любого класса, а вот стоимость гигабайта и доступное расширение различаются, и это отражается в отчётности.
Квоту можно привязать к конкретному классу. Ограничения вида fast-ssd.storageclass.storage.k8s.io/requests.storage: 200Gi и gold.storageclass.storage.k8s.io/persistentvolumeclaims: "5" действуют только на тома соответствующего класса, что удобно для разделения дешёвого и быстрого хранения.
Флаг allowVolumeExpansion решает, можно ли увеличить PVC без пересоздания. Если он выключен, плановый рост тома превращается в миграцию данных с простоем. Проверить все классы разом удобно командой kubectl get storageclass -o custom-columns=NAME:.metadata.name,PROV:.provisioner,BIND:.volumeBindingMode,RECLAIM:.reclaimPolicy,EXPAND:.allowVolumeExpansion.
Если кластер разворачивается с нуля в облаке, managed Kubernetes с блочным хранилищем закрывает часть работы по CSI-драйверам, зонам доступности и квотам на стороне провайдера, например Timeweb Cloud. В таком случае StorageClass уже создан провайдером, и вам остаётся настроить сбор метрик и лимиты внутри кластера.
Настройка сбора метрик PV и PVC через Prometheus
Сбор делится на два потока. Kubelet на каждой ноде отдаёт фактическое заполнение томов, kube-state-metrics описывает объекты PVC, PV и StorageClass. Нужны оба: без kubelet не видно реального процента заполнения, без kube-state-metrics не видно запрошенного объёма, фазы привязки и привязки к классу.
Метрики kubelet для PV
Kubelet публикует статистику томов на своём endpoint https://<адрес-ноды>:10250/metrics. Основные метрики:
- kubelet_volume_stats_used_bytes - занятое место в томе;
- kubelet_volume_stats_capacity_bytes - ёмкость тома;
- kubelet_volume_stats_available_bytes - свободное место;
- kubelet_volume_stats_inodes, kubelet_volume_stats_inodes_used, kubelet_volume_stats_inodes_free - расход инод.
Метки у этих метрик: namespace и persistentvolumeclaim. Есть ограничение, о котором забывают: статистика появляется только на той ноде, где том смонтирован в работающий под. Для PVC в статусе Pending или для томов без подов данных не будет, там работают метрики kube-state-metrics. Kubelet обновляет статистику томов раз в минуту (параметр volume-stats-agg-period), поэтому резкие всплески на графике стоит сглаживать через max_over_time или rate с окном больше минуты.
Процент использования тома:
100 * kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes
Топ-10 самых заполненных томов в кластере:
topk(10, 100 * kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes)
Прогноз заполнения на неделю вперёд с учётом тренда за 6 часов:
predict_linear(kubelet_volume_stats_used_bytes[6h], 86400 * 7) / kubelet_volume_stats_capacity_bytes * 100
Расход инод, который ловит проблему раньше, чем кончится место:
100 * kubelet_volume_stats_inodes_used / kubelet_volume_stats_inodes
Метрики kube-state-metrics для PVC
Kube-state-metrics опрашивает API-сервер и превращает объекты в метрики. Для учёта хранилища полезны:
- kube_persistentvolumeclaim_status_phase - фазы Bound, Pending, Lost;
- kube_persistentvolumeclaim_resource_requests_storage_bytes - запрошенный объём PVC;
- kube_persistentvolumeclaim_info - метки со storageclass и volumename;
- kube_persistentvolumeclaim_status_condition - детальные условия (Resizing, FileSystemResizePending);
- kube_persistentvolume_capacity_bytes и kube_persistentvolume_status_phase - фактическая ёмкость PV и его состояние (Available, Bound, Released, Failed);
- kube_persistentvolumeclaim_created - время создания тома для расчёта гигабайт-часов.
Количество PVC в статусе Pending по неймспейсам:
count by (namespace) (kube_persistentvolumeclaim_status_phase{phase="Pending"})
PVC без данных о фактическом использовании, то есть созданные, но не смонтированные в поды:
kube_persistentvolumeclaim_resource_requests_storage_bytes unless on (namespace, persistentvolumeclaim) kubelet_volume_stats_used_bytes
Суммарный запрошенный объём по классам хранения:
sum by (storageclass) (kube_persistentvolumeclaim_resource_requests_storage_bytes * on (namespace, persistentvolumeclaim) group_left (storageclass) kube_persistentvolumeclaim_info)
Пошаговая установка kube-state-metrics и node-exporter через Helm с готовыми селекторами разобрана в материале о сборе метрик CPU, памяти, диска и сети в Kubernetes. Node-exporter здесь дополняет картину: метрики node_filesystem_avail_bytes показывают заполнение локальных дисков ноды, что важно для корневого раздела и ephemeral-storage.
Настройка ServiceMonitor для сбора метрик
В Prometheus Operator цели задаются объектами ServiceMonitor. Для kube-state-metrics достаточно такого манифеста:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: kube-state-metrics
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
namespaceSelector:
matchNames:
- kube-system
selector:
matchLabels:
app.kubernetes.io/name: kube-state-metrics
endpoints:
- port: http-metrics
interval: 30s
Для kubelet требуется HTTPS и токен сервисного аккаунта, потому что endpoint закрыт авторизацией:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: kubelet
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
namespaceSelector:
matchNames:
- kube-system
selector:
matchLabels:
k8s-app: kubelet
endpoints:
- port: https-metrics
scheme: https
path: /metrics
interval: 30s
bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
tlsConfig:
insecureSkipVerify: true
Prometheus нужен ClusterRole с доступом к ресурсу nodes/metrics, иначе kubelet вернёт 403 и метрики томов не появятся. В чарте kube-prometheus-stack эти ServiceMonitor и роль уже созданы, проверьте только, что селектор release совпадает с меткой вашего релиза. Часть CSI-драйверов публикует собственные метрики (например, счётчики операций и задержки тома), их добавляют отдельным ServiceMonitor, если провайдер их отдаёт.
Проверка после настройки: в Prometheus откройте Status - Targets и убедитесь, что цели kubelet и kube-state-metrics в состоянии UP, затем выполните в консоли запрос count(kubelet_volume_stats_used_bytes) и сравните число с количеством смонтированных PVC.
Визуализация и алертинг в Grafana
Рабочий дашборд по хранилищу удобно собирать из четырёх строк. Верхняя строка: количество PVC в статусе Pending и Lost, суммарная запрошенная ёмкость, суммарное фактическое использование. Вторая строка: таблица по всем PVC с колонками namespace, имя, StorageClass, запрошено, использовано, процент, прогноз на 7 дней. Третья строка: графики динамики роста по топ-10 томам и расход инод. Четвёртая строка: PV в статусе Released и Failed, а также тома с выключенным allowVolumeExpansion.
Готовый дашборд kube-prometheus-stack с идентификатором 15757 подходит как основа для обзорной строки, дашборд 11454 полезен для метрик kube-state-metrics. Оба стоит адаптировать: добавить переменные namespace и storageclass, а метрики использования вывести отдельной таблицей с сортировкой по проценту заполнения. Полный стек мониторинга с установкой Grafana и Alertmanager через Helm описан в руководстве по настройке Prometheus, Grafana и Alertmanager.
Примеры PromQL для панелей
Запросы, которые покрывают большинство панелей дашборда:
# Использование PVC в процентах
100 * kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes
# Свободное место в байтах
kubelet_volume_stats_available_bytes
# Количество PVC в статусе Pending
count(kube_persistentvolumeclaim_status_phase{phase="Pending"})
# Прогноз заполнения через 24 часа в процентах
predict_linear(kubelet_volume_stats_used_bytes[6h], 86400)
/ kubelet_volume_stats_capacity_bytes * 100
# Запрошено против фактической ёмкости PV
kube_persistentvolumeclaim_resource_requests_storage_bytes
- on (namespace, persistentvolumeclaim) group_left
kubelet_volume_stats_capacity_bytes
Последний запрос показывает расхождение между заявленным и фактическим размером тома. Разница в 5 процентов нормальна: файловая система ext4 резервирует часть блоков под root, поэтому том на 100Gi даёт около 93-95 GiB доступного места.
Настройка алертов в Alertmanager
Пороговые алерты описываются объектом PrometheusRule и подхватываются оператором автоматически:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: storage-alerts
namespace: monitoring
labels:
release: kube-prometheus-stack
spec:
groups:
- name: storage.rules
rules:
- alert: VolumeUsageHigh
expr: (kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes) * 100 > 85
for: 15m
labels:
severity: warning
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} в {{ $labels.namespace }} заполнен на {{ $value | printf \"%.1f\" }}%"
- alert: VolumeUsageCritical
expr: (kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes) * 100 > 95
for: 5m
labels:
severity: critical
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} близок к переполнению"
- alert: VolumeWillFillIn24h
expr: predict_linear(kubelet_volume_stats_used_bytes[6h], 86400) > kubelet_volume_stats_capacity_bytes * 0.95
for: 30m
labels:
severity: warning
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} заполнится в течение суток"
- alert: PersistentVolumeClaimPending
expr: kube_persistentvolumeclaim_status_phase{phase="Pending"} == 1
for: 15m
labels:
severity: warning
annotations:
summary: "PVC {{ $labels.persistentvolumeclaim }} не привязан 15 минут"
- alert: PersistentVolumeReleased
expr: kube_persistentvolume_status_phase{phase="Released"} == 1
for: 60m
labels:
severity: info
annotations:
summary: "PV {{ $labels.persistentvolume }} освобождён и занимает ёмкость"
Алерт на прогноз срабатывает раньше порогового и даёт время на расширение тома без аварии. Маршрутизацию удобно делить по критичности: инциденты уходят дежурному, предупреждения - в командный чат. Пример маршрута Alertmanager:
route:
group_by: ['alertname', 'namespace']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: devops-chat
routes:
- matchers:
- severity = critical
receiver: oncall-pager
continue: true
inhibit_rules:
- source_matchers:
- severity = critical
target_matchers:
- severity = warning
equal: ['alertname', 'persistentvolumeclaim', 'namespace']
receivers:
- name: devops-chat
slack_configs: []
- name: oncall-pager
webhook_configs: []
Каналы доставки (почта, Slack, Telegram через webhook-реле) настраиваются в блоке receivers: укажите адрес получателя и шаблон сообщения со ссылкой на дашборд. Блок inhibit_rules убирает шум: пока горит критичный алерт по тому, предупреждение по нему же не отправляется.
Учёт и квоты на пространство: ResourceQuota и LimitRange
Квоты задают верхнюю границу потребления на неймспейс и защищают от ситуации, когда одна команда забирает всю ёмкость класса хранения. Проверка квоты происходит на этапе создания PVC: при превышении API-сервер отклоняет объект с ошибкой exceeded quota.
Пример ResourceQuota для хранилища
apiVersion: v1
kind: ResourceQuota
metadata:
name: storage-quota
namespace: production
spec:
hard:
requests.storage: 500Gi
persistentvolumeclaims: "20"
fast-ssd.storageclass.storage.k8s.io/requests.storage: 200Gi
fast-ssd.storageclass.storage.k8s.io/persistentvolumeclaims: "5"
requests.ephemeral-storage: 50Gi
limits.ephemeral-storage: 100Gi
Поле requests.storage ограничивает суммарный объём всех PVC в неймспейсе, persistentvolumeclaims - их количество. Строки с префиксом имени класса действуют только на тома этого StorageClass. Лимиты на ephemeral-storage не дают поду занять весь локальный диск ноды и спровоцировать DiskPressure с вытеснением соседних подов.
PVC, созданные через volumeClaimTemplates в StatefulSet, тоже попадают в квоту. Посмотреть текущий расход удобно командой kubectl describe resourcequota storage-quota -n production: в выводе видно used и hard по каждому параметру.
Ограничение размера PVC через LimitRange
apiVersion: v1
kind: LimitRange
metadata:
name: pvc-size-limits
namespace: production
spec:
limits:
- type: PersistentVolumeClaim
max:
storage: 200Gi
min:
storage: 1Gi
defaultRequest:
storage: 10Gi
LimitRange запрещает создание слишком больших и слишком маленьких томов: PVC на 500Gi при max 200Gi не пройдёт валидацию, а запрос на 100Mi без явного указания размера получит defaultRequest 10Gi. Если в неймспейсе действует квота на requests.storage, блок min в LimitRange обязателен, иначе запросы без указанного объёма будут отклоняться.
Экспорт данных во внешние системы учёта
Данные о занятом месте нужны не только в Grafana: их передают в биллинг, CMDB и системы отчётности. Есть три рабочих способа.
Настройка remote_write
Prometheus отправляет метрики в удалённое хранилище через remote_write. В качестве приёмника подходят VictoriaMetrics, Thanos и Grafana Mimir. Пример конфигурации с фильтром, который оставляет только метрики хранилища:
remote_write:
- url: https://<адрес-приёмника>/api/v1/push
basic_auth:
username: billing
password_file: /etc/prometheus/remote-write-password
write_relabel_configs:
- source_labels: [__name__]
regex: 'kubelet_volume_stats_.*|kube_persistentvolumeclaim_.*|kube_persistentvolume_.*'
action: keep
queue_config:
max_samples_per_send: 5000
max_shards: 30
Фильтр нужен, если внешнее хранилище используют только для учёта: это снижает объём передачи и стоимость хранения. Метки team, owner и cost-center удобно проставлять через metric_relabel_configs на этапе сбора или через метки неймспейсов, иначе группировать расход по подразделениям будет нечем.
Выгрузка отчётов через Grafana
Для регулярных отчётов используют Reporting в Grafana (экспорт панели и дашборда в PDF и CSV по расписанию), HTTP API Grafana либо прямой запрос к API Prometheus. Пример выгрузки суммарного запрошенного объёма по неймспейсам в CSV:
curl -G 'https://<адрес-prometheus>/api/v1/query' \ --data-urlencode 'query=sum by (namespace) (kube_persistentvolumeclaim_resource_requests_storage_bytes)' \ | jq -r '.data.result[] | [.metric.namespace, .value[1]] | @csv'
Для биллинга считают гигабайт-часы: среднее значение kubelet_volume_stats_used_bytes за час умножают на 3600 и делят на 1073741824. Сумма таких значений по неймспейсу за месяц даёт корректный расход даже при неравномерной нагрузке. Такой запрос удобно отдавать в биллинг через API Prometheus по расписанию.
Типичные причины переполнения томов и как их предотвратить
Инциденты с нехваткой места почти всегда сводятся к нескольким сценариям:
- Алертов нет вообще, о переполнении узнают от пользователей. Решение: PrometheusRule с порогами 85 и 95 процентов и прогнозом на сутки.
- Приложение пишет логи и временные файлы внутрь тома с данными. Решение: ротация логов, вынос логов в stdout, монтирование временных директорий как emptyDir с параметром sizeLimit.
- Квоты не заданы, и один неймспейс занимает всю ёмкость класса. Решение: ResourceQuota с разбивкой по StorageClass.
- Ретеншен не настроен: Prometheus TSDB, поисковые индексы, очереди сообщений и бэкапы растут бесконечно. Решение: явные политики хранения и вынос бэкапов в объектное хранилище.
- Файлы удалены, но остаются открытыми в процессе: место занято, а du ничего не показывает. Решение: проверка через lsof +L1 внутри контейнера и корректный рестарт процесса.
- Служебный расход файловой системы недооценён: ext4 резервирует около 5 процентов блоков, том на 100Gi даёт примерно 95 GiB доступного места.
- Иноды кончились раньше места при множестве мелких файлов. Решение: отдельная панель по kubelet_volume_stats_inodes_used.
- Расширение невозможно: allowVolumeExpansion выключен или драйвер не умеет онлайн-рост. Решение: включить флаг заранее, до инцидента.
- Под вытеснен из-за ephemeral-storage, нода ушла в DiskPressure. Решение: лимиты и квоты на ephemeral-storage.
Диагностика переполнения
Порядок действий при заполненном томе:
- Смотрим процент заполнения и тренд в Grafana или запросом 100 * kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes.
- Смотрим состояние объекта: kubectl describe pvc app-data -n production, обращая внимание на условия FileSystemResizePending и события.
- Заходим в под и смотрим фактический расход: kubectl exec -it deploy/app -n production -- df -h /data и kubectl exec -it deploy/app -n production -- du -sh /data/* | sort -h.
- Ищем удалённые, но открытые файлы: kubectl exec -it deploy/app -n production -- lsof +L1.
- Проверяем вытеснения и давление на ноде: kubectl get events -A --field-selector reason=Evicted и kubectl describe node node-1 | grep -A 8 Conditions.
- Смотрим логи приложения на ошибки записи и отсутствие ротации.
Быстрая диагностика неисправностей кластера через метрики CPU, памяти, диска и сети с готовыми PromQL-запросами разобрана в отдельном руководстве по диагностике подов и узлов через метрики.
Автоматическое расширение томов
Расширение включается флагом allowVolumeExpansion: true в StorageClass и работает только для тех CSI-драйверов, которые поддерживают рост тома. Сам процесс занимает одну команду:
kubectl patch pvc app-data -n production -p '{"spec":{"resources":{"requests":{"storage":"80Gi"}}}}'
После патча контроллер начинает операцию Resize, а в статусе PVC появляются условия Resizing или FileSystemResizePending. Первое означает рост блочного устройства, второе требует перезапуска пода для расширения файловой системы. Уменьшить PVC нельзя: новое значение принимается только больше текущего. Для StatefulSet правка манифеста не расширит существующие тома, менять нужно сами PVC, иначе новые реплики получат другой размер, чем старые.
Чек-лист по запуску мониторинга хранилища
Порядок действий, который закрывает тему целиком:
- Установите kube-state-metrics и node-exporter в кластер.
- Проверьте, что kubelet отдаёт kubelet_volume_stats_* и Prometheus имеет доступ к nodes/metrics.
- Создайте ServiceMonitor для kubelet и kube-state-metrics с интервалом 30 секунд.
- Импортируйте дашборд Grafana и добавьте таблицу по всем PVC с процентом заполнения.
- Опишите PrometheusRule с алертами на 85 и 95 процентов, на прогноз заполнения за 24 часа и на PVC в Pending.
- Настройте маршрутизацию уведомлений в Alertmanager с разделением по severity и правилом подавления шума.
- Создайте ResourceQuota на requests.storage и LimitRange с max и min для PVC в каждом рабочем неймспейсе.
- Включите allowVolumeExpansion в StorageClass, где нужен рост томов.
- Настройте remote_write, если метрики хранилища нужны во внешнем хранилище или биллинге.
- Проверьте работу на тестовом PVC: создайте том, запишите данные, убедитесь, что метрики появились и алерты срабатывают при снижении порога.
Обзор ключевых компонентов кластера и типовых сценариев их мониторинга помогает не пропустить ни один уровень наблюдения: от нод и томов до рабочих нагрузок и Ingress. Начните с пунктов 1-3 чек-листа, а остальные добавляйте по мере роста кластера.