Интеграция систем хранения данных с Kubernetes: PersistentVolume, NFS, Rook/Ceph | AdminWiki

Интеграция систем хранения данных с Kubernetes: PersistentVolume, NFS, Rook/Ceph

22 июля 2026 11 мин. чтения

Почему постоянное хранилище в 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 станет очевидным. Готовые манифесты из этой статьи - ваша отправная точка.

Поделиться:
Сохранить гайд? В закладки браузера