Що таке StatefulSet і коли його використовувати
На відміну від стандартного Deployment, де поди абсолютно взаємозамінні, безликі та мають випадкові суфікси в іменах (наприклад, web-78df6c-x9k2), у StatefulSet:
🟢 Junior Level
30-секундна відповідь
StatefulSet — це спеціалізований контролер робочого навантаження Kubernetes, призначений для керування застосунками, яким потрібне збереження стану (State), стабільна мережева ідентичність та персональні постійні диски.
На відміну від стандартного Deployment, де поди абсолютно взаємозамінні, безликі та мають випадкові суфікси в іменах (наприклад, web-78df6c-x9k2), у StatefulSet:
- Кожен под отримує строго передбачуване ім’я з порядковим номером (Ordinal):
db-0,db-1,db-2. - Кожен под отримує власний виділений диск: через шаблон
volumeClaimTemplatesстворюються персональні PVC (data-db-0,data-db-1). При перезапуску под на будь-якій ноді гарантовано підключає назад свій же диск. - Порядок запуску та зупинки строго детермінований: поди запускаються по черзі (0 $\to$ 1 $\to$ 2) і вимикаються у зворотному порядку (2 $\to$ 1 $\to$ 0).
Наочна аналогія з життя
- Deployment — це міські таксі: вам неважливий номер машини та водій. Якщо одне таксі зламалося, ви сідаєте в будь-яке інше — усі машини однакові та взаємозамінні.
- StatefulSet — це пасажирський потяг: вагони з’єднані в строгому порядку: Вагон №1 (Локомотив/Leader), Вагон №2, Вагон №3. Ви не можете викинути вагон №2 і поставити випадкову машину таксі. Якщо вагон №2 відчепили на техобслуговування, на його місце має повернутися саме відремонтований вагон №2 з усім багажем пасажирів.
Базовий маніфест StatefulSet з Headless Service
apiVersion: v1
kind: Service
metadata:
name: postgres-cluster
spec:
clusterIP: None # 1. ОБОВ'ЯЗКОВИЙ Headless Service!
selector:
app: postgres
ports:
- port: 5432
name: postgres
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
serviceName: postgres-cluster # Зв'язка з Headless Service
replicas: 3
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16
ports:
- containerPort: 5432
volumeMounts:
- name: pgdata
mountPath: /var/lib/postgresql/data
# 2. Шаблон генерації персональних дисків для кожного пода
volumeClaimTemplates:
- metadata:
name: pgdata
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 20Gi
🟡 Middle Level
4 стовпи архітектури StatefulSet
[ StatefulSet: postgres (replicas: 3) ]
│
┌────────────────────────────────┼────────────────────────────────┐
▼ ▼ ▼
[ Под: postgres-0 ] [ Под: postgres-1 ] [ Под: postgres-2 ]
DNS: postgres-0.cluster... DNS: postgres-1.cluster... DNS: postgres-2.cluster...
│ │ │
▼ ▼ ▼
[ PVC: pgdata-postgres-0 ] [ PVC: pgdata-postgres-1 ] [ PVC: pgdata-postgres-2 ]
(Диск тому PV №0) (Диск тому PV №1) (Диск тому PV №2)
- Передбачуваний мережевий DNS (Headless Service):
Завдяки
clusterIP: NoneCoreDNS створює персональний A-запис для кожної репліки:<pod-name>.<service-name>.<namespace>.svc.cluster.local
Наприклад:postgres-0.postgres-cluster.default.svc.cluster.local. Поди кластера завжди знають, де шукати лідера (postgres-0). - Персональне дискове сховище (
volumeClaimTemplates): Під час масштабування контролер автоматично запитує новийPersistentVolumeClaim. При видаленні пода диск НЕ видаляється, зберігаючи базу даних у цілісності. - Строгий порядок ініціалізації:
Под
postgres-1не почне створюватися, поки подpostgres-0не перейде в статусRunningтаReady. - Зворотний порядок видалення:
При зменшенні масштабу (
scale --replicas=1) спочатку коректно завершуєтьсяpostgres-2, і лише потімpostgres-1.
Порівняльна таблиця: StatefulSet vs Deployment
| Критерій | Deployment | StatefulSet |
| :— | :— | :— |
| Іменування подів | Випадковий хеш (app-79d8f-kx9q) | Строгий порядковий номер (app-0, app-1) |
| Мережева адресація | Єдиний віртуальний ClusterIP | Персональний DNS для кожного пода (Headless) |
| Дискові томи | Спільні або ефемерні | Персональний постійний PV на кожен под |
| Порядок запуску | Паралельний (швидкий) | Строго почерговий (0 $\to$ 1 $\to$ 2) |
| Типові навантаження | Stateless REST API, Spring Boot воркери | PostgreSQL, Kafka, Cassandra, ZooKeeper, Redis |
🔴 Senior Level
Головна ілюзія: чому «голий» StatefulSet не рятує бази даних
Senior-інженер повинен розуміти ключове обмеження:
StatefulSet керує подами та дисками, але він АБСОЛЮТНО НІЧОГО не знає про внутрішню реплікацію баз даних!
- Якщо запустити PostgreSQL у StatefulSet на 3 репліки, Kubernetes підніме три ізольованих екземпляри PostgreSQL, які нічого не знають один про одного.
- StatefulSet не вміє автоматично обирати нового майстра при падінні
postgres-0, не вміє налаштовуватиstreaming replication, не керує WAL-архівацією і не захищає від Split-Brain. - Продакшен-стандарт: бази даних у Kubernetes розгортають не через сирий StatefulSet, а за допомогою спеціалізованих Операторів (Operators):
- PostgreSQL: CloudNativePG, Zalando Postgres Operator (на базі Patroni).
- Apache Kafka: Strimzi Operator.
- Redis: Redis Operator.
Проблема зонального блокування дисків та WaitForFirstConsumer
У публічних хмарах (AWS, GCP, Azure) дискові томи (наприклад, AWS EBS) жорстко прив’язані до конкретної зони доступності (Availability Zone, наприклад us-east-1a).
Катастрофічний сценарій із volumeBindingMode: Immediate:
- Створюється PVC. При значенні
Immediateхмара миттєво виділяє EBS-диск у випадковій зоніus-east-1a. - Планувальник
kube-schedulerзапускає под, але призначає його на ноду в зоніus-east-1b(де більше вільної пам’яті). - Kubelet намагається примонтувати диск із сусідньої зони: виникає фатальна помилка
VolumeZoneMismatch / Multi-Attach error. Под назавжди зависає в статусіContainerCreating!
Архітектурне рішення:
У StorageClass обов’язково вказується режим volumeBindingMode: WaitForFirstConsumer:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ebs-gp3-sc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer # Створювати диск строго в тій зоні, куди шедулер посадив Под!
Канаркові оновлення через partition
У StatefulSet доступний унікальний механізм канаркового оновлення stateful-сервісів за допомогою параметра partition:
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # Оновити тільки поди з номером >= 2
Якщо у вас 3 репліки (0, 1, 2), то при зміні образу оновиться тільки под postgres-2. Поди 0 та 1 залишаться на старій версії! Ви можете провести навантажувальні тести на канарковому поді, після чого виставити partition: 0 для завершення розгортання кластера.
4 Tricky Questions
1. Чому для роботи StatefulSet строго обов’язковий Headless Service (clusterIP: None), і що зламається, якщо використовувати звичайний ClusterIP Service?
Відповідь:
- У стандартному сервісі
ClusterIPзапити балансуються черезiptables/IPVSвипадковим чином між усіма подами за єдиним віртуальним IP. - Проте кластерним stateful-застосункам (таким як Kafka, Cassandra або PostgreSQL) категорично потрібно звертатися до конкретного вузла безпосередньо (наприклад, запис повинен йти строго на майстра
db-0, а репліки повинні синхронізуватися зі строго визначеним брокером). - Headless Service відключає віртуальний IP (
clusterIP: None). Замість цього DNS-сервер кластера (CoreDNS) при запиті доdb-headlessповертає список персональних A-записів для кожного окремого пода (db-0.db-headless,db-1.db-headless). Без Headless Service вузли розподіленої системи не зможуть виявити топологію кластера та встановити мізвузлові P2P-з’єднання.
2. Чому при масштабуванні StatefulSet вниз (kubectl scale --replicas=0) або видаленні об’єкта (kubectl delete statefulset) Kubernetes навмисно НЕ видаляє створені PersistentVolumeClaims (PVC)?
Відповідь: Це фундаментальний захисний механізм безпеки Kubernetes для запобігання незворотній втраті даних:
- Якби Kubernetes автоматично видаляв PVC при випадковому зменшенні масштабу або перестворенні StatefulSet, будь-яка помилка інженера в CI/CD пайплайні призводила б до миттєвого знищення терабайтів бойових баз даних.
- Томи PVC оголошуються «осиротілими» (orphaned), але продовжують надійно зберігати дані.
- При подальшому збільшенні масштабу знову створений под
db-0знайде наявний PVC з точно співпадаючим ім’ямdata-db-0і автоматично підмонтує колишній диск з усією історією даних. Якщо видалення дисків дійсно необхідне, адміністратор зобов’язаний видалити їх окремою усвідомленою командоюkubectl delete pvc -l app=db.
3. Як поведеться StatefulSet при падінні фізичної ноди з працюючим подом, і чому Kubernetes не перестворить под на іншій ноді автоматично без втручання оператора?
Відповідь: Якщо фізична нода припиняє відповідати (Node NotReady), контролер StatefulSet НЕ перестворює под на іншій ноді автоматично.
- Причина: захист від розділення пам’яті та пошкодження даних (Split-Brain / Data Corruption). Kubernetes не може достовірно знати: чи впав сервер фізично, чи просто втратив мережевий лінк. Якщо контролер запустить дублікат пода
db-0на іншій ноді, обидва інстанси можуть спробувати писати на один і той же мережевий диск, що гарантовано зруйнує файлову систему бази даних. - Под залишиться в статусі
TerminatingабоUnknown. - Для перенесення пода на іншу ноду потрібне або відновлення зв’язку, або примусове підтвердження оператора/контролера відмовостійкості (STONITH / Node Fencing, або
kubectl delete pod db-0 --force --grace-period=0).
4. У яких випадках має сенс змінити політику podManagementPolicy зі стандартного OrderedReady на Parallel?
Відповідь:
За замовчуванням OrderedReady змушує поди підійматися строго по одному. Якщо запуск важкого Java-контейнера займає 2 хвилини, запуск 10 реплік триватиме 20 хвилин.
Політика podManagementPolicy: Parallel відключає серіалізацію: усі поди створюються і масштабуються одночасно й паралельно, зберігаючи при цьому всі інші переваги StatefulSet (унікальні стабільні імена node-0, node-1, персональні PVC та персональний DNS).
Це застосовується для розподілених систем, протоколи яких спочатку розраховані на паралельний старт вузлів (наприклад, data-ноди Elasticsearch, кешуючий кластер Redis Cluster або розподілене сховище ScyllaDB / Cassandra).
🎯 Шпаргалка для інтерв’ю
Головне про StatefulSet
- Суть: контролер для stateful-застосунків, що вимагають збереження стану, стабільного мережевого імені та унікальних дисків.
- Передбачувані імена:
pod-0,pod-1,pod-2(зберігаються при будь-яких перезапусках). - Headless Service:
clusterIP: Noneобов’язковий для створення прямих DNS-записів до кожного вузла. volumeClaimTemplates: автоматично генерує персональний PVC для кожного пода; PVC ніколи не видаляються автоматично при scale down.- Порядок: за замовчуванням
OrderedReady(запуск 0 $\to$ 1 $\to$ 2, зупинка 2 $\to$ 1 $\to$ 0). - Production Standard: сирий StatefulSet недостатній для бойових СУБД; для автоматичного failover та бекапів використовуються Kubernetes Operators (CloudNativePG, Zalando, Strimzi).
- Зональність сховищ: у StorageClass обов’язковий параметр
volumeBindingMode: WaitForFirstConsumer.
StatefulSet vs Deployment
| Параметр | Deployment | StatefulSet |
| :— | :— | :— |
| Ідентичність | Взаємозамінні поди (Cattle) | Унікальні іменовані поди (Pets) |
| Сховище | Спільний том або без диска | Персональний PVC на кожен под |
| Мережевий доступ| Єдиний балансувальник Service | Прямий DNS до кожного модуля (Headless) |
| Порядок старту| Паралельний | Почерговий (OrderedReady) |
Червоні прапорці на співбесіді (Чого говорити не можна)
- ❌ «Ми розгорнули PostgreSQL у StatefulSet на 3 репліки, і він сам автоматично налаштував master-slave реплікацію» — StatefulSet не знає логіку БД, для цього потрібен Operator або Patroni.
- ❌ «StatefulSet підходить для звичайних stateless мікросервісів на Spring Boot» — антипатерн, що ускладнює кластер та уповільнює деплой.
- ❌ «При видаленні StatefulSet усі бази даних та PVC автоматично стираються» — диски PVC залишаються недоторканими.