Learning Platform
Глоссарий Troubleshooting
Урок 06.05 · 22 мин
Средний
StatefulSetStateful workloadsHeadless servicePVCOrdered deploymentStable identity

StatefulSet: ordered stateful workloads

Deployment и ReplicaSet делают Pod-ы взаимозаменяемыми — каждый replica идентичен любому другому, имена случайные, дискa нет (или есть, но эфемерный). Это идеально для stateless HTTP-сервисов: nginx, любое REST API, frontend.

Но огромный класс приложений так не работает. Базы данных, кластерные системы (Kafka, etcd, Cassandra, Zookeeper) — у них каждая нода имеет свою identity: уникальный hostname, свой персистентный disk, своё место в кластере. Их нельзя просто пересоздать с тем же набором replicas — это либо разрушит кластер, либо приведёт к split brain.

Для таких workloads в Kubernetes есть StatefulSet.

NOTE

StatefulSet входит в CKAD curriculum (v1.33+) — особенно тема volumeClaimTemplates и identity Pod-ов. Это не «глубокий» экзамен по distributed databases, но базовые сценарии (создать StatefulSet с PVC, понимать ordinal naming, разница с Deployment) — обязательны. В реальной работе любой Helm chart БД использует StatefulSet.


Брокеры Kafka: каждый имеет свой ID и persistent storage

Что такое StatefulSet

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres-headless
  replicas: 3
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16
          ports:
            - containerPort: 5432
              name: postgres
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: fast-ssd
        resources:
          requests:
            storage: 100Gi

Поля, специфичные для StatefulSet:

  • spec.serviceName — имя headless Service, который обеспечивает DNS для Pod-ов
  • spec.volumeClaimTemplates — шаблоны PVC, по одному PVC на каждый Pod
  • spec.replicas, spec.selector, spec.template — как в Deployment
  • spec.podManagementPolicyOrderedReady (default) или Parallel
  • spec.updateStrategyRollingUpdate (default) или OnDelete

Уникальная identity: имена и DNS

В StatefulSet Pod-ы НЕ получают случайные имена. Они называются строго по индексу:

postgres-0
postgres-1
postgres-2

Это stable identity: даже после удаления и пересоздания Pod снова получит имя postgres-0. Никаких суффиксов-хешей как в Deployment.

Headless Service: stable DNS

Для DNS-резолва используется headless Service (Service без ClusterIP):

apiVersion: v1
kind: Service
metadata:
  name: postgres-headless
spec:
  clusterIP: None  # ← headless
  selector:
    app: postgres
  ports:
    - port: 5432
      name: postgres

Headless Service не делает load balancing. Вместо одного ClusterIP DNS выдаёт A-record на каждый Pod:

postgres-0.postgres-headless.default.svc.cluster.local  →  10.244.1.5
postgres-1.postgres-headless.default.svc.cluster.local  →  10.244.2.7
postgres-2.postgres-headless.default.svc.cluster.local  →  10.244.3.9

Это даёт стабильные DNS-имена для каждой replica. PostgreSQL primary может настроить replication с postgres-1.postgres-headless как replica, и это будет работать через ребуты, миграции на другие ноды — потому что DNS-имя стабильное.

Identity StatefulSet Pod-а
Pod nameСтабильное имя <statefulset>-<ordinal>. Ordinal начинается с 0. Даже после удаления и пересоздания имя то же самое.
HostnameHostname контейнера = pod name. Это не Pod IP — это logical name, который сервис внутри Pod-а видит через hostname / uname.
резолвится через headless Service
DNS A recordpostgres-1.postgres-headless.<ns>.svc.cluster.local — резолвится в текущий Pod IP. При пересоздании Pod-а DNS обновляется (через CoreDNS + EndpointSlices), но имя остаётся.
PVC по этому имени
PVCКаждый Pod получает свой PVC из volumeClaimTemplates. Имя PVC: <vct-name>-<pod-name>. PVC переживает удаление Pod-а — при пересоздании Pod снова замонтирует тот же PVC.

volumeClaimTemplates: per-Pod persistent storage

В Deployment, если указан volumes.persistentVolumeClaim, все replicas делят один PVC. Это работает только для ReadWriteMany (NFS, EFS), иначе только одна replica может его смонтировать.

StatefulSet делает по-другому. Поле spec.volumeClaimTemplates — это шаблон PVC, на основе которого controller создаёт отдельный PVC на каждый Pod:

postgres-0   →   PVC data-postgres-0   →   PV pv-xxxx (100Gi)
postgres-1   →   PVC data-postgres-1   →   PV pv-yyyy (100Gi)
postgres-2   →   PVC data-postgres-2   →   PV pv-zzzz (100Gi)

При создании Pod-а из volumeClaimTemplates автоматически создаётся PVC. При scale up создаётся новый PVC. При удалении Pod-а PVC ОСТАЁТСЯ — это сделано специально для безопасности данных.

WARNING

Когда вы удаляете StatefulSet (kubectl delete sts postgres), Pod-ы удаляются — но PVC остаются. Это защита: данные БД не должны исчезнуть при удалении workload-объекта. Чтобы удалить и PVC, нужно явно: kubectl delete pvc -l app=postgres.

С Kubernetes v1.27 GA есть поле spec.persistentVolumeClaimRetentionPolicy, которое позволяет менять это поведение:

spec:
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Retain   # или Delete
    whenScaled: Retain    # или Delete
  • whenDeleted: Delete — удалять PVC при удалении всего StatefulSet
  • whenScaled: Delete — удалять PVC при scale down (когда replicas уменьшается)

По умолчанию обе политики Retain — это безопасно, но требует ручной очистки.


Ordered start/stop

В StatefulSet Pod-ы создаются строго по порядку: сначала ordinal 0, ждём пока он станет Ready, потом 1, потом 2.

При удалении (например, scale down с 3 до 1) — обратный порядок: сначала ordinal 2, потом 1.

kubectl get pods -l app=postgres -w
# postgres-0   Pending → ContainerCreating → Running → Ready
# postgres-1   Pending  (ждём пока postgres-0 будет Ready)
# postgres-1   ContainerCreating → Running → Ready
# postgres-2   Pending
# ...

Это критично для кластерных систем: первый Pod может быть seed/primary, остальные подключаются к нему. Если их запустить параллельно, они могут конфликтовать.

podManagementPolicy: Parallel

С v1.7 можно отключить ordered start:

spec:
  podManagementPolicy: Parallel

При Parallel все Pods создаются и удаляются одновременно. Это даёт быстрый bootstrap, но не подходит для тех, кому нужен ordered start (etcd, Zookeeper).

Parallel не влияет на updateStrategy — там всё равно по одному.


Update strategy

spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition: 0

RollingUpdate (default)

Controller обновляет Pods в обратном порядке: сначала ordinal N-1, ждёт пока станет Ready, потом N-2, и так до 0.

Это даёт zero-downtime для primary/replica топологий: replicas обновляются первыми, потом primary.

partition: canary rollout

spec.updateStrategy.rollingUpdate.partition: K — обновляются только Pods с ordinal >= K. Это даёт canary-стиль: можно обновить только postgres-2, постоить, посмотреть на стабильность, потом снизить partition.

# Все 3 Pod-а на старой версии
kubectl get sts postgres -o jsonpath='{.spec.updateStrategy.rollingUpdate.partition}'
# 3

# Меняем image и partition=2 — обновится только postgres-2
kubectl patch sts postgres -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":2}}}}'
kubectl set image sts/postgres postgres=postgres:16.2

# Смотрим, потом снижаем
kubectl patch sts postgres -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":1}}}}'
# Теперь обновится и postgres-1

kubectl patch sts postgres -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":0}}}}'
# Обновится postgres-0

OnDelete

updateStrategy.type: OnDelete — controller НЕ обновляет автоматически. Нужно вручную удалить Pod, чтобы он пересоздался с новым template. Для критичных stateful workloads.


StatefulSet vs Deployment: когда что

Когда выбирать что
DeploymentStateless apps. Replicas взаимозаменяемые. Имена random. Storage эфемерный или shared (ReadWriteMany). Use case: HTTP-сервисы, frontend, любые stateless workers.
StatefulSetStateful apps с identity. Replicas различимы по имени и ordinal. Каждая имеет свой persistent storage. Use case: БД, brokers, любое cluster software.
Имена Pod-овDeployment: random suffix (web-7d4b9f-abc12). StatefulSet: ordinal (postgres-0, postgres-1).
StorageDeployment: shared PVC или эфемерный emptyDir. StatefulSet: per-Pod PVC из volumeClaimTemplates.
Start orderDeployment: параллельно. StatefulSet: ordered 0,1,2,... (или Parallel если задано).
DNSDeployment: через обычный Service (ClusterIP). StatefulSet: дополнительно headless Service с per-Pod DNS.

Правило простое: если у workload есть persistent state на disk или identity (которая используется другими нодами кластера) — нужен StatefulSet. Иначе — Deployment.


Типичные use cases

Databases

  • PostgreSQL primary + replicas — primary постит WAL, replicas подписаны через DNS-имена
  • MySQL Group Replication — нужны stable hostnames для кластера
  • MongoDB ReplicaSet — primary election зависит от identity

Distributed coordination

  • etcd — нужны 3-5 узлов с известными именами для Raft peer-to-peer
  • Zookeeper — myid файл персистится на disk, identity критична
  • Consul — gossip cluster, нужны stable members

Message brokers

  • Kafka — broker.id персистится, leader election топиков привязан к ID
  • RabbitMQ cluster mode — узлы знают друг друга по hostname
  • NATS Streaming — нужен persistent storage для replicated message log

kubectl: команды

# Создать
kubectl apply -f sts.yaml

# Список
kubectl get sts
# NAME       READY   AGE
# postgres   3/3     5m

# Pods (заметьте ordered names)
kubectl get pods -l app=postgres
# postgres-0   Running   3m
# postgres-1   Running   2m
# postgres-2   Running   1m

# PVC, созданные из volumeClaimTemplates
kubectl get pvc -l app=postgres
# data-postgres-0   Bound   pv-xxxx   100Gi   RWO   fast-ssd
# data-postgres-1   Bound   pv-yyyy   100Gi   RWO   fast-ssd
# data-postgres-2   Bound   pv-zzzz   100Gi   RWO   fast-ssd

# Scale up — новый Pod postgres-3 и новый PVC data-postgres-3
kubectl scale sts/postgres --replicas=4

# Scale down — postgres-3 удалится, но PVC data-postgres-3 ОСТАНЕТСЯ (default policy)
kubectl scale sts/postgres --replicas=3

# Rollout
kubectl rollout status sts/postgres
kubectl rollout history sts/postgres
kubectl rollout undo sts/postgres

# Удаление StatefulSet — Pods удалятся, PVC ОСТАНУТСЯ
kubectl delete sts postgres

# Удалить и PVC
kubectl delete pvc -l app=postgres

Проверка знанийKnowledge check
Почему в StatefulSet при удалении объекта (kubectl delete sts) или scale down по умолчанию PVC сохраняются (не удаляются автоматически)? И в каких случаях это разумно изменить?
ОтветAnswer
Из соображений безопасности данных. StatefulSet используется для stateful workloads — БД, брокеров, кластерного storage. На PVC лежат критичные персистентные данные: таблицы PostgreSQL, Kafka log segments, etcd Raft journal. Если бы PVC удалялись каскадно при удалении StatefulSet, любая ошибка оператора (опечатка в команде, неправильный namespace) приводила бы к потере данных. Поэтому default behaviour — PVC остаются (Retain), нужно вручную их удалить. С v1.27 GA есть spec.persistentVolumeClaimRetentionPolicy с полями whenDeleted и whenScaled, позволяющая выбрать Delete. Менять на Delete разумно: (1) для test/staging environment, где данные эфемерные; (2) для эфемерных workloads вроде in-memory cache с persistent disk; (3) когда отдельный backup mechanism уже гарантирует сохранность данных и PVC может быть пересоздан с нуля.

Проверьте понимание

Результат: 0 из 0
Аналитический
Вопрос 1 из 5. Чем имена Pods в StatefulSet отличаются от Deployment, и почему это важно для stateful applications?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5