StatefulSets и постоянное хранилище в Kubernetes

Развёртывание приложений с сохранением состояния, упорядоченным масштабированием и постоянными данными

Kubernetes StatefulSets — это основное решение для управления приложениями с состоянием, которым требуются стабильные идентификаторы, постоянное хранилище и упорядоченные паттерны развертывания, что критически важно для баз данных, распределенных систем и кэш-услуг.

Если вы новичок в Kubernetes или настраиваете кластер, рассмотрите возможность изучения дистрибутивов Kubernetes вроде k3s или MicroK8s для разработки, или установки Kubernetes с помощью Kubespray для кластеров производственного уровня.

презентация в кофейне Это приятное изображение было сгенерировано AI-моделью Flux 1 dev.

Что такое StatefulSets?

StatefulSets — это объект API рабочей нагрузки Kubernetes, специально предназначенный для управления приложениями с состоянием. В отличие от Deployments, которые рассматривают все поды как взаимозаменяемые, StatefulSets сохраняют уникальную идентичность для каждого пода с гарантиями порядка и уникальности.

Основные особенности:

  • Стабильные сетевые идентификаторы: Каждый под получает предсказуемое имя хоста, которое сохраняется после перезапусков
  • Постоянное хранилище: Выделенные PersistentVolumeClaims, которые следуют за подами при их переназначении
  • Упорядоченное развертывание: Поды создаются последовательно (0, 1, 2…) и завершаются в обратном порядке
  • Упорядоченные обновления: Ролинговые обновления происходят по порядку, обеспечивая стабильность приложения

StatefulSets критически важны для приложений вроде PostgreSQL, MySQL, MongoDB, Cassandra, Elasticsearch, Kafka, ZooKeeper, Redis и etcd — любых рабочих нагрузок, где важна идентичность пода и сохранение данных.

Понимание постоянного хранилища в Kubernetes

Kubernetes предоставляет сложный абстракционный слой хранилища, который отделяет управление хранилищем от жизненного цикла пода:

Компоненты хранилища

PersistentVolume (PV): Фрагмент хранилища в кластере, предоставленный администратором или динамически созданный через StorageClass. PVs существуют независимо от подов.

PersistentVolumeClaim (PVC): Запрос на хранилище от пода. PVC связываются с доступными PV, которые соответствуют их требованиям (размер, режим доступа, класс хранилища).

StorageClass: Определяет различные “классы” хранилища с разными провизерами (AWS EBS, GCE PD, Azure Disk, NFS и т.д.) и параметрами, такими как репликация, уровни производительности и политики резервного копирования.

Режимы доступа

  • ReadWriteOnce (RWO): Том монтируется как запись-запись одним узлом
  • ReadOnlyMany (ROX): Том монтируется как только для чтения многими узлами
  • ReadWriteMany (RWX): Том монтируется как запись-запись многими узлами (требует специальных бэкендов хранилища)

Архитектура хранилища StatefulSets

StatefulSets используют volumeClaimTemplates для автоматического создания PersistentVolumeClaims для каждого реплики пода. Это принципиально отличается от Deployments:

Как работают volumeClaimTemplates

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  serviceName: mysql-service
  replicas: 3
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mysql-secret
              key: password
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "fast-ssd"
      resources:
        requests:
          storage: 10Gi

Когда вы создаете этот StatefulSet:

  1. Kubernetes создает под mysql-0 и PVC data-mysql-0
  2. Затем создает под mysql-1 и PVC data-mysql-1
  3. Наконец создает под mysql-2 и PVC data-mysql-2

Каждый под получает свой собственный выделенный постоянный том объемом 10ГБ. Если mysql-1 будет удален или переназначен, Kubernetes создаст его заново и снова прикрепит тот же PVC data-mysql-1, сохранив все данные.

Создание Headless Service для StatefulSets

StatefulSets требуют Headless Service для предоставления стабильных сетевых идентификаторов:

apiVersion: v1
kind: Service
metadata:
  name: mysql-service
spec:
  clusterIP: None  # Это делает его headless сервисом
  selector:
    app: mysql
  ports:
  - port: 3306
    name: mysql

Это создает DNS-записи для каждого пода:

  • mysql-0.mysql-service.default.svc.cluster.local
  • mysql-1.mysql-service.default.svc.cluster.local
  • mysql-2.mysql-service.default.svc.cluster.local

Приложения могут подключаться напрямую к конкретным экземплярам пода, используя эти стабильные DNS-имена.

Паттерны развертывания StatefulSets

Для команд, управляющих сложными развертываниями Kubernetes, Helm Charts предоставляют мощный способ упаковки и развертывания StatefulSets с шаблонизацией, управлением версиями и управлением зависимостями. Helm упрощает управление конфигурациями StatefulSets в разных средах.

Упорядоченное масштабирование

При масштабировании с 3 до 5 реплик:

kubectl scale statefulset mysql --replicas=5

Kubernetes создает поды в порядке: mysql-3 → ожидание готовности → mysql-4

При масштабировании с 5 до 3 реплик:

kubectl scale statefulset mysql --replicas=3

Kubernetes завершает в обратном порядке: mysql-4 → ожидание завершения → mysql-3

Ролинговые обновления

StatefulSets поддерживают две стратегии обновления:

OnDelete: Ручное обновление — поды обновляются только при их удалении RollingUpdate: Автоматическое последовательное обновление в обратном порядке ординалов

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2  # Обновлять только поды с ординалом >= 2

Параметр partition позволяет проводить канарные развертывания — вы можете сначала обновить поды с высокими номерами и протестировать перед развертыванием на всех репликах.

Лучшие практики работы с хранилищем

Динамическое предоставление

Всегда используйте StorageClasses для динамического предоставления томов:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  iops: "3000"
  throughput: "125"
allowVolumeExpansion: true
reclaimPolicy: Retain

allowVolumeExpansion: Позволяет изменять размер PVC без их пересоздания reclaimPolicy: Retain сохраняет данные PV после удаления PVC, Delete автоматически удаляет их

Политики сохранения PVC

Kubernetes 1.23+ поддерживает persistentVolumeClaimRetentionPolicy:

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain    # Сохранять PVC при удалении StatefulSet
    whenScaled: Delete     # Удалять PVC при уменьшении масштаба

Варианты:

  • Retain: Сохранять PVC (стандартное поведение, самое безопасное)
  • Delete: Автоматически удалять PVC (полезно для сред разработки)

Стратегии резервного копирования

Снимки томов: Используйте ресурсы VolumeSnapshot для создания резервных копий на момент времени

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: mysql-snapshot-20251113
spec:
  volumeSnapshotClassName: csi-snapclass
  source:
    persistentVolumeClaimName: data-mysql-0

Резервное копирование на уровне приложения: Используйте инструменты вроде mysqldump, pg_dump или Velero для резервного копирования баз данных

Распределенная репликация: Настройте репликацию на уровне приложения (репликация MySQL, потоковая репликация PostgreSQL) как первую линию защиты

Реальные сценарии использования

Кластер баз данных (PostgreSQL)

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
      - name: postgres
        image: postgres:16
        ports:
        - containerPort: 5432
        env:
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: postgres-secret
              key: password
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        volumeMounts:
        - name: data
          mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: "standard"
      resources:
        requests:
          storage: 20Gi

Распределенный кэш (Redis)

Для кластеров Redis требуется как StatefulSet, так и тщательная настройка:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: redis-service
  replicas: 6
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        command:
        - redis-server
        - "--appendonly"
        - "yes"
        - "--appendfsync"
        - "everysec"
        ports:
        - containerPort: 6379
          name: redis
        volumeMounts:
        - name: data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 5Gi

Очередь сообщений (Kafka)

Kafka требует как постоянного хранилища для логов, так и стабильных сетевых идентификаторов для координации брокеров:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kafka
spec:
  serviceName: kafka-service
  replicas: 3
  selector:
    matchLabels:
      app: kafka
  template:
    metadata:
      labels:
        app: kafka
    spec:
      containers:
      - name: kafka
        image: confluentinc/cp-kafka:7.5.0
        ports:
        - containerPort: 9092
          name: kafka
        env:
        - name: KAFKA_BROKER_ID
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        volumeMounts:
        - name: data
          mountPath: /var/lib/kafka/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 100Gi

Мониторинг и устранение неполадок

Для всестороннего справочника по командам Kubernetes, используемым в этом разделе, см. Краткое руководство по Kubernetes.

Проверка состояния StatefulSet

# Просмотр деталей StatefulSet
kubectl get statefulset mysql
kubectl describe statefulset mysql

# Проверка порядка и состояния создания подов
kubectl get pods -l app=mysql -w

# Просмотр состояния PVC
kubectl get pvc
kubectl describe pvc data-mysql-0

Распространенные проблемы

Под застрял в состоянии Pending: Проверьте состояние PVC и доступность хранилища

kubectl describe pod mysql-0
kubectl get events --sort-by='.lastTimestamp'

Заполнено хранилище: Увеличьте PVC, если StorageClass это позволяет

kubectl patch pvc data-mysql-0 -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'

Под не завершается: Проверьте блокировки на уровне приложения или проблемы с размонтированием томов

kubectl delete pod mysql-0 --grace-period=0 --force

Метрики для мониторинга

  • Использование хранилища: Мониторинг емкости и процента использования PVC
  • Производительность I/O: Отслеживание IOPS, пропускной способности и задержки
  • Перезапуски подов: Частые перезапуски могут указывать на проблемы с хранилищем
  • Время привязки PVC: Медленная привязка указывает на проблемы с развертыванием

Стратегии миграции

При миграции на StatefulSets убедитесь, что ваш кластер Kubernetes правильно настроен. Для домашних лабораторий или небольших кластеров ознакомьтесь с нашим подробным сравнением дистрибутивов Kubernetes, чтобы выбрать подходящую платформу для ваших требований к нагрузке.

От Deployment к StatefulSet

  1. Создайте StatefulSet с volumeClaimTemplates
  2. Плавно уменьшите масштаб Deployment
  3. Восстановите данные из резервных копий в поды StatefulSet
  4. Обновите ссылки DNS/Service
  5. Удалите старый Deployment и PVCs

Резервное копирование перед миграцией

# Создайте снимок существующих PVCs
kubectl get pvc -o yaml > pvc-backup.yaml

# Создайте снимки томов
kubectl apply -f volume-snapshot.yaml

Меры безопасности

Шифрование хранилища

Включите шифрование “на месте” с помощью параметров StorageClass:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: encrypted-storage
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  encrypted: "true"
  kmsKeyId: arn:aws:kms:us-east-1:123456789012:key/abcd-1234

Контроль доступа

Используйте RBAC для ограничения доступа к созданию/изменению StatefulSets и PVCs:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: statefulset-manager
rules:
- apiGroups: ["apps"]
  resources: ["statefulsets"]
  verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: [""]
  resources: ["persistentvolumeclaims"]
  verbs: ["get", "list", "create", "delete"]

Политики сети

Ограничьте коммуникацию между подами:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mysql-network-policy
spec:
  podSelector:
    matchLabels:
      app: mysql
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: backend
    ports:
    - protocol: TCP
      port: 3306

Оптимизация производительности

Уровни производительности хранилища

Выбирайте подходящие StorageClasses в зависимости от нагрузки:

  • Высокие IOPS: Базы данных с интенсивным случайным чтением/записью (gp3, io2)
  • Высокая пропускная способность: Агрегация логов, аналитика (st1, sc1)
  • Сбалансированные: Общие приложения (gp3)

Распределение подов

Используйте анти-аффинность подов для распределения подов StatefulSet по зонам доступности:

spec:
  template:
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: mysql
            topologyKey: topology.kubernetes.io/zone

Запросы и ограничения ресурсов

Установите подходящие ресурсы для стабильной производительности:

resources:
  requests:
    cpu: "2"
    memory: "4Gi"
    ephemeral-storage: "10Gi"
  limits:
    cpu: "4"
    memory: "8Gi"
    ephemeral-storage: "20Gi"

Дополнительные схемы

Stateful-приложение с init-контейнерами

Используйте init-контейнеры для инициализации базы данных:

spec:
  template:
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:8.0
        command:
        - bash
        - "-c"
        - |
          if [[ ! -d /var/lib/mysql/mysql ]]; then
            mysqld --initialize-insecure --datadir=/var/lib/mysql
          fi          
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql

Многоконтейнерные поды для sidecar

Добавьте sidecar для резервного копирования или мониторинга:

spec:
  template:
    spec:
      containers:
      - name: mysql
        image: mysql:8.0
        # ... конфигурация mysql ...
      - name: backup-sidecar
        image: mysql-backup:latest
        volumeMounts:
        - name: data
          mountPath: /var/lib/mysql
          readOnly: true

ConfigMaps для динамической конфигурации

Разделите конфигурацию от определения StatefulSet:

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-config
data:
  my.cnf: |
    [mysqld]
    max_connections=200
    innodb_buffer_pool_size=2G    
---
spec:
  template:
    spec:
      containers:
      - name: mysql
        volumeMounts:
        - name: config
          mountPath: /etc/mysql/conf.d
      volumes:
      - name: config
        configMap:
          name: mysql-config

Полезные ссылки