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.
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 на каждый Podspec.replicas,spec.selector,spec.template— как в Deploymentspec.podManagementPolicy—OrderedReady(default) илиParallelspec.updateStrategy—RollingUpdate(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-имя стабильное.
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 ОСТАЁТСЯ — это сделано специально для безопасности данных.
Когда вы удаляете 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 при удалении всего StatefulSetwhenScaled: 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: когда что
Правило простое: если у 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