Kubernetes

Теория: StatefulSet

В предыдущих уроках мы работали с Deployment — контроллером для stateless-приложений. Deployment создаёт Pod с произвольными именами, не гарантирует порядок запуска и предполагает, что все реплики взаимозаменяемы. Для веб-серверов и API это работает отлично.

Но есть приложения, которым нужна стабильная идентичность: базы данных, кластеры Kafka, Elasticsearch, ZooKeeper. Каждый экземпляр уникален, имеет своё состояние, и замена одного экземпляра другим — это не просто перезапуск, а потенциальная потеря данных или разрушение кластера. StatefulSet решает эту проблему.

Зачем нужен StatefulSet

Представьте кластер PostgreSQL с репликацией: один primary и два replica. Каждый инстанс должен знать свою роль и иметь стабильный адрес для связи с другими нодами кластера. В реальных HA-кластерах primary выбирается динамически — это делает оператор или само приложение (например, Patroni для PostgreSQL). Обычно создают отдельный Service, который указывает на текущего primary через labels, а не привязываются к database-0 навсегда.

Или кластер Elasticsearch из трёх нод. Каждая нода хранит часть данных (шарды). Если нода перезапустится с потерей данных — кластер потеряет часть индекса.

Deployment не подходит для таких сценариев:

  • Pod получают случайные имена (nginx-deployment-7b8c9d5f4-xk2p9)
  • При перезапуске Pod получает новое имя и новый IP
  • Нет гарантии порядка запуска
  • Нет связи между Pod и его хранилищем

StatefulSet даёт то, что нужно stateful-приложениям:

  • Стабильные, предсказуемые имена Pod (database-0, database-1, database-2)
  • Стабильные сетевые идентификаторы через Headless Service
  • Гарантированный порядок запуска и остановки
  • Постоянное хранилище, привязанное к конкретному Pod

Важно понимать: StatefulSet даёт стабильность идентичности Pod (имя, DNS) и привязку PVC к конкретному Pod. Но он не делает приложение кластерным или отказоустойчивым сам по себе. За репликацию, failover и выбор primary отвечает само приложение или оператор (Patroni, Zalando Postgres Operator, Elasticsearch Operator и т.д.).

Отличия от Deployment

DeploymentStatefulSet
Имена Pod случайные: app-7b8c9d5f4-xk2p9Имена Pod предсказуемые: app-0, app-1, app-2
Pod взаимозаменяемыКаждый Pod уникален
Все Pod создаются параллельноPod создаются последовательно: 0 → 1 → 2
При удалении Pod — новый получает новое имяПри удалении Pod — новый получает то же имя
Нет встроенного механизма «PVC на реплику»Каждый Pod получает свой PVC через volumeClaimTemplates
Обычный ServiceТребуется Headless Service

Как работает StatefulSet

Создадим простой StatefulSet:

apiVersion: v1
kind: Service
metadata:
  name: nginx-headless
spec:
  clusterIP: None  # Это делает Service headless
  selector:
    app: nginx
  ports:
    - port: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: nginx-headless  # Обязательно указать headless service
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.27
          ports:
            - containerPort: 80

После применения вы увидите:

kubectl get pods

Pod создаются последовательно: web-0 запускается первым, только после его готовности запускается web-1, затем web-2.

Headless Service и сетевые идентификаторы

Обычный Service имеет ClusterIP и балансирует трафик между Pod. Headless Service (clusterIP: None) работает иначе — он создаёт DNS-записи для каждого Pod отдельно.

Headless Service нужен, чтобы у каждого Pod появились стабильные DNS-имена вида:

<pod-name>.<service-name>.<namespace>.svc.cluster.local

Для нашего примера:

web-0.nginx-headless.default.svc.cluster.local
web-1.nginx-headless.default.svc.cluster.local
web-2.nginx-headless.default.svc.cluster.local

Это позволяет обращаться к конкретному Pod по имени. Реплика базы данных может подключиться к нужной ноде кластера по её стабильному DNS-имени.

volumeClaimTemplates: хранилище для каждого Pod

В Deployment все Pod используют один PVC (или не используют вовсе). В StatefulSet каждому Pod нужен свой PVC — свои данные, своё хранилище.

volumeClaimTemplates — это шаблон PVC. StatefulSet автоматически создаёт PVC для каждого Pod:

apiVersion: v1
kind: Service
metadata:
  name: database-headless
spec:
  clusterIP: None
  selector:
    app: database
  ports:
    - port: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: database
spec:
  serviceName: database-headless
  replicas: 3
  selector:
    matchLabels:
      app: database
  template:
    metadata:
      labels:
        app: database
    spec:
      containers:
        - name: postgres
          image: postgres:15
          env:
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-secret
                  key: password
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
          ports:
            - containerPort: 5432
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi

StatefulSet создаст:

  • Pod: database-0, database-1, database-2
  • PVC: data-database-0, data-database-1, data-database-2

Каждый Pod привязан к своему PVC. При перезапуске database-1 он получит тот же PVC data-database-1 с теми же данными.

Порядок создания и удаления

StatefulSet гарантирует порядок:

При создании: По умолчанию StatefulSet использует podManagementPolicy: OrderedReady — Pod создаются последовательно. app-1 не запустится, пока app-0 не станет Ready. Это важно для кластерных приложений, где порядок запуска имеет значение.

При удалении: Pod удаляются в обратном порядке. Сначала app-2, потом app-1, потом app-0. Это позволяет корректно остановить кластер.

При обновлении: По умолчанию используется стратегия RollingUpdate. Pod обновляются в обратном порядке: сначала app-2, потом app-1, потом app-0.

Можно изменить поведение:

spec:
  podManagementPolicy: Parallel  # Создавать Pod параллельно

Но для большинства stateful-приложений последовательный запуск — это то, что нужно.

Обновление StatefulSet

StatefulSet обновляет Pod по одному, в обратном порядке: сначала app-2, потом app-1, потом app-0. Каждый Pod должен стать Ready, прежде чем начнётся обновление следующего.

Можно контролировать обновление через partition:

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 2

С partition: 2 обновятся только Pod с ordinal >= 2 (то есть app-2 и выше). Pod app-0 и app-1 останутся на старой версии. Это полезно для канареечного обновления — сначала обновить один Pod, проверить, потом остальные.

Масштабирование

При увеличении реплик:

kubectl scale statefulset database --replicas=5

Создаются database-3, database-4 с новыми PVC data-database-3, data-database-4.

При уменьшении реплик:

kubectl scale statefulset database --replicas=2

Удаляются database-4, database-3, database-2 (в обратном порядке). Но PVC остаются! Данные не теряются. Если снова увеличить реплики — Pod получат свои старые PVC.

Это важная особенность: StatefulSet не удаляет PVC автоматически. Для удаления PVC нужно сделать это вручную:

kubectl delete pvc data-database-2 data-database-3 data-database-4

Когда использовать StatefulSet

Используйте StatefulSet:

  • Базы данных: PostgreSQL, MySQL, MongoDB.
  • Кластерные системы: Kafka, Elasticsearch, ZooKeeper, etcd.
  • Приложения, которым нужна стабильная сетевая идентичность.
  • Приложения, которым нужно постоянное хранилище, привязанное к конкретному экземпляру.

Используйте Deployment:

  • Stateless веб-серверы и API.
  • Воркеры, обрабатывающие очереди.
  • Любые приложения, где экземпляры взаимозаменяемы.

Частые ошибки

  • Забыли Headless Service — StatefulSet требует serviceName, который указывает на Headless Service. Без него не будет стабильных DNS-имён вида pod-name.service-name.
  • PVC в статусе Pending — проблема не в StatefulSet, а в StorageClass или provisioner. Проверьте kubectl get sc и kubectl describe pvc <name>.
  • Pod не запускается, ждёт предыдущий — это нормальное поведение при podManagementPolicy: OrderedReady. web-1 ждёт, пока web-0 станет Ready. Проверьте, почему web-0 не готов.
  • Думают, что StatefulSet = HA — StatefulSet не настраивает репликацию и failover. Это делает само приложение или оператор.
  • Удалили StatefulSet, PVC остались — это by design. Данные не удаляются автоматически. Удалите PVC вручную, если они больше не нужны.
  • Привязались к app-0 как к primary — в реальных HA-кластерах primary выбирается динамически. Используйте отдельный Service с селектором на текущего primary.

Что запомнить

  • StatefulSet — для приложений со состоянием: базы данных, кластеры. Даёт стабильную идентичность, но не делает приложение отказоустойчивым автоматически.
  • Стабильные именаapp-0, app-1, app-2 вместо случайных.
  • Headless Service — создаёт DNS-записи для каждого Pod: app-0.service-name.
  • volumeClaimTemplates — автоматически создаёт PVC для каждого Pod.
  • OrderedReady — по умолчанию Pod создаются по порядку, каждый ждёт готовности предыдущего.
  • partition — позволяет контролировать обновление, обновляя только часть Pod.
  • PVC не удаляются — при масштабировании вниз или удалении Pod данные сохраняются.

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845