Управление данными в Kubernetes часто кажется сложным. Если грамотно не подключить внешнее хранилище, то при любом перезапуске пода все накопленные файлы бесследно исчезнут. Этот материал — практическое руководство для тех, кто только осваивает администрирование K8s и хочет разобраться в логике работы дисков без погружения в запутанную документацию.
Привет! Я Катя, младший системный администратор в Selectel. Надеюсь, моя работа поможет выстроить в голове четкую картину и сэкономит часы на дебаге, если нужный диск однажды откажется подключаться.
Введение
Хранилища предназначены для долговременного хранения данных. На локальных компьютерах эту задачу выполняют физические диски. В зависимости от операционной системы, это могут быть логические тома C:/, D:/ или устройства /dev/sda, /dev/sdb. На них и размещаются различные файлы и документы.
Аналогично и приложения, работающие в подах кластера Kubernetes, требуют выделенного дискового пространства. Однако в отличие от домашнего ПК, поды эфемерны: они могут перезапускаться или удаляться. Если сохранять информацию только внутри контейнера, при его остановке данные будут потеряны.
Именно поэтому в Kubernetes предусмотрены специальные механизмы, позволяющие сохранять данные независимо от жизненного цикла пода. Этот принцип схож с камерой хранения в отеле: вещи остаются в сохранности независимо от присутствия их владельца в номере.
Общая архитектура хранения
Приложение ничего не знает о конкретной реализации хранилища и его устройстве. Kubernetes также не управляет дисками напрямую. Чтобы максимально просто и прозрачно предоставить поду место для размещения данных, была придумана следующая схема взаимодействия.
1. Приложение в поде понимает, что ему нужно куда-то сохранять данные.
2. Что делает приложение? Оно создает заявку — Persistent Volume Claim (PVC). В ней честно пишет: «Требуется столько-то гигабайт, нужны права на чтение и запись, и хранилище нужно вот такого типа».
3. Дальше в игру вступает главный диспетчер — Kubernetes. Он изучает заявку и понимает: готового подходящего диска, или как он называется в Kubernetes, Persistent Volume (PV) — нет. Тогда он зовет переводчика — CSI-драйвер (Container Storage Interface). Заявка же (PVC) временно получает статус Pending — как заказ, который еще не подтвержден.
4. Сам Kubernetes — не волшебник. Он не знает, как общаться с каждым облачным провайдером или файловой системой в отдельности. Их же сотни! Поэтому у него есть специальный посредник — CSI-драйвер. Этот помощник знает, как разговаривать с конкретным хранилищем на его родном языке — прямо как синхронный переводчик на конференции.
5. CSI-драйвер берет заявку и создает новый диск прямо в облачном хранилище. Облако отвечает: «Диск готов!». Теперь Kubernetes создает для этого диска PV и наконец-то привязывает его к нашей заявке PVC. Статус Pending меняется на Bound. Все, сделка состоялась!
6. Финал: приложение получает доступ к своему личному, пусть и условному, диску. Теперь оно может размещать на нем базы данных, логи, пользовательские файлы и вообще что угодно. И главное — даже если под перезапустится или его удалят, данные никуда не исчезнут, а останутся лежать спокойно в хранилище в ожидании нового пода.
Подытожим.
Когда описывается под и запрашивается для него диск, то создается ресурс PersistentVolumeClaim (дословно, «заявка на постоянное хранилище», далее PVC) — запрос, но еще не само хранилище. Позже Kubernetes выделит PersistentVolume (дословно, «постоянное хранилище», далее PV) — уже ссылающийся на диск конкретный ресурс, которым и оперирует под.
Чтобы диск был создан в инфраструктуре облачного провайдера, Kubernetes обращается к CSI-драйверу, который, в свою очередь, и делает всю работу, исходя из параметров в PVC.
Общая цепочка выглядит так: Под → PVC (PersistentVolumeClaim) → CSI (Container Storage Interface) → физический диск → PV (PersistentVolume)
Сам K8s никогда не управляет дисками напрямую — вместо этого он контролирует цепочку абстракций, которые и связывают под с реальным хранилищем.
Теперь, уяснив принципы взаимодействия пода с диском, и начнем наше знакомство с работой хранилища в K8s.
Источником диска всегда выступает внешнее хранилище: ресурсы облачных провайдеров или мощные локальные серверы с решениями Ceph и NFS.
CSI и StorageClass— создание и управление дисками
CSI (Container Storage Interface) — это драйвер, который непосредственно работает с дисками. Именно ему Kubernetes делегирует задачи по созданию, удалению и подключению томов к узлам кластера.
Технически компоненты CSI представляют собой обычные поды. Проверить их наличие можно командой:
kubectl -n kube-system get pods | grep csi
Обычно разворачивается два типа подов:
- controller — отвечает за создание тома (volume),
- node — монтирует созданный том на конкретную ноду для использования подом.
В нашем тестовом кластере с одной worker-нодой мы видим два запущенных пода — controller и node-плагин:
kube-system csi-cinder-controllerplugin-78858d84f9-pdj2r 6/6 Running 0 59m
kube-system csi-cinder-nodeplugin-nbvhk 3/3 Running 0 59m
StorageClass — это профиль конфигурации, который указывает кластеру, какой тип накопителя нужно выделить и с помощью какого CSI-драйвера. Он работает как шаблон, где заранее зафиксированы технические параметры будущего диска, его производительность и правила удаления данных.
Пример манифеста StorageClass:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast.ru-3b
provisioner: cinder.csi.openstack.org
reclaimPolicy: Delete
volumeBindingMode: Immediate
parameters:
type: fast.ru-3b
В примере выше используется CSI‑драйвер Cinder от платформы Openstack.
Посмотреть, какие есть в кластере классы хранилищ и их основные характеристики можно с помощью команды:
kubectl get storageclass
Пример вывода:
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
fast.ru-3b cinder.csi.openstack.org Delete Immediate true 14m
Можно также увидеть и подробную информацию о конкретном классе:
kubectl describe storageclass fast.ru-3b
Команда выше незаменима, когда нужно разобраться в детальных настройках профиля или найти причину ошибки при создании диска.
Как связаны между собой PV и PVC
Связь между заявкой (PVC) и выделенным томом (PV) устанавливается через процесс привязки (binding). Kubernetes не выдает первый попавшийся диск: контроллер проводит строгую проверку совместимости объектов. Чтобы связка состоялась, должны совпасть три ключевых условия:
- идентичный класс хранилища —
StorageClass; - совпадающие режимы доступа — Access Modes;
- достаточный объем — емкость выделяемого PV должна быть больше или равна размеру, запрошенному в PVC.
Если подходящего тома нет, контроллер инициирует его динамическое создание или переводит заявку в режим ожидания.
Требования к будущему хранилищу фиксируются в спецификации. Пример минимального манифеста PVC, который будет использоваться в практической части:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: busybox-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast.ru-3b
resources:
requests:
storage: 5Gi
Разберем поля манифеста:
apiVersion— используемая версия Kubernetes API;kind— тип создаваемого объекта;metadata.name— уникальное имя заявки (busybox-pvc);spec.accessModes— конфигурация прав доступа к диску;spec.resources.requests.storage— запрашиваемый объем дискового пространства (5Gi).
После применения манифеста Kubernetes либо находит подходящий PersistentVolume, либо создает новый с помощью StorageClass и CSI‑драйвера. Далее происходит сама привязка (binding) — когда для PVC находится подходящий PV.
В текущем пространстве имен статус объектов проверяется командами:
kubectl get pvc
kubectl get pv
Флаг -A (или его полная версия --all-namespaces) указывает Kubernetes, что нужно показать ресурсы во всех пространствах имен кластера — например:
kubectl get pvc -A
Пример вывода:
NAMESPACE NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
default busybox-pvc Bound pvc-528f4464-5253-4796-8908-6942af89a125 5Gi RWO fast.ru-3b <unset> 64m
Если все в порядке, мы должны увидеть, что у PVC статус Bound, а у PV появилась ссылка на PVC. Однако у PVC еще может быть статус Pending. В чем отличие?
Статус Bound показывает, что успешно произошла связка PVC ↔ PV, все работает. Статус же Pending значит, нет свободного PV, не хватает емкости или возникла ошибка при создании диска на стороне провайдера.
В выводе видим Access Modes и Reclaim Policy — это важные настройки, поэтому их рассмотрим подробнее.
Есть три вида прав доступа к PV.
ReadWriteOnce(RWO) — чтение и запись. Том монтируется только к одной физической ноде. Несколько подов могут делить этот диск, только если они запущены на одном узле.ReadWriteMany(RWX) — чтение и запись с возможностью монтирования к нескольким нодам. Разные поды могут работать с одним ресурсом одновременно, даже если находятся на разных физических нодах кластера.ReadOnlyMany(ROX) — режим «только чтение», доступный для множества нод.
Политика ReclaimPolicy определяет поведение кластера с данными после удаления PVC. Есть три варианта:
Delete— диск удаляется вместе со всеми данными, PV также удаляется из кластера;Retain— физический том и данные сохраняются для дальнейшего ручного администрирования;Recycle— данные с диска удаляются, но PV становится доступным для дальнейшего использования в кластере.
Еще важно отметить, что есть два режима выделения томов в кластере: статический (Static Provisioning) и динамический (Dynamic Provisioning). На практике почти всегда используется динамический (через StorageClass), но важно понимать разницу:
- статическое выделение — подразумевает ручное создание PV администратором с последующим формированием ссылающегося на него PVC;
- динамическое — автоматизирует процесс: создается только PVC, а Kubernetes генерирует хранилище самостоятельно.
В практических примерах мы как раз воспользуемся динамическим созданием хранилища в кластере.
Практика
Закрепим знания на простых примерах. Создадим базовый кластер в Managed Kubernetes с одной воркер-нодой. Для этого перейдите в панель управления → Продукты → Managed Kubernetes → Создать кластер.



Подробнее о том как, начать работать с Managed Kubernetes — в нашей документации.
Первое, что стоит сделать в любом кластере — посмотреть существующие тома:
kubectl get pvc -A
kubectl get pv -A
Если кластер пуст, команды вернут:
No resources found
Вывод ожидаемый, так как в кластере нет клиентских подов. Создадим тестовый под на базе легковесного образа busybox и заявку на хранилище — PVC.
kubectl apply -f - <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: busybox-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Pod
metadata:
name: busybox
spec:
containers:
- name: busybox
image: busybox:latest
command: ["/bin/sh", "-c"]
args:
- |
while true; do
date >> /data/log.txt
sleep 10
done
volumeMounts:
- name: storage
mountPath: /data
volumes:
- name: storage
persistentVolumeClaim:
claimName: busybox-pvc
EOF
YAML‑файл для формирования PVC мы уже разобрали выше, теперь посмотрим, как создавать под.
Начинаем с указания базовой информации:
apiVersion— версия Kubernetes API;kind— тип объекта;metadata.name— служебная информация об объекте, указываем Pod;
Далее описываем список контейнеров внутри пода. В нашем случае контейнер только один:
spec.containers.name— имя контейнера;image— образ контейнера;command— команда запуска контейнера;args— аргументы для команды запускают цикл, который каждые 10 секунд записывает текущую дату в файл/data/log.txt;- volumeMounts — монтирует том хранилища
storageвнутрь контейнера по пути/data; - volumes — задает список томов для пода:
name— имя тома,persistentVolumeClaim— указывает источником тома заявкуbusybox-pvc.
Проверим, что заявка PVC благополучно сформирована:
kubectl get pvc -A
Пример вывода:
NAMESPACE NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
default busybox-pvc Bound pvc-528f4464-5253-4796-8908-6942af89a125 5Gi RWO fast.ru-3b <unset> 64m
Видим не только параметры самой заявки, но и уже созданный том (volume) для пода, что видно по статусу Bound у заявки. Процесс происходит обычно быстро, вплоть до нескольких секунд.
Статус Bound также значит, что у PVC появилась связка с PV. Посмотрим ресурсы в кластере:
kubectl get pv -A
Вывод:
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-528f4464-5253-4796-8908-6942af89a125 5Gi RWO Delete Bound default/busybox-pvc fast.ru-3b <unset> 64m
Действительно есть PV размером 5 Гбайт — как и было указано в PVC.
Детальную конфигурацию, которая не отображается в выводе kubectl get pvc, а также возможные причины ошибок, при их наличии, можно изучить с помощью команды:
kubectl describe pvc busybox-pvc
Здесь можно обратить внимание на статус (Bound или Pending), к какому PV он привязан, какой StorageClass используется.
Монтирование и устройство на ноде
Посмотрим, как volume попадает в под и что происходит при его старте.
Когда под запускается с подключенным томом, включается kubelet — агент, управляющий контейнерами на ноде. Он выполняет следующие шаги:
- считывает спецификацию пода и информацию о требуемом томе;
- с помощью драйвера CSI подключает диск к физическому серверу (ноде);
- монтирует хранилище в файловую систему ноды;
- прокидывает директорию внутрь изолированного контейнера.
Для приложения внутри контейнера том выглядит как обычный каталог с файлами. Все тома Kubernetes монтирует в одну директорию на ноде — /var/lib/kubelet/pods/. Путь к хранилищу конкретного пода в любой момент времени можно посмотреть по следующему пути: /var/lib/kubelet/pods/<pod-uuid>/volumes/.
Визуально выглядит это так:

Практика
Чтобы узнать уникальный идентификатор пода (UUID), выполняется команда:
kubectl get pod <имя-пода> -o jsonpath='{.metadata.uid}'
После подключения к ноде можно перейти в директорию монтирования:
/var/lib/kubelet/pods/<pod-uuid>/volumes/
Вывод:
/var/lib/kubelet/pods/a958e390-1f3c-4dea-89fd-d407ab644a63/volumes/kubernetes.io~csi/pvc-528f4464-5253-4796-8908-6942af89a125/mount# ls -l
total 36
-rw-r--r-- 1 root root 18821 May 14 11:30 log.txt
drwx------ 2 root root 16384 May 14 09:42 lost+found
Команда ls -l показывает сгенерированный тестовым контейнером файл log.txt, который мы указывали в deployment при развертывании busybox.
У вас будут другие UUID, так как у каждого пода свой уникальный идентификатор.
Посмотрим еще раз на полезные команды.
Работа с PVC:
kubectl get pvc
kubectl describe pvc
Получение информации о PV:
kubectl get pv
Отладка подов:
kubectl describe pod
Просмотр логов драйвера CSI:
kubectl -n kube-system get pods | grep csi
kubectl logs <csi-pod>
Заключение
Система хранения в Kubernetes выглядит структурированно, если рассматривать ее как поэтапный процесс:
- администратор формирует PVC,
- Kubernetes через
StorageClassвызывает CSI, - CSI создает реальный диск в инфраструктуре,
- появляется PV,
- PVC и PV связываются,
- Под начинает использовать volume.
Хранилища в Kubernetes кажутся сложными ровно до тех пор, пока не раскладываются на цепочку: Под → PVC → CSI → внешнее хранилище → PV. Такой подход объясняет происхождение дисков, алгоритмы определения их размера и зоны ответственности каждого компонента.
Понимание границы между абстракциями кластера и реальным накопителем значительно облегчает диагностику проблем с выделением ресурсов.
Главный вывод: Kubernetes не занимается физическим хранением данных, а лишь управляет надежными и безопасными маршрутами доступа к ним.