Volumes, PVC и StorageClass
Что нужно знать перед уроком
- M1.01 — Pod и контейнеры — нужно понимать жизненный цикл Pod
Что нужно знать перед уроком
M1 урок 1 — Pod’ы эфемерны и пересоздаются. Если приложению нужно сохранять данные между пересозданиями (БД, файлы пользователей) — нужен механизм хранения, независимый от жизненного цикла Pod’а. Это и есть тема урока.
Теория
Эфемерные volumes vs Persistent Volumes
Простейший том — emptyDir — директория, создаваемая на узле при старте Pod’а и удаляемая безвозвратно при удалении Pod’а (используется для временных данных, кэшей, обмена файлами между контейнерами одного Pod’а — вспомните M1.02, где Homer использовал похожий подход для конфигурации). Для данных, которые должны переживать пересоздание Pod’а, нужна отдельная пара объектов с жизненным циклом, не привязанным к Pod’у:
- PersistentVolume (PV) — cluster-scoped объект, представляющий реальный кусок хранилища (диск, NFS-экспорт, блочное устройство CSI-драйвера);
- PersistentVolumeClaim (PVC) — namespace-scoped запрос на хранилище с нужными параметрами (размер, режим доступа), который Kubernetes связывает («биндит») с подходящим PV.
Pod ссылается не на PV напрямую, а на PVC — это даёт разделение ответственности: администратор кластера управляет реальными PV (или настраивает динамическое провижининг через StorageClass), разработчик Application просит только «дайте мне 10Gi с таким-то режимом доступа», не заботясь о физическом устройстве хранения.
| |
accessModes: от ReadWriteOnce до строгого ReadWriteOncePod
| Режим | Значение |
|---|---|
ReadWriteOnce (RWO) | монтируется на чтение-запись одним узлом, но потенциально несколькими Pod’ами на этом же узле одновременно |
ReadOnlyMany (ROX) | монтируется многими узлами только на чтение |
ReadWriteMany (RWX) | монтируется многими узлами на чтение-запись (требует сетевого хранилища, например NFS/CephFS) |
ReadWriteOncePod (RWOP) | строгая эксклюзивность на уровне всего кластера — гарантированно только один конкретный Pod, а не просто один узел |
Частая ошибка — думать, что ReadWriteOnce защищает от двух Pod’ов, использующих один и тот же PVC одновременно. Это не так: RWO ограничивает узел, а не Pod — если у вас два Pod’а на одном узле оба смонтировали один RWO-PVC, это разрешено и может привести к порче данных, если приложение (например, БД) не готово к конкурентному доступу нескольких процессов. Именно для таких случаев ввели ReadWriteOncePod.
StorageClass.volumeBindingMode
StorageClass описывает «класс» динамически провижинируемого хранилища (например, standard — локальный SSD, fast-nvme — более быстрый диск). Ключевой параметр — volumeBindingMode:
| Режим | Когда происходит провижининг | Риск |
|---|---|---|
Immediate (по умолчанию) | сразу при создании PVC, до того как Pod запланирован на узел | если хранилище топологически привязано к конкретной зоне/узлу (например, локальный диск), а планировщик потом выберет другой узел — Pod окажется unschedulable |
WaitForFirstConsumer | провижининг откладывается до момента, когда планировщик уже выбрал узел для Pod’а, использующего PVC | обязателен для локальных дисков (local-path-provisioner, аналогичные), устраняет риск Immediate |
| |
reclaimPolicy: Delete vs Retain
Что происходит с PV после удаления связанного PVC:
Delete(типичный дефолт для динамически провижинируемых классов) — PV и физические данные уничтожаются вместе с PVC;Retain— PV остаётся (в статусеReleased), данные физически сохраняются, но требуют ручного администрирования для повторного использования.
Это первое касание темы backup/защиты данных, к которой мы вернёмся в M6 уроке 7 (аннотация helm.sh/resource-policy: keep со стороны Nelm) и в M10 уроке 8 (честное разграничение, что бэкапится, а что нет).
Ручное связывание через selector
PVC может явно указать selector.matchLabels/matchExpressions, чтобы связаться с конкретным заранее созданным PV, минуя динамический провижининг — редкий, но иногда нужный случай (например, известный набор дисков с данными, которые нужно смонтировать в определённый Pod).
Частые ошибки и подводные камни
- Путать
ReadWriteOnceс эксклюзивностью на уровне Pod’а. RWO ограничивает узел, не Pod — для истинной эксклюзивности нуженReadWriteOncePod. - Использовать
volumeBindingMode: Immediateс топологически привязанным хранилищем. Классическая причина Pod’ов, зависших вPendingс ошибкой планирования «no nodes available» — провижининг случился раньше, чем планировщик выбрал узел. - Не проверять
reclaimPolicyперед удалением PVC на важных данных. Если класс хранения используетDelete— удаление PVC (даже случайное, например приkubectl delete -fпо всему манифесту) стирает данные безвозвратно.
Practice: Linux / Диски
На уровне узла PV, использующий local-path-provisioner-подобный механизм, — это просто директория на файловой системе узла (в кластере kind — внутри Docker-контейнера узла). Через docker exec на узел можно увидеть эти директории напрямую — полезно для понимания, что «постоянное хранилище» в разработческом кластере физически живёт ровно до тех пор, пока жив Docker-контейнер узла (kind delete cluster уничтожит и эти данные тоже — это не противоречит PVC-абстракции, просто напоминание о природе kind).
| |
Практика в кластере
Miniflux — минималистичный self-hosted RSS-читалка на Go, обязательно требует PostgreSQL — хороший повод развернуть Postgres с PVC «вручную» (без Module — это чистый K8s-пример, зависимость от managed-postgres в терминах Application появится только в M6).
| |
- Убедитесь, что PVC связался (
Bound) с автоматически созданным PV:
| |
- Запишите тестовые данные и пересоздайте Pod, чтобы убедиться, что данные переживают пересоздание:
| |
- Проверьте
reclaimPolicyвашего PV и что произойдёт при удалении PVC (осторожно, это удалит данные):
| |
Практика разработки
Используются официальные образы postgres и (при желании расширить пример) miniflux/miniflux; собственная сборка — в M3.
Шпаргалка команд урока
| |
Вопросы для самопроверки
RWO ограничивает узел, а не конкретный Pod — для гарантии единственного Pod'а нужен ReadWriteOncePod.
Источник: Kubernetes — persistent volumes
WaitForFirstConsumer откладывает провижининг до момента, когда планировщик уже выбрал узел, устраняя риск несовпадения зоны хранилища и назначенного узла.
Источник: Kubernetes — storage classes
Delete — типичный дефолт динамически провижинируемых классов: PV и его физические данные уничтожаются сразу вместе с PVC.
Источник: Kubernetes — persistent volumes
PVC — namespace-scoped запрос; администратор/StorageClass отвечают за реальное предоставление PV, разработчик — только за параметры запроса.
Источник: Kubernetes — persistent volumes
RWOP — более строгий режим, появившийся именно для устранения слабости RWO, которая ограничивает лишь узел, а не конкретный Pod.
Источник: Kubernetes — persistent volumes
Рекомендуемая литература
Официальная документация
- kubernetes.io — concepts/storage/persistent-volumes
- kubernetes.io — concepts/storage/storage-classes
- kubernetes.io — Persistent Volumes.
Статьи и блоги
- Kubernetes для начинающих: что такое Pod и как с ним работать — Flant на Habr.
- Основные концепции сетевой архитектуры Kubernetes — Habr.
- Миграция с ingress-nginx: выбор нового контроллера — Habr.
Книги
- Marko Lukša. Kubernetes in Action. 2nd ed., 2022. ISBN 978-1617297618.
- Kelsey Hightower, Brendan Burns, Joe Beda. Kubernetes: Up and Running. 3rd ed., 2022. ISBN 978-1098110201.
- Michael Hausenblas, Stefan Schimanski. Programming Kubernetes. 2019. ISBN 978-1492047107.
Связанные материалы
- Предыдущий урок: M1.05 — Namespace, labels, selectors.
- Следующий урок: M1.07 — Probes.
- Дальше:
reclaimPolicy: Retainи защита PVC от удаления Nelm — M6 урок 7; честное разграничение backup — M10 урок 8.
✓ Урок пройден — все вопросы самопроверки отвечены верно