Что такое 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 остаются нетронутыми.