深色模式
StatefulSet 有状态应用编排
摘要:本文面向需要编排 etcd、ZooKeeper、数据库等有状态服务的 SRE,讲解 StatefulSet 的稳定标识、持久卷、有序性与更新策略。覆盖 Kubernetes v1.28+。
适用版本与前提
- Kubernetes:v1.28+(start ordinal 自 v1.31 稳定;
pod-index-label自 v1.32 稳定;minReadySeconds自 v1.25 稳定) - 工具:
kubectl - 前提:理解 Pod、Headless Service、PVC/StorageClass
背景与问题
无状态服务可以“随便换一个 Pod 顶上”,但 etcd、Kafka、MySQL 这类服务不能。它们需要:固定的网络身份( pod-0 永远是 pod-0)、与身份绑定的持久存储、以及有序的启停(先起 0 再起 1,缩容先停大的)。Deployment 给不了这些保证——它的 Pod 是可互换的、名字带随机哈希。StatefulSet 正是为此设计。
三大保证
- 稳定、唯一的网络标识:Pod 主机名形如
$(sts名)-$(序号),如web-0、web-1。配合 Headless Service,获得稳定 DNS(web-0.nginx.default.svc.cluster.local)。 - 稳定、持久的存储:
volumeClaimTemplates为每个 Pod 创建独立 PVC,Pod 重调度后仍挂载同一块 PV。 - 有序的部署/扩缩/更新:序号 0→N-1 顺序启动,N-1→0 顺序终止。
注意
StatefulSet 要求你自建一个 Headless Service(clusterIP: None)来负责网络身份,否则创建会失败或不工作。删除/缩容 StatefulSet 不会自动删除 PVC,这是有意为之的数据安全保护。
完整示例
yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None # Headless Service
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: registry.k8s.io/nginx-slim:0.24
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "my-storage-class"
resources:
requests:
storage: 1Gi1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
建议
生产使用 ReadWriteOncePod 访问模式替代 ReadWriteOnce,避免同一 PV 被误并发挂载(官方推荐)。terminationGracePeriodSeconds 不要设为 0,否则 StatefulSet Pod 可能丢失数据,官方明确反对。
Pod 身份与 DNS
| 字段 | 含义 |
|---|---|
spec.serviceName | 治理该 StatefulSet 网络身份的 Headless Service 名 |
$(sts)-$(ordinal) | Pod 主机名,跨重调度保持不变 |
apps.kubernetes.io/pod-index | v1.32+ 自动打标签,值为序号,便于按索引路由/过滤 |
更新策略
RollingUpdate(默认):按序(N-1→0)更新,支持partition实现金丝雀式分批(只更新序号 ≥ partition 的 Pod)。OnDelete:修改模板后不自动更新,需手动删除 Pod 才触发替换,适合需要严格人工控制的场景。
yaml
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # 仅 web-2 更新,web-0/1 保持旧版,验证后再调小 partition1
2
3
4
5
2
3
4
5
生产危险
使用默认 OrderedReady 策略滚动更新时,若某 Pod 长期处于未就绪状态,更新会卡在该序号,后续 Pod 全部不动,形成需要人工介入的“broken state”。发布前务必确保 Readiness 能快速通过,并监控 kubectl rollout status。
生产实践
- 存储类必须支持动态供给或提前预置 PV,否则 Pod 永远
Pending。 - 扩缩容顺序保证:缩容先停最大序号,且其所有“后继”必须先完全关闭——这意味着缩容有依赖约束,不能跳号。
- 删除前先缩到 0:如需优雅终止全部 Pod,先把
replicas调到 0,再删除 StatefulSet,避免无序终止破坏法定人数(quorum)。 - 备份独立于集群:PVC 不随 StatefulSet 删除,但节点/集群级灾难仍需外部备份(如 Velero、存储快照)。
故障排查
| 现象 | 原因 | 处理 |
|---|---|---|
一直 Pending | PVC 无法绑定(无 StorageClass/无 PV) | 检查 StorageClass 与 PV |
| --- | --- | --- |
| 滚动更新卡住 | 某 Pod 未 Ready(OrderedReady) | 查该 Pod 事件/探针;必要时修正模板 |
| Pod 被新节点调度后无数据 | 用了本地存储且未绑定 node | 持久卷需跨节点可访问(网络存储) |
| DNS 查不到新 Pod | CoreDNS 负缓存 | 直连 API watch,或调小 CoreDNS 缓存 |
常见坑
- 忘了建 Headless Service,StatefulSet 虽然能创建但网络身份异常。
- 把
terminationGracePeriodSeconds设 0,强制删除可能致数据不一致。 - 误以为缩容会删 PVC——不会,需手动清理,否则 PV 泄漏。
替代方案与权衡
- 纯无状态服务坚决用 Deployment,StatefulSet 的复杂度不值得。
- 某些有状态中间件(如 Cassandra)官方提供 Operator,比手写 StatefulSet 更省心,但引入额外依赖。
FAQ
Q:StatefulSet 删除后 PVC 还在吗? A:在。Kubernetes 故意保留 PVC 以防数据丢失,需管理员手动删除。
Q:partition 怎么用于金丝雀? A:设 partition=N-1 只更新最大序号 Pod,验证无误后逐步调小至 0,实现逐台灰度。
参考资料
- StatefulSets - Kubernetes 官方文档,访问日期:2026-10-08。
- StatefulSet 基本概念 - Kubernetes 官方文档,访问日期:2026-10-08。
- StatefulSet 更新策略 - Kubernetes 官方文档,访问日期:2026-10-08。