Вопрос 23 · Раздел 14

Что такое StatefulSet и когда его использовать?

StatefulSet гарантирует четыре ключевых свойства:

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Простое определение

StatefulSet — это контроллер в Kubernetes для управления приложениями, которым важно сохранять свою идентичность и данные между перезапусками. В отличие от Deployment (где все Pod’ы одинаковые и взаимозаменяемые), каждый Pod в StatefulSet имеет уникальное имя, постоянный DNS и свой собственный диск.

StatefulSet – как Deployment, но каждый Pod получает стабильное имя (web-0, web-1, web-2) и стабильное хранилище (свой PersistentVolume). Pod’ы создаются и удаляются по порядку.

Аналогия

Deployment — это как такси: любая машина может подвезти вас, все взаимозаменяемы.
StatefulSet — это как поезд: каждый вагон имеет свой номер, стоит на определённом месте, и если вагон №3 убрать, поезд не может просто поставить любой другой на его место — нужен именно вагон №3.

Пример YAML

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: db
          image: postgres:15
          ports:
            - name: postgres
              containerPort: 5432
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 10Gi
---
# Headless Service для StatefulSet
apiVersion: v1
kind: Service
metadata:
  name: database-headless
spec:
  clusterIP: None  # Headless!
  selector:
    app: database
  ports:
    - port: 5432

Headless Service (clusterIP: None) – K8s не создаёт виртуальный IP. Вместо этого DNS-запрос возвращает IP каждого Pod напрямую. Нужно для StatefulSet – каждый Pod должен быть доступен по своему имени.

Пример kubectl

# Посмотреть StatefulSet
kubectl get statefulset database

# Посмотреть Pod'ы с их уникальными именами
kubectl get pods -l app=database
# database-0  Running
# database-1  Running
# database-2  Running

# DNS к конкретному Pod'у
# database-0.database-headless.default.svc.cluster.local

# Масштабировать (порядково!)
kubectl scale statefulset database --replicas=5

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

  • Базы данных: PostgreSQL, MySQL, MongoDB
  • Распределённые системы: Kafka, Zookeeper, Cassandra, Elasticsearch
  • Любые приложения, где каждый экземпляр имеет уникальную роль (master/replica)
  • Когда данные должны быть привязаны к конкретному Pod’у

StatefulSet нужен когда: (1) важна идентичность Pod (web-0 ≠ web-1), (2) нужно стабильное хранилище, (3) важен порядок запуска (web-0 → web-1 → web-2).


🟡 Middle Level

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

StatefulSet гарантирует четыре ключевых свойства:

  1. Стабильный сетевой идентификатор: Pod’ы именуются <name>-<ordinal> (database-0, database-1, database-2). Каждый Pod получает DNS-имя: database-0.database-headless.default.svc.cluster.local. При перезапуске Pod сохраняет своё имя.

  2. Стабильное хранилище: Через volumeClaimTemplates Kubernetes создаёт отдельный PVC для каждого Pod’а (data-database-0, data-database-1). PVC не удаляется при удалении Pod’а или StatefulSet. При пересоздании Pod монтирует тот же PVC.

  3. Порядковое развёртывание: Pod’ы создаются строго по порядку (0 → 1 → 2) и удаляются в обратном (2 → 1 → 0). Pod N не начнёт создаваться, пока Pod N-1 не станет Running и Ready.

  4. Headless Service: StatefulSet требует Service с clusterIP: None. Этот Service не создаёт виртуальный IP, а позволяет DNS резольвить каждый Pod индивидуально.

Практические сценарии

Сценарий 1: PostgreSQL Master-Replica кластер

database-0 → Master (read/write)
database-1 → Replica (read-only)
database-2 → Replica (read-only)

Каждый Pod знает свою роль через ordinal. Init-контейнер определяет: если ordinal=0, стать master; если >0, подключиться к master как replica.

Сценарий 2: Kafka кластер

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kafka
spec:
  serviceName: kafka-headless
  replicas: 3
  template:
    spec:
      containers:
        - name: kafka
          image: confluentinc/cp-kafka:7.5
          env:
            - name: KAFKA_BROKER_ID
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            # broker.id = pod ordinal

Каждый Kafka broker имеет уникальный broker.id, равный ordinal. Это критично для Kafka internals.

Сценарий 3: Параллельное масштабирование

spec:
  podManagementPolicy: Parallel  # вместо OrderedReady (по умолчанию)

Для приложений, которым не нужен строгий порядок (например, Elasticsearch data nodes), Parallel ускоряет масштабирование.

Таблица распространённых ошибок

Ошибка Последствие Решение
Забыт Headless Service DNS не резольвит индивидуальные Pod’ы, StatefulSet не работает корректно Всегда создавать Service с clusterIP: None
PVC не удаляются при kubectl delete statefulset “Осиротевшие” PVC занимают хранилище Удалить PVC вручную: kubectl delete pvc -l app=database
Использование :latest тега Rolling Update не работает, Pod’ы после пересоздания могут получить другой образ Использовать конкретные теги
Ожидание, что StatefulSet сам настроит кластер StatefulSet только гарантирует идентичность, не настраивает БД/кластер Использовать Init-контейнеры или Operators
StorageClass без динамического provisioning PVC зависают в Pending, Pod’ы не стартуют Настроить StorageClass с provisioner
StatefulSet для stateless приложения Излишняя сложность, порядковый запуск замедляет деплой Использовать Deployment

Сравнительная таблица: StatefulSet vs Deployment vs DaemonSet

Характеристика StatefulSet Deployment DaemonSet
Идентичность Pod’ов Уникальная (имя, DNS, PVC) Все одинаковые Все одинаковые
Порядок создания Строгий (0→1→2) Параллельный Параллельный
Хранилище Стабильный PVC на Pod Общий PVC или ephemeral HostPath или local volume
DNS Индивидуальный (pod-N.service) Один Service IP Один Service IP
Масштабирование Порядковое (медленное) Параллельное (быстрое) По одной реплике на ноду
Когда использовать БД, очереди, кластеры Stateless API, веб-приложения Мониторинг, лог-агенты
Rolling Update Обратный порядок (N→0), с waitForFirstConsumer Параллельный По нодам

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

  • Stateless-приложения — используйте Deployment. StatefulSet добавляет сложность без пользы
  • Когда все Pod’ы одинаковые — если не важна идентичность, Deployment проще и быстрее
  • Нужно быстрое масштабирование — порядковый запуск StatefulSet медленный. Для 100 реплик это займёт 100 × startup_time
  • Хранение состояния во внешней БД — если приложение stateless, а состояние в PostgreSQL, используйте Deployment

🔴 Senior Level

Глубинная механика: StatefulSet Controller, PVC, и Reconciliation

StatefulSet Controller Architecture: StatefulSet Controller (в kube-controller-manager) работает через reconciliation loop, но значительно сложнее Deployment Controller:

  1. Watch: Подписывается на StatefulSet, Pod, PVC события
  2. Ordinal Management: Поддерживает набор ordinal’ов (0..N-1). Каждый ordinal = Pod + PVC
  3. Ordered Creation:
    Для i = 0 до replicas-1:
      Если Pod-<i> не существует → создать
      Если Pod-<i> не Ready → ждать
      Если Pod-<i> Ready → перейти к i+1
    
  4. PVC Binding: Для каждого Pod создаётся PVC из volumeClaimTemplates. PVC именуется <volumeClaimTemplate-name>-<statefulset-name>-<ordinal>. PVC создаётся до Pod’а, чтобы kubelet мог смонтировать volume перед запуском контейнера.

  5. Pod Identity: При пересоздании Pod (на другой ноде) контроллер находит существующий PVC по label selector и присоединяет его к новому Pod’у. PVC никогда не удаляется автоматически.

Headless Service DNS: Headless Service (clusterIP: None) не создаёт iptables/IPVS правил. Вместо этого CoreDNS создаёт DNS записи для каждого Pod:

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

DNS обновляется при изменении Pod IP (через EndpointSlice).

PersistentVolume Binding: PV привязывается к PVC через claimRef. При удалении Pod’а PVC сохраняется. При удалении StatefulSet PVC сохраняется (orphaned). PV reclaimPolicy (Retain/Delete/Recycle) определяет судьбу данных.

Update Strategy:

spec:
  updateStrategy:
    type: RollingUpdate  # или OnDelete
    rollingUpdate:
      partition: 0  # только Pod'ы с ordinal >= partition обновляются
  • RollingUpdate: Обновляет Pod’ы в обратном порядке (N→0), по одному
  • OnDelete: Обновляет Pod только после его ручного удаления
  • partition: Позволяет обновить только часть Pod’ов (для canary в StatefulSet)

Trade-offs

Аспект Trade-off
OrderedReady vs Parallel OrderedReady = безопаснее для кластеров с leader election. Parallel = быстрее для data nodes
Retain vs Delete PVC Retain = данные безопасны, но нужно ручное управление. Delete = автоматически, но риск потери данных
StatefulSet vs Operator StatefulSet = базовая идентичность + storage. Operator = полная автоматизация (backup, failover, scaling). Operator сложнее, но мощнее
Replica count Малое (3) = меньше ресурсов, но менее отказоустойчиво. Большое (7+) = больше replica lag, медленнее writes
StorageClass local vs network Local SSD = быстрее (IOPS), но нет migration между нодами. Network (EBS, Ceph) = portable, но latency выше

Edge Cases (7+)

Edge Case 1: Pod переезжает на другую ноду, PVC не следует PVC привязан к зоне доступности (availability zone). Если Pod переезжает в другую зону, PVC не может быть примонтирован (EBS/Ceph zone-locked). Pod застревает в ContainerCreating. Решение: использовать WaitForFirstConsumer volumeBindingMode в StorageClass — PVC создаётся в зоне, где запланирован Pod.

Edge Case 2: Split-brain при network partition StatefulSet с 3 репликами PostgreSQL. Network partition: database-0 (master) на одной стороне, database-1 и database-2 на другой. database-1 и database-2 выбирают нового master. Теперь два master’а записывают данные. При восстановлении сети — data corruption. Решение: Patroni или другой HA framework с consensus (etcd/Zookeeper).

Edge Case 3: StatefulSet Rolling Update с несовместимыми версиями Обновление Kafka с версии 3.0 → 3.5. StatefulSet обновляет Pod’ы в обратном порядке: broker-2, broker-1, broker-0. Во время обновления кластер имеет mix версий. Если протокол репликации несовместим, data loss или cluster failure. Решение: Operator с версионированием и pre-flight checks.

Edge Case 4: Orphaned PVC после kubectl delete statefulset StatefulSet удалён, но 3 PVC (по 10Gi каждый) остались. Они занимают хранилище, но не используются. Через неделю StorageClass quota exceeded. Решение: автоматизировать cleanup через finalizer или CronJob.

Edge Case 5: Readiness Probe + StatefulSet ordinal awareness Все Pod’ы имеют одинаковую Readiness Probe. Но database-0 (master) и database-1 (replica) имеют разные требования к готовности. Replica может быть “готова” только после полной синхронизации с master (что занимает минуты). Стандартная readinessProbe не учитывает это. Решение: custom readiness script, проверяющий replication lag.

Edge Case 6: HPA с StatefulSet HPA не работает с StatefulSet напрямую — StatefulSet не поддерживает scale subresource для HPA в том же виде, что Deployment. Начиная с K8s 1.23, StatefulSet поддерживает scale subresource, но HPA scaling не учитывает порядковые ограничения. Решение: KEDA (Kubernetes Event-driven Autoscaling) с StatefulSet scaler.

Edge Case 7: Pod ordinal reuse после удаления Удалить database-2. StatefulSet с replicas=3 создаёт новый database-2. Новый Pod получает тот же ordinal и тот же PVC (data-database-2). Но если PVC был удалён вручную, новый PVC создаётся пустым. Pod стартует без данных, и кластер думает, что у него есть реплика с данными. Решение: никогда не удалять PVC без полной re-sync кластера.

Edge Case 8: StatefulSet с podManagementPolicy: Parallel и failure При параллельном создании все 3 Pod’ы стартуют одновременно. Если database-0 (master) ещё не готов, database-1 и database-2 не могут подключиться к master. Они входят в crash loop. Решение: OrderedReady (по умолчанию) для кластеров с leader-follower архитектурой.

Performance Numbers

Метрика Значение
Pod creation latency (ordered) N × (scheduling + container startup + readiness)
StatefulSet 3 replicas startup 2-5 минут (зависит от container startup)
StatefulSet 10 replicas startup (ordered) 5-15 минут
StatefulSet 10 replicas startup (parallel) 1-3 минуты
PVC creation + binding 5-30 секунд (зависит от provisioner)
DNS update latency (CoreDNS) 1-5 секунд
Rolling Update (reverse order) N × startup_time (обновляет по одному)
Local SSD IOPS 100K-1M IOPS, <1ms latency
Network storage (EBS) IOPS 3K-64K IOPS, 1-10ms latency

Security

  • PVC данные не шифруются по умолчанию — используйте StorageClass с encryption (AWS EBS encryption, Ceph encryption)
  • StatefulSet Pod’ы имеют стабильные IP — это упрощает атаку на конкретный Pod. NetworkPolicy должна ограничивать доступ к каждому ordinal
  • Headless Service DNS enumeration — злоумышленник может enumerate все Pod’ы через DNS: pod-0.service, pod-1.service, … Не раскрывайте чувствительные сервисы через headless Service
  • PVC access modesReadWriteOnce позволяет монтировать только на одну ноду. ReadWriteMany — на несколько, что увеличивает поверхность атаки
  • Pod Security Admission — StatefulSet Pod’ы часто требуют привилегий (например, для БД). Используйте baseline или privileged только если необходимо
  • Secrets для БД credentials — храните в Secrets, монтируйте как volumes (не env vars). Rotating secrets требует Rolling Update StatefulSet

Production War Story

Ситуация: Финтех-компания, PostgreSQL кластер на StatefulSet (3 реплики), EBS storage, AWS. StatefulSet настроен с volumeClaimTemplates, StorageClass с volumeBindingMode: Immediate.

Инцидент:

  1. Нода с database-0 (master) упала (hardware failure)
  2. Kubernetes пересоздал database-0 на новой ноде
  3. EBS volume был в зоне us-east-1a. Новая нода была в us-east-1b
  4. PVC не мог примонтироваться: Multi-Attach error — EBS zone-locked
  5. database-0 застрял в ContainerCreating на 2 часа
  6. database-1 и database-2 (replicas) продолжали работать в read-only режиме
  7. Все writes упали — master недоступен
  8. Ручное вмешательство: snapshot EBS в us-east-1a → restore в us-east-1b → attach к PVC → restart Pod
  9. Downtime: 2.5 часа, потеря ~$200K транзакций

Post-mortem и fix:

  1. StorageClass с volumeBindingMode: WaitForFirstConsumer — PVC создаётся в зоне, где запланирован Pod, а не заранее
  2. Pod Topology Spread Constraints — раскидать Pod’ы по зонам: ```yaml topologySpreadConstraints:
    • maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule ```
  3. Patroni для HA — автоматический failover: если master упал, replica promoted to master
  4. EBS Multi-AZ storage — использовать AWS EBS с cross-zone replication (дороже, но надёжнее)
  5. Automated backup — ежедневный snapshot + point-in-time recovery через WAL-G
  6. Alert на Pod pending > 5 минут — сработал бы сразу при zone mismatch

Мониторинг после fix:

# Alert: Pod в ContainerCreating > 5 минут
sum(kube_pod_status_phase{phase="Pending"}) by (statefulset) > 0

# Alert: PVC не примонтирован
sum(kube_persistentvolumeclaim_status_phase{phase!="Bound"}) > 0

# Alert: PostgreSQL replication lag
pg_replication_lag_seconds > 30

# Alert: Pod cross-zone reschedule
kube_pod_info{namespace="database"} unless on(node) kube_node_info

Monitoring (Prometheus/Grafana)

Ключевые метрики:

# StatefulSet replicas status
kube_statefulset_status_replicas
kube_statefulset_status_replicas_ready
kube_statefulset_status_replicas_current
kube_statefulset_status_replicas_updated

# Pod status по ordinal'ам
kube_pod_status_phase{statefulset="database"}

# PVC status
kube_persistentvolumeclaim_status_phase

# PVC storage usage
kubelet_volume_stats_used_bytes / kubelet_volume_stats_capacity_bytes

# PostgreSQL replication lag (через postgres_exporter)
pg_replication_lag_seconds

# Kafka broker status (через kafka_exporter)
kafka_brokers

# DNS resolution latency для headless service
coredns_dns_request_duration_seconds{service="database-headless"}

Grafana Dashboard панели:

  1. StatefulSet replicas: current vs ready vs desired — детекция mismatch
  2. Pod status по ordinal’ам — heatmap (0, 1, 2)
  3. PVC storage usage % — alert при > 80%
  4. Replication lag — critical для HA кластеров
  5. Pod restart rate по ordinal’ам — детекция crash loop конкретного Pod’а
  6. DNS resolution latency — detektsiya problem s headless service

Highload Best Practices

  1. Используйте Operator, а не raw StatefulSet — Zalando Postgres Operator, Strimzi Kafka Operator. Они автоматизируют backup, failover, scaling, version upgrades
  2. volumeBindingMode: WaitForFirstConsumer — PVC создаётся в зоне Pod’а, не zone-locked заранее
  3. Pod Topology Spread Constraints — раскидать Pod’ы по зонам и нодам для fault tolerance
  4. Разделяйте master и replica на разные StorageClass — master:高性能 SSD, replica: cheaper storage
  5. Automated backup — ежедневный snapshot + continuous WAL archiving (WAL-G, barman)
  6. Monitor replication lag — alert при lag > 30 секунд
  7. PodDisruptionBudgetminAvailable: 2 для 3-replica кластера, чтобы не потерять quorum
  8. Readiness Probe с replication awareness — replica готова только после sync с master
  9. podManagementPolicy: Parallel только для stateless data nodes — для master-replica кластеров всегда OrderedReady
  10. Не используйте StatefulSet для production БД без Operator — StatefulSet даёт идентичность, но не автоматизирует failover, backup, или recovery
  11. Storage IOPS monitoring — alert при volume IOPS approaching limit
  12. Regular failover testing — Chaos Engineering: kill master Pod, verify automatic failover

🎯 Шпаргалка для интервью

Обязательно знать:

  • StatefulSet — контроллер для stateful приложений: стабильные имена (pod-0, pod-1), DNS, PVC
  • Pod’ы создаются по порядку (0→1→2), удаляются обратно (2→1→0)
  • Headless Service (clusterIP: None) обязателен — DNS резольвит каждый Pod индивидуально
  • volumeClaimTemplates — отдельный PVC на каждый Pod; PVC не удаляется при удалении StatefulSet
  • Для production БД используйте Operator (Zalando Postgres, Strimzi Kafka), не raw StatefulSet
  • WaitForFirstConsumer в StorageClass — PVC создаётся в зоне Pod’а, не zone-locked
  • Rolling Update в обратном порядке (N→0); partition для canary-обновления

Частые уточняющие вопросы:

  • «StatefulSet vs Deployment?» — Deployment: все Pod’ы одинаковые; StatefulSet: уникальная идентичность + stable storage
  • «PVC удаляется при kubectl delete statefulset — Нет, PVC осиротевают; нужно удалять вручную
  • «HPA работает с StatefulSet?» — Ограниченно (scale subresource с K8s 1.23); лучше KEDA
  • «Split-brain в StatefulSet?» — При network partition возможен dual master; нужен Patroni/consensus

Красные флаги (НЕ говорить):

  • «StatefulSet для stateless приложений» (избыточно, используйте Deployment)
  • «StatefulSet сам настраивает БД кластер» (только идентичность; нужна настройка через Init/Operator)
  • «PVC удаляется автоматически» (orphaned PVC — частая проблема)
  • «StatefulSet = быстрое масштабирование» (порядковый запуск медленный)

Связанные темы:

  • [[Что такое Pod в Kubernetes]] — единица запуска
  • [[Как организовать rolling update в Kubernetes]] — обновление Pod’ов
  • [[Как происходит масштабирование в Kubernetes]] — KEDA для StatefulSet