Чем StatefulSet отличается от Deployment:
Три гарантии. Первая: стабильные имена. Поды называются redis-0, redis-1, redis-2 и сохраняют имя при пересоздании. Вторая: порядок. По умолчанию поды стартуют по очереди, следующий не запустится, пока предыдущий не пройдет readiness-пробу (глава 10), гасятся в обратном порядке. Третья: личный диск. volumeClaimTemplates создает отдельный PVC (глава 8) на каждый под, и после пересоздания под получает свой же диск с данными.
Для стабильных DNS-имен StatefulSet нужен headless Service: обычный Service из главы 5, но с clusterIP: None. Он не балансирует, а отдает DNS-запись на каждый под отдельно. Его имя указывается в поле serviceName.
Код: Выделить всё
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: shop
spec:
clusterIP: None
selector:
app: redis
ports:
- port: 6379
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
namespace: shop
spec:
serviceName: redis
replicas: 3
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7.4-alpine
ports:
- containerPort: 6379
readinessProbe:
exec:
command: ["redis-cli", "ping"]
periodSeconds: 5
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
memory: 512Mi
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5GiКод: Выделить всё
kubectl get pods -n shop -l app=redis
# NAME READY STATUS AGE
# redis-0 1/1 Running 2m10s
# redis-1 1/1 Running 95s
# redis-2 1/1 Running 70s
kubectl get pvc -n shop
# NAME STATUS CAPACITY AGE
# data-redis-0 Bound 5Gi 2m10s
# data-redis-1 Bound 5Gi 95s
# data-redis-2 Bound 5Gi 70sПри уменьшении replicas StatefulSet удаляет поды с конца (сначала redis-2), а их PVC оставляет. Вернете replicas обратно, под поднимется со старыми данными. С версии 1.32 поведением можно управлять штатно:
Код: Выделить всё
spec:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Delete
whenScaled: RetainЧестное предупреждение: StatefulSet не делает из трех redis-подов кластер. Репликацию, выборы мастера и failover настраивает само приложение. В проде для Postgres и Kafka обычно берут операторы (CloudNativePG, Strimzi) с логикой конкретной СУБД, но строятся они на тех же примитивах, так что понимать StatefulSet все равно придется.
DaemonSet: один под на каждой ноде:
Типовые жильцы: сборщики логов (fluent-bit, vector), node-exporter для Prometheus, CNI-плагины и CSI-драйверы. Поля replicas тут нет вообще, количество диктует кластер: добавили ноду, под на ней появился сам, нода ушла, под исчез вместе с ней.
Код: Выделить всё
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
namespace: monitoring
spec:
selector:
matchLabels:
app: node-exporter
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 10%
template:
metadata:
labels:
app: node-exporter
spec:
hostNetwork: true
hostPID: true
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: node-exporter
image: quay.io/prometheus/node-exporter:v1.9.1
args: ["--path.rootfs=/host"]
ports:
- containerPort: 9100
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 128Mi
volumeMounts:
- name: rootfs
mountPath: /host
readOnly: true
volumes:
- name: rootfs
hostPath:
path: /Обновления StatefulSet:
RollingUpdate идет от последнего пода к нулевому, по одному, с ожиданием Ready. Поле partition дает канарейку: при partition: 2 обновятся только поды с порядковым номером 2 и выше, остальные сидят на старой ревизии, пока не опустите partition до нуля.
Типичные грабли:
serviceName не совпадает с именем headless Service, или сервис вообще забыли создать. Поды работают, а DNS-имен нет.
volumeClaimTemplates неизменяемый. Увеличить диск правкой StatefulSet нельзя, kubectl apply вернет ошибку. Правят сами PVC (если StorageClass умеет allowVolumeExpansion), а потом пересоздают объект, не трогая поды: kubectl delete sts redis --cascade=orphan и apply манифеста с новым размером.
Под не прошел readiness после обновления, и выкатка молча стоит, остальные ждут. kubectl rollout status это покажет, не выкатывайте StatefulSet вслепую.
Нода умерла, под на ней завис в Terminating, новый не создается. Это не баг. StatefulSet гарантирует, что под с данным именем существует максимум в одном экземпляре, иначе два пода могли бы писать в один диск. Штатный выход: удалить объект ноды или поставить на нее taint node.kubernetes.io/out-of-service. Сносить под с --force можно, только когда точно уверены, что нода мертва.
Уменьшили replicas у базы, не выведя реплику из кластера средствами самой СУБД, и сломали кворум. Сначала команды СУБД, потом kubectl scale.
DaemonSet без requests. Агент стоит на каждой ноде, и утечка памяти в нем умножается на размер кластера. Requests и limits из главы 9 тут обязательны.
Что усвоили:
StatefulSet дает стабильные имена, порядок запуска и личные PVC, но кластеризацию оставляет приложению. DaemonSet держит ровно по одному поду на ноде и сам реагирует на изменения состава кластера. В следующей главе займемся тем, что здесь мелькало краем: стратегии обновления и rollback, graceful shutdown, nodeSelector, affinity и taints.