StatefulSet: приложения с состоянием
StatefulSet запускает поды с устойчивой идентичностью и собственным хранилищем — это контроллер для баз данных и кластеров, а не для безликих веб-реплик.
StatefulSet — контроллер для приложений с состоянием: он даёт каждому поду стабильное имя, предсказуемый порядок запуска и собственный том, который переживает пересоздание пода.
В разделе про базовые контроллеры вы уже видели Deployment: он отлично гоняет stateless-приложения, где реплики взаимозаменяемы. Поды Deployment получают случайные имена вроде web-7d9f8c-abcde, стартуют и гибнут в любом порядке, и любой из них может ответить на запрос. Но как только приложению нужна идентичность — собственные данные на диске, фиксированный адрес, роль «первичного» узла — модель Deployment ломается. Поднимать так PostgreSQL, MySQL, MongoDB, Kafka, Zookeeper, Elasticsearch нельзя: реплики такого кластера не равноправны.
Для этого и существует StatefulSet. Он сохраняет идею декларативного контроллера (вы описываете желаемое, Kubernetes приводит кластер к нему), но добавляет три гарантии: устойчивые сетевые имена, упорядоченное развёртывание и привязку постоянного тома к конкретному поду.
Зачем это на практике
Представьте кластер PostgreSQL из трёх узлов: один первичный (принимает запись) и две реплики (только чтение). Здесь нельзя «просто перезапустить любой под»: у каждого свои данные, а реплики должны знать постоянный адрес первичного, чтобы тянуть журнал. StatefulSet решает три задачи разом:
- Стабильная идентичность. Поды называются предсказуемо:
postgres-0,postgres-1,postgres-2. Имя сохраняется при пересоздании: упавшийpostgres-1вернётся именно какpostgres-1, а не получит случайный суффикс. - Привязка данных. Каждый под получает собственный
PersistentVolumeClaim. Томdata-postgres-1навсегда закреплён заpostgres-1— пересоздание пода не теряет данные и не путает их с чужими. - Порядок. Поды поднимаются по очереди: сначала
postgres-0станет первичным, потом подключаются реплики. Удаление идёт в обратном порядке.
Headless service и стабильные DNS-имена
Чтобы у каждого пода был постоянный адрес, StatefulSet работает в паре с headless-сервисом — обычным Service с полем clusterIP: None. Такой сервис не выдаёт единый виртуальный IP и не балансирует нагрузку; вместо этого он публикует DNS-запись на каждый под отдельно.
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
clusterIP: None # headless: без единого виртуального IP
selector:
app: postgres
ports:
- port: 5432
В результате внутри кластера резолвятся стабильные имена вида postgres-0.postgres.default.svc.cluster.local. Реплика всегда найдёт первичный по адресу postgres-0.postgres, даже если под пересоздавался. Имя сервиса указывается в поле serviceName самого StatefulSet — это обязательная связка.
Манифест StatefulSet
Главное отличие от Deployment в манифесте — секция volumeClaimTemplates: это не один общий том, а шаблон, по которому Kubernetes создаёт отдельный PVC для каждого пода.
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
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates: # по PVC на каждый под
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
Применяем и наблюдаем за упорядоченным запуском:
kubectl apply -f postgres-statefulset.yaml
# Поды появляются строго по очереди: 0, затем 1, затем 2
kubectl get pods -l app=postgres -w
# Имена PVC привязаны к индексам подов
kubectl get pvc
# data-postgres-0 Bound ...
# data-postgres-1 Bound ...
# data-postgres-2 Bound ...
Как это работает под капотом
Контроллер StatefulSet в kube-controller-manager ведёт поды по индексам, а не как безымянное множество. При создании он идёт от 0 к N-1 и не запускает следующий под, пока предыдущий не станет Ready (по умолчанию политика OrderedReady). При удалении — наоборот, от старшего к младшему. Это и даёт упорядоченность: реплика не стартует раньше, чем первичный готов её обслужить.
Тома живут отдельной жизнью. PVC, созданные из volumeClaimTemplates, не удаляются вместе со StatefulSet — это защита от случайной потери данных. Когда под пересоздаётся, планировщик находит существующий PVC с тем же индексом и монтирует его обратно. Именно поэтому postgres-1, упав, возвращается со своими прежними данными.
Масштабирование тоже упорядочено. kubectl scale statefulset postgres --replicas=5 добавит postgres-3 и postgres-4 по очереди; уменьшение до трёх удалит postgres-4, затем postgres-3, но их PVC останутся, чтобы при обратном росте данные вернулись.
Частые ошибки
- Нет headless-сервиса или не совпадает
serviceName. Без него стабильные DNS-имена подов не появятся, и кластер не соберётся. Сервис должен существовать и его имя — точно совпадать сspec.serviceName. - Ожидание, что StatefulSet удалит тома. После
kubectl delete statefulsetPVC остаются. Это не баг: удаляйте их вручную, когда данные точно не нужны, иначе хранилище будет копиться. - StatefulSet там, где хватило бы Deployment. Для stateless-приложений он избыточен: упорядоченный запуск замедляет выкатку, а PVC не нужны. Правило простое: нет собственных данных и роли узла — берите Deployment.
- Расчёт на общий том для всех реплик.
volumeClaimTemplatesдаёт отдельный диск каждому поду (режимReadWriteOnce). Это не общая папка — у каждого узла своя копия данных, как и должно быть в кластере БД.
Итоги
StatefulSet— контроллер для приложений с состоянием: БД, очередей, кластеров.- Даёт стабильные имена подов (
name-0,name-1, …), сохраняющиеся при пересоздании. - Запуск и удаление идут упорядоченно по индексам; следующий под ждёт готовности предыдущего.
volumeClaimTemplatesсоздаёт по постоянному тому на под, и том навсегда привязан к своему индексу.- Требует headless-сервиса (
clusterIP: None) для адресации отдельных подов через DNS.