Питання 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), але немає міграції між нодами. 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 — детекція проблем з 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: дешевше сховище
  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