Почему постоянное хранилище в Kubernetes - это важно
Поды в Kubernetes эфемерны по своей природе. Когда под перезапускается, падает или перемещается на другую ноду, его файловая система уничтожается. Для stateless-приложений это нормально, но база данных PostgreSQL внутри контейнера потеряет все записи после первого же рестарта. Решение - вынести данные за пределы жизненного цикла пода с помощью PersistentVolume (PV).
PV - это ресурс кластера, существующий независимо от подов. Под запрашивает доступ к тому через PersistentVolumeClaim (PVC), и после перезапуска данные остаются на месте. В этой статье мы разберем три типа хранилищ - NFS, iSCSI и Local Path - и настроим отказоустойчивый кластер на базе Rook/Ceph. Вы получите готовые манифесты YAML, которые можно сразу применить в production-среде.
Основы PersistentVolume и PersistentVolumeClaim
Kubernetes разделяет предоставление хранилища и его потребление. Администратор создает PV - физический том, а разработчик подает заявку через PVC. Кластер сопоставляет их по размеру, режиму доступа и селекторам. Жизненный цикл тома проходит четыре стадии: Provisioning (создание), Binding (связывание), Using (использование) и Reclaiming (освобождение).
Что такое PersistentVolume (PV) и PersistentVolumeClaim (PVC)
PV - это аналог физического диска, который администратор выделил для кластера. PVC - заявка на часть этого диска. Представьте: у вас есть жесткий диск на 500 ГБ (PV), а приложение запрашивает 10 ГБ (PVC). Kubernetes находит подходящий PV и резервирует запрошенный объем.
Пример простого PV типа hostPath:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-hostpath
spec:
capacity:
storage: 10Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
hostPath:
path: /mnt/data
PVC, который запрашивает этот том:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-hostpath
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
Режимы доступа определяют, как поды могут монтировать том. ReadWriteOnce (RWO) разрешает чтение и запись только одному поду на одной ноде. ReadOnlyMany (ROX) - чтение многим подам. ReadWriteMany (RWX) - чтение и запись с нескольких нод одновременно. Выбор режима критичен: если два пода попытаются записать данные в RWO-том, один из них получит ошибку.
StorageClass: динамическое выделение томов
Ручное создание PV для каждого приложения неудобно. StorageClass автоматизирует этот процесс. Вы описываете класс хранилища, а PVC, ссылаясь на него, инициирует создание тома «на лету». Параметр provisioner указывает, какой драйвер будет создавать PV, а reclaimPolicy - что делать с диском после удаления PVC: Delete (удалить), Retain (сохранить) или Recycle (очистить, устарел).
StorageClass для NFS-сервера:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-sc
provisioner: nfs.csi.k8s.io
parameters:
server: 192.168.1.100
path: /srv/nfs/k8s
reclaimPolicy: Retain
mountOptions:
- nfsvers=4.1
StorageClass для Ceph RBD:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ceph-rbd
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
clusterID: rook-ceph
pool: replicapool
imageFormat: "2"
imageFeatures: layering
reclaimPolicy: Delete
allowVolumeExpansion: true
Типы хранилищ: NFS, iSCSI, Local Path - что выбрать?
Выбор бэкенда зависит от требований к производительности, отказоустойчивости и сложности администрирования. NFS прост в настройке и поддерживает RWX, но медленнее блочных решений. iSCSI дает блочный доступ с высокой скоростью, однако требует отдельной сети хранения. Local Path обеспечивает максимальную производительность, но данные теряются при отказе ноды. В таблице - ключевые различия.
| Характеристика | NFS | iSCSI | Local Path |
|---|---|---|---|
| Тип доступа | Файловый (NAS) | Блочный (SAN) | Файловый |
| Режимы доступа | RWO, ROX, RWX | RWO, ROX | RWO |
| Производительность | Средняя | Высокая | Максимальная |
| Отказоустойчивость | Зависит от NFS-сервера | Зависит от SAN | Нет |
| Сложность | Низкая | Средняя | Минимальная |
| Сценарий | Общие файлы, веб-контент | Базы данных, транзакционные системы | Логи, кэш, тестовые среды |
Подключение NFS-сервера к Kubernetes
NFS-сервер должен быть развернут и доступен с нод кластера. Проверьте, что пакет nfs-common установлен на всех worker-нодах. Манифест PV для NFS:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteMany
nfs:
server: 192.168.1.100
path: /srv/nfs/k8s-data
PVC и под, использующий этот том:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-nfs
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Pod
metadata:
name: nfs-test
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
persistentVolumeClaim:
claimName: pvc-nfs
После применения манифестов создайте тестовый файл внутри пода: kubectl exec nfs-test -- touch /usr/share/nginx/html/test.txt. Удалите под и создайте заново - файл останется на месте. Это подтверждает, что данные хранятся вне жизненного цикла контейнера.
Использование iSCSI для блочного хранилища
iSCSI предоставляет блочное устройство, которое Kubernetes монтирует как диск. Это нужно для приложений, чувствительных к задержкам: баз данных, систем очередей. В отличие от NFS, iSCSI-том нельзя одновременно смонтировать на несколько нод в режиме RWX. Настройка требует указания targetPortal (IP и порт iSCSI-таргета), iqn (имя таргета) и lun (номер логического устройства).
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-iscsi
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteOnce
iscsi:
targetPortal: 192.168.1.200:3260
iqn: iqn.2024-07.com.example:storage.disk1
lun: 0
fsType: ext4
Перед использованием убедитесь, что iscsid запущен на всех нодах и iSCSI-инициатор настроен. Провайдеры облачных инфраструктур, такие как Timeweb Cloud, предлагают готовые блочные тома с автоматической настройкой iSCSI-подключения к кластеру Kubernetes.
Local Path: хранилище на локальном диске узла
Тип local привязывает PV к конкретной ноде через nodeAffinity. Данные хранятся на локальном диске, что дает скорость, близкую к нативной. Цена - отсутствие отказоустойчивости: при падении ноды данные недоступны до ее восстановления, а при необратимом сбое теряются безвозвратно.
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-local
spec:
capacity:
storage: 200Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
local:
path: /data/local-pv
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- worker-node-1
Local Path подходит для логов, временных файлов и кэша. В production-средах для stateful-приложений лучше использовать распределенные хранилища. Подробное сравнение подходов к управлению постоянными данными в кластерах есть в нашем руководстве по Docker Swarm и Kubernetes.
CSI-драйверы: универсальный интерфейс для систем хранения
Container Storage Interface (CSI) - стандарт, который позволяет поставщикам хранилищ создавать плагины, работающие с любым оркестратором контейнеров. Архитектура CSI разделена на две части: контроллер (управляет созданием и удалением томов) и нодовый плагин (монтирует тома в поды). Контроллер обычно запускается как Deployment, нодовый плагин - как DaemonSet на каждой ноде.
CSI заменяет старый механизм in-tree-драйверов, которые требовали изменения кода Kubernetes. Теперь любой вендор может выпустить драйвер независимо от цикла разработки Kubernetes. Для NFS, Ceph, TrueNAS и других систем существуют готовые CSI-драйверы.
Установка и настройка NFS CSI Driver
NFS CSI Driver позволяет динамически создавать подтома на NFS-сервере. Установка через Helm:
helm repo add csi-driver-nfs https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts
helm install csi-driver-nfs csi-driver-nfs/csi-driver-nfs --namespace kube-system --version v4.7.0
После установки создайте StorageClass, который будет использовать этот драйвер. Параметры server и path указывают на корневую директорию NFS-шары, где драйвер будет создавать поддиректории для каждого PVC:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
server: 192.168.1.100
path: /srv/nfs/dynamic
onDelete: archive
reclaimPolicy: Retain
volumeBindingMode: Immediate
Теперь любой PVC с storageClassName: nfs-csi автоматически получит поддиректорию на NFS-сервере. Параметр onDelete: archive при удалении PVC переименовывает директорию в archived-<имя>, предотвращая случайную потерю данных.
Развертывание отказоустойчивого хранилища с Rook/Ceph
Rook - оператор Kubernetes, который автоматизирует развертывание и управление Ceph. Ceph - распределенная система хранения, объединяющая диски всех нод в единый пул с репликацией данных. При отказе узла или диска Ceph автоматически перераспределяет данные на оставшиеся носители. Rook/Ceph поддерживает три типа хранилища: RBD (блочное), CephFS (файловое) и RGW (объектное, S3-совместимое).
Это решение для production-сред, где потеря данных недопустима. Наше руководство по SDS-кластерам содержит детальное сравнение Ceph и GlusterFS с готовыми конфигурациями.
Установка Rook оператора
Клонируйте репозиторий Rook и примените манифесты:
git clone --single-branch --branch v1.14.9 https://github.com/rook/rook.git
cd rook/deploy/examples
kubectl create -f crds.yaml -f common.yaml -f operator.yaml
Проверьте, что поды оператора запущены:
kubectl -n rook-ceph get pod
Оператор состоит из трех компонентов: rook-ceph-operator (управляет кластером), rook-discover (обнаруживает диски на нодах) и rook-ceph-agent (выполняет операции на узлах).
Создание CephCluster и настройка пулов
Манифест CephCluster описывает, какие узлы и диски использовать. В примере ниже кластер разворачивается на трех нодах с пометкой role: storage-node. Параметр count: 3 задает количество мониторов Ceph, обеспечивающих кворум. Раздел storage указывает, что использовать: все свободные диски (useAllDevices: true) или конкретные устройства.
apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
name: rook-ceph
namespace: rook-ceph
spec:
cephVersion:
image: quay.io/ceph/ceph:v18.2.2
dataDirHostPath: /var/lib/rook
mon:
count: 3
allowMultiplePerNode: false
storage:
useAllNodes: true
useAllDevices: true
config:
osdsPerDevice: "1"
placement:
all:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: role
operator: In
values:
- storage-node
После применения манифеста создайте пул для блочных устройств:
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
name: replicapool
namespace: rook-ceph
spec:
failureDomain: host
replicated:
size: 3
requireSafeReplicaSize: true
Параметр failureDomain: host гарантирует, что реплики данных размещаются на разных физических узлах. size: 3 означает трехкратную репликацию - данные переживут отказ двух узлов одновременно.
Подключение приложений к Ceph через StorageClass
Создайте StorageClass для RBD:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rook-ceph-block
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
clusterID: rook-ceph
pool: replicapool
imageFormat: "2"
imageFeatures: layering
csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
csi.storage.k8s.io/controller-expand-secret-name: rook-csi-rbd-provisioner
csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph
csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
reclaimPolicy: Delete
allowVolumeExpansion: true
PVC и под для проверки:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: rbd-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: rook-ceph-block
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Pod
metadata:
name: ceph-test
spec:
containers:
- name: app
image: busybox
command: ["sleep", "3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: rbd-pvc
Запишите данные: kubectl exec ceph-test -- sh -c "echo 'persistent' > /data/test.txt". Удалите под и создайте снова - файл на месте. Более детально работа с PVC и StorageClass для stateful-приложений разобрана в руководстве по настройке CSI-драйверов.
Тестирование отказоустойчивости
Симулируйте отказ узла с OSD (Object Storage Daemon). Найдите ноду, на которой работает OSD: kubectl -n rook-ceph get pods -l app=rook-ceph-osd -o wide. Выключите эту ноду или удалите под OSD: kubectl -n rook-ceph delete pod .
Ceph обнаружит отказ в течение нескольких минут и начнет ребалансировку данных на оставшиеся OSD. Наблюдайте за процессом: kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph status. Пока репликация не завершена, кластер в состоянии HEALTH_WARN, но данные доступны. После восстановления состояние возвращается к HEALTH_OK.
Приложение с PVC на Ceph продолжит работать - под перезапустится на другой ноде, а том перемонтируется. Это ключевое преимущество распределенного хранилища перед Local Path.
Готовые манифесты YAML для быстрого старта
Ниже - коллекция всех манифестов из статьи в одном месте. Скопируйте, замените IP-адреса и пути на свои, примените через kubectl apply -f.
PV и PVC для NFS (статическое выделение):
# pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteMany
nfs:
server: 192.168.1.100
path: /srv/nfs/k8s-data
---
# pvc-nfs.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc-nfs
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
StorageClass для NFS CSI (динамическое выделение):
# sc-nfs-csi.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-csi
provisioner: nfs.csi.k8s.io
parameters:
server: 192.168.1.100
path: /srv/nfs/dynamic
onDelete: archive
reclaimPolicy: Retain
StorageClass для Ceph RBD:
# sc-ceph-rbd.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rook-ceph-block
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
clusterID: rook-ceph
pool: replicapool
imageFormat: "2"
imageFeatures: layering
csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
reclaimPolicy: Delete
allowVolumeExpansion: true
StatefulSet с PVC (на примере PostgreSQL):
# statefulset-postgres.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
env:
- name: POSTGRES_PASSWORD
value: "securepassword"
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: rook-ceph-block
resources:
requests:
storage: 20Gi
StatefulSet с volumeClaimTemplates автоматически создает отдельный PVC для каждого пода. При перезапуске пода на другой ноде PVC следует за ним, сохраняя данные.
Лучшие практики и типичные ошибки
Ошибки при настройке хранилища в Kubernetes дорого обходятся. Три самые частые: несовпадение режимов доступа между PV и PVC, забытый reclaimPolicy: Delete (данные удаляются вместе с PVC), отсутствие мониторинга заполнения дисков. Проверяйте соответствие accessModes на всех этапах. Для production-сред всегда используйте динамическое выделение через StorageClass - это исключает ручные ошибки при создании PV.
Настройте резервное копирование PVC. Инструменты вроде Velero позволяют создавать снапшоты и сохранять их в S3-совместимое хранилище. Без бэкапов даже трехкратная репликация Ceph не спасет от случайного удаления данных администратором.
Мониторинг хранилища с Prometheus и Grafana
Ceph экспортирует метрики через встроенный модуль Prometheus. Включите его в манифесте CephCluster:
monitoring:
enabled: true
rulesNamespace: rook-ceph
prometheusRule:
enabled: true
Prometheus автоматически обнаружит сервис rook-ceph-mgr и начнет собирать метрики: использование емкости, количество объектов, состояние OSD, задержки ввода-вывода. Настройте алерты на заполнение пула выше 80% и на переход OSD в состояние down.
Для Grafana импортируйте дашборд с ID 2842 (Ceph Cluster) или 5346 (Ceph OSD). Они показывают графики производительности, состояние репликации и прогноз заполнения дисков. Без мониторинга вы рискуете узнать о проблеме, когда место закончится и поды начнут падать с ошибкой «No space left on device».
Заключение: выбор стратегии хранения для вашего кластера
Выбор бэкенда сводится к трем факторам: масштаб, требования к отказоустойчивости и доступные ресурсы на администрирование. NFS подходит для небольших кластеров и общих файловых хранилищ - настройка занимает минуты, но отказ NFS-сервера остановит все зависимые поды. Rook/Ceph требует минимум трех нод и времени на освоение, но дает самовосстановление и горизонтальное масштабирование. Local Path - выбор для тестовых сред и приложений, которые сами реплицируют данные (например, Kafka или Elasticsearch).
Начните с развертывания Rook/Ceph в тестовом кластере из трех виртуальных машин. Пройдите путь от установки оператора до тестирования отказоустойчивости. Когда вы увидите, как под с базой данных автоматически переезжает на другую ноду без потери данных, выбор для production станет очевидным. Готовые манифесты из этой статьи - ваша отправная точка.