Зачем вообще нужны StorageClass, PV и PVC
Подсистема хранения Kubernetes устроена так, чтобы приложение описывало потребность в диске декларативно, а платформа решала, где и как создать том. У этого подхода есть типичный побочный эффект: том не создаётся, PVC висит в Pending, а Pod’ы не стартуют.
Три базовых объекта, которые важно не путать:
- PV (PersistentVolume) — конкретный ресурс хранения в кластере: диск, LUN, каталог NFS и т.п.
- PVC (PersistentVolumeClaim) — запрос на хранение: «хочу 20Gi, такой-то режим доступа, такой-то класс».
- StorageClass — политика, по которой создаются PV (обычно динамически): какой
provisionerиспользовать, какие параметры передать драйверу и как привязывать том к ноде/зоне.
В идеальном сценарии вы создаёте PVC, Kubernetes выбирает StorageClass, внешний CSI-provisioner создаёт PV в бэкенде, и PVC переходит в состояние Bound.
Как выглядит проблема: PVC Pending и что это реально значит
Статус Pending у PVC означает: Kubernetes пока не смог найти существующий PV или не смог создать новый PV под требования PVC. Почти все причины укладываются в несколько категорий:
- не задан
storageClassNameи нет default StorageClass; - StorageClass есть, но не работает CSI provisioner (контроллерная часть драйвера) или указан неверный
provisioner; - конфликт параметров (например,
accessModes,volumeMode, размер,selector); - особенности
volumeBindingMode(например, ожидание первого потребителя); - ограничения topology (зона/регион/конкретная нода) — том нельзя создать «там, где нужно»;
- лимиты/квоты/ошибки API хранилища (ресурсы кончились, запрет политиками, нет прав).
Правильный подход — не гадать, а читать события: Kubernetes почти всегда пишет понятную причину в events у PVC и у Pod.
Мини-чеклист диагностики: что выполнить в первую очередь
1) Посмотреть PVC и связанные события
kubectl get pvc -A
kubectl describe pvc -n NAMESPACE PVC_NAME
Команда kubectl describe pvc — самая полезная: внизу вывода будет раздел Events. Там обычно прямо написано, что не так: нет StorageClass, provisioner не найден, ошибка драйвера, ожидание из-за binding mode и т.д.
2) Проверить StorageClass и ключевые поля
kubectl get storageclass
kubectl describe storageclass STORAGECLASS_NAME
kubectl get storageclass STORAGECLASS_NAME -o yaml
Сфокусируйтесь на полях:
provisioner— должен совпадать с CSI-драйвером, который реально установлен в кластере;volumeBindingMode— влияет на то, будет ли PVC ждать Pod;parameters— специфичны для драйвера, опечатки и неподдерживаемые значения часто ломают provisioning;allowedTopologies(если есть) — может жёстко ограничить зоны/сегменты.
3) Проверить PV (если provisioning статический или PV уже появился)
kubectl get pv
kubectl describe pv PV_NAME
Если PV существует, но PVC всё равно Pending, обычно PV «не матчится» по условиям: класс, режимы доступа, volumeMode, размер или есть nodeAffinity, которую не удаётся удовлетворить.
4) Проверить состояние CSI-компонентов
При dynamic provisioning важны две части: controller (provisioner/attacher и т.п.) и node plugin. Быстрые проверки:
kubectl get csidrivers
kubectl get pods -A | grep -i csi
kubectl get events -A --sort-by=.metadata.creationTimestamp
Если в events встречается provisioner not found, failed to provision volume или rpc error — почти всегда проблема в CSI-компонентах, их RBAC или параметрах StorageClass.

Если вы поднимаете Kubernetes на выделенных виртуальных машинах и хотите предсказуемое поведение дисков и сети, удобнее сразу закладывать инфраструктуру под stateful-нагрузки. Для этого обычно выбирают VDS — проще контролировать ресурсы и диагностику.
StorageClass и dynamic provisioning: как это работает на практике
Dynamic provisioning — это автоматическое создание PV при появлении PVC. Внешний provisioner (контроллер CSI) отслеживает новые PVC, делает вызовы к драйверу, а тот создаёт том в бэкенде.
Ключевое поле StorageClass — provisioner. Если строка неверная или драйвер не установлен, PVC останется Pending.
Частые сообщения в events:
no persistent volumes available for this claim and no storage class is set
и:
waiting for a volume to be created either by the external provisioner
Первое обычно означает: в PVC не задан storageClassName, а default StorageClass в кластере отсутствует. Второе — Kubernetes ждёт внешний provisioner, но тот не создаёт PV (не запущен контроллер, ошибка прав, неверные parameters или отказ/лимит со стороны хранилища).
Проверка default StorageClass
Если вы иногда создаёте PVC без storageClassName, вам важно понимать, есть ли default-класс:
kubectl get storageclass -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.annotations.storageclass\.kubernetes\.io/is-default-class}{"\n"}{end}'
Если default нет — поведение предсказуемее: просто указывайте storageClassName явно во всех манифестах.
Volume binding mode: Immediate vs WaitForFirstConsumer
volumeBindingMode — частая причина «почему PVC Pending, хотя всё вроде настроено». Особенно в кластерах с зонами или с локальными томами.
- Immediate — PV создаётся сразу при создании PVC.
- WaitForFirstConsumer — PVC может оставаться
Pending, пока не появится Pod, который использует PVC, и пока Scheduler не выберет ноду/зону. Только после этого будет выбран подходящий топологический домен и создаться PV.
Практический симптом: вы создали PVC и ждёте Bound, но видите Pending без явных ошибок. Если StorageClass использует WaitForFirstConsumer — это нормально. Поднимите Pod/StatefulSet, который монтирует PVC, и уже потом смотрите события у Pod и PVC.
Topology: почему том нельзя создать в нужной зоне или на нужной ноде
Topology — это ограничения «где может жить том». В облаках это обычно зона/регион, в on-prem — пул/стойка/узел, для локальных дисков — конкретная нода.
Topology проявляется минимум в двух местах:
- в StorageClass — через
allowedTopologiesи/или параметры драйвера; - в PV — через
nodeAffinity(ограничения по нодам/зонам).
Симптомы конфликтов topology
- PVC
Pending, в events упоминания о невозможности выбрать сегмент topology или создать том в нужной зоне; - Pod тоже
Pendingиз-за планирования, а рядом «подвисает» PVC; - PV создан, но Pod не планируется, потому что PV привязан к зоне A, а Pod из-за affinity/taints фактически может попасть только в зону B.
Что проверять руками
kubectl get nodes -o wide
kubectl get nodes --show-labels
kubectl describe pod -n NAMESPACE POD_NAME
kubectl get pv PV_NAME -o yaml
В YAML PV ищите nodeAffinity и topology-ключи вроде topology.kubernetes.io/zone или topology.kubernetes.io/region (точные ключи зависят от драйвера и окружения).
Если у вас небольшой кластер на виртуальных машинах, топология часто «ломается» из-за несогласованных меток нод. В таких случаях либо упрощайте StorageClass (без излишних ограничений), либо приводите метки/планирование в порядок так, чтобы stateful-нагрузка не пыталась переезжать туда, где том создать нельзя.
Access modes: почему ReadWriteOnce не монтируется «как вы ожидали»
Access modes описывают, как том может быть примонтирован к Pod’ам/нодам. Это частая причина ситуаций «добавил реплик — часть Pod’ов не стартует».
ReadWriteOnce(RWO) — чтение/запись, обычно присоединение к одной ноде одновременно.ReadOnlyMany(ROX) — только чтение многим.ReadWriteMany(RWX) — чтение/запись многим (обычно сетевое/распределённое хранилище).ReadWriteOncePod— чтение/запись только одному Pod (поддержка зависит от версии Kubernetes и драйвера).
Ключевой момент: режим в PVC — это запрос. Драйвер обязан уметь его выполнить. Если вы запросили RWX, а StorageClass создаёт только «обычные» блочные диски с RWO, provisioning может завершиться ошибкой или том будет непригоден для ваших ожиданий.
Как быстро понять, что именно не совпало
kubectl describe pvc -n NAMESPACE PVC_NAME
kubectl get pvc -n NAMESPACE PVC_NAME -o yaml
Сравните spec.accessModes у PVC и у PV (если PV уже есть). Если PV не появляется — события у PVC обычно явно говорят, что запрошенный режим не поддерживается.
Если у вас много небольших сайтов/панелей и нужен простой сценарий с предсказуемыми лимитами и бэкапами, часто удобнее разместить вспомогательные сервисы на виртуальном хостинге, а stateful-компоненты кластера вынести на отдельные узлы.
PV/PVC совпадение: какие поля должны «сойтись», чтобы PVC стал Bound
При статическом provisioning (PV создан заранее) Kubernetes матчится по набору правил. Чаще всего «мимо» из-за:
- несовпадения
storageClassName(или один объект без класса); - несовпадения
accessModes; - разного
volumeMode(Filesystem/Block); - запрошенный размер PVC больше, чем
capacityPV; - PVC содержит
selector, а PV не подходит по label’ам; - PV уже связан с другим PVC или находится в состоянии, которое не позволяет привязку.
Если вы подозреваете статический сценарий — выгрузите YAML обоих объектов и сравните ключевые поля. Затем вернитесь к events: они почти всегда подскажут, почему матчинг не произошёл.
Типовые причины PVC Pending и быстрые действия
Причина 1: не указан StorageClass и нет default
Симптом: PVC без storageClassName, в events сообщения о том, что storage class не задан.
Действия: укажите storageClassName в PVC или назначьте default StorageClass (осторожно: это влияет на весь кластер).
Причина 2: неверный provisioner или CSI-драйвер не работает
Симптом: ошибки provisioner, rpc error, PV не создаётся при dynamic provisioning.
Действия: проверьте Pods CSI-контроллера и node-плагина, RBAC, логи компонентов драйвера.
Причина 3: WaitForFirstConsumer, но Pod ещё не создан
Симптом: PVC выглядит «зависшим» без явных ошибок.
Действия: создайте Pod/StatefulSet, который использует PVC, и анализируйте события у Pod и PVC вместе.
Причина 4: topology конфликтует с размещением Pod
Симптом: Pod не планируется или PV создан в зоне/на ноде, где Pod оказаться не может.
Действия: проверяйте метки нод, affinity/anti-affinity, taints/tolerations, allowedTopologies в StorageClass, nodeAffinity в PV.
Причина 5: access modes не поддерживаются вашим типом хранилища
Симптом: запросили RWX, а класс даёт только RWO.
Действия: меняйте accessModes под возможности драйвера или выбирайте другой StorageClass/бэкенд.
Полезные команды: собрать максимум фактов за 2–3 минуты
kubectl get pvc -A -o wide
kubectl describe pvc -n NAMESPACE PVC_NAME
kubectl get storageclass -o wide
kubectl get storageclass STORAGECLASS_NAME -o yaml
kubectl get pv -o wide
kubectl get events -A --sort-by=.metadata.creationTimestamp
kubectl describe pod -n NAMESPACE POD_NAME
Если цель — быстро локализовать проблему, начинайте с kubectl describe pvc (events), затем проверьте StorageClass (в первую очередь provisioner и volumeBindingMode), дальше — Pod (scheduling) и PV (nodeAffinity/topology).

Практика: как проектировать StorageClass и PVC, чтобы Pending случался реже
- Явно указывайте
storageClassNameв продакшн-манифестах, чтобы поведение не менялось при появлении/смене default StorageClass. - Выбирайте
WaitForFirstConsumerдля зональных/локальных хранилищ: меньше шанс создать том «не в той зоне». - Документируйте поддерживаемые access modes для каждого StorageClass: это экономит часы отладки при масштабировании StatefulSet.
- Следите за topology-метками нод: любые перекосы в метках или ограничениях планировщика быстро вылезают в PVC/POD Pending.
- Делайте отдельные StorageClass под разные профили: быстрые диски, дешёвые диски, «только RWO», «RWX», локальные тома и т.д.
Если вы отлаживаете Kubernetes-хранилище на одиночном сервере
В небольших инсталляциях (одна-две ноды) особенно важно, чтобы StorageClass и драйвер не «ожидали» несуществующих зон/сегментов. Если вы строите кластер на виртуальных машинах, проверьте, что:
- CSI-драйвер рассчитан на ваш тип хранения и корректно установлен;
- в кластере есть корректные node labels для topology (или StorageClass не требует их);
volumeBindingModeвыбран осознанно под сценарий;parametersStorageClass соответствуют возможностям бэкенда.
Если параллельно приходится разбираться с моделью изоляции и ограничениями рантайма, держите под рукой материал про изоляцию контейнеров через gVisor и Firecracker: это иногда влияет на дизайн stateful-нагрузок и выбор инфраструктуры.
Короткое резюме
PVC Pending — это не «магия Kubernetes», а конкретный сигнал: подходящий PV не найден или не создан. В большинстве случаев ответ находится внизу kubectl describe pvc в разделе Events.
Дальше всё сводится к трём группам проверок: provisioner/CSI (создаём ли том вообще), volume binding mode (когда и где создаём том) и topology/access modes (можно ли подключить том в нужном месте и нужным способом).


