StatefulSet 解决的是一类 Deployment 不擅长的问题:副本之间不是完全可互换的。数据库、消息队列、带复制关系的中间件,往往都需要稳定名字、稳定卷,以及更可控的启动和更新顺序,这正是 StatefulSet 出场的地方。
什么时候该用 StatefulSet
- 每个副本都需要自己的持久卷
- 副本身份不能乱
- 启动和关闭顺序很重要
- 集群内部发现依赖固定 DNS 名称
如果你的副本本质上可以互换、数据可丢弃、顺序也不重要,Deployment 通常会更简单。
StatefulSet 和 Deployment 的区别
适合 Deployment:
- 副本完全可互换
- 不需要稳定身份
- 数据可以外置或不持久化
适合 StatefulSet:
- 每个副本有自己的身份和数据
- 有序更新比速度更重要
- 客户端或集群节点需要直接识别具体副本
很多数据库部署不稳定,不是因为 YAML 写错,而是一开始就选错了控制器。
StatefulSet 真正提供了什么
- 稳定 Pod 名称,比如
mysql-0、mysql-1 - 每个副本独立 PVC
- 默认按序启动和缩容
- 借助 Headless Service 提供稳定 DNS
它不会自动给你提供备份、主从切换或恢复策略。这些能力仍然需要你自己设计。
Headless Service 是常见配套项
大多数 StatefulSet 都会配一个 Headless Service:
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
clusterIP: None
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
这样每个副本都会有稳定 DNS,例如:mysql-0.mysql.default.svc.cluster.local。
核心片段
spec:
serviceName: mysql
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
为什么它默认要“慢一点”
StatefulSet 默认按序扩缩容,也通常按序更新。相比 Deployment,这会慢很多,但它换来的好处是:
- 更符合主从或成员关系
- 更适合有初始化依赖的应用
- 更容易控制风险推进顺序
这种“慢”通常不是缺点,而是有状态系统必须付出的代价。
稳定 DNS 和访问方式
有状态系统经常需要两种访问模式:
- 通过普通 Service 做统一访问
- 通过稳定 Pod DNS 直接访问某个副本
例如:写流量走主节点,读流量走副本;或者复制关系要精确指定 peer。StatefulSet 之所以有价值,就是因为这些身份不会随着重建而漂移。
存储模型意味着什么
StatefulSet 通常是“一副本一卷”,所以 ReadWriteOnce 很常见。也正因为如此,副本数一多,PVC 数和存储成本也会跟着一起涨。
这和 Deployment 那种“副本多一点只是多几个容器”的感觉完全不同。
如果你想把这条存储链路看完整,建议配合阅读 kubernetes-quickstart-pv-pvc.md(从工作负载视角理解 claim 和绑定)以及 kubernetes-quickstart-storageclass.md(从平台视角理解 provisioner 和动态供给策略)。
滚动更新和分区更新
默认情况下,StatefulSet 会从高序号向低序号滚动更新。你也可以通过分区控制只更新一部分副本:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 1
这种方式很适合数据库或带主从拓扑的系统,能先在次要副本上验证,再逐步推进。
Pod 管理策略
默认的有序策略通常更稳。只有在应用明确能接受乱序启动时,才考虑 podManagementPolicy: Parallel。这个参数不是“提速开关”,而是业务行为假设的一部分。
StatefulSet 最常见的坑
PVC 没绑定
这时候 StatefulSet 其实根本没法继续推进,Pod 只是外在表现。
readiness 太严格
只要 readiness 一直不过,按序更新就可能整条链卡住。
把 PVC 当备份
PVC 解决的是持久化,不是恢复能力。
用 Deployment 的思路操作有状态副本
随手删 Pod、让多个副本共享一个卷、把所有副本当完全等价实例,都会让 StatefulSet 场景出问题。
一套实用排障顺序
kubectl get pods
kubectl get pvc
kubectl describe pod <pod-name>
kubectl describe pvc <claim-name>
kubectl get events -A
然后依次确认:
- 是哪个序号的副本卡住了?
- 存储是否已经绑定并成功挂载?
- readiness 是否阻断了后续顺序推进?
- 应用自身是否依赖固定启动顺序或成员关系?
恢复思维一定要单独建立
StatefulSet 给你的是稳定身份,不是自动韧性。你仍然需要:
- 备份
- 恢复演练
- 现实的 failover 预期
- 副本容量规划
- 迁移方案
很多教程只讲“怎么跑起来”,真正麻烦的是“坏了之后怎么办”。
FAQ
Q: 为什么不用 Deployment 加一个 PVC 就行? A: 因为 Deployment 把副本当可互换实例,而 StatefulSet 强调的是稳定身份和一副本一卷的关系,这对数据库和集群成员型服务更关键。
Q: 为什么 StatefulSet 的发布总是比 Deployment 慢? A: 因为它要优先保证顺序、身份和存储安全,速度天然不是第一目标。
Q: 删除 StatefulSet 以后,数据一定会跟着删吗? A: 不一定。PVC 往往会保留,但最终还要看底层存储与回收策略怎么配置。
下一步阅读
- 接着读
kubernetes-quickstart-headless-service.md - 如果你想看具体数据库场景,继续读 MySQL 系列
- 如果要进入更真实的运维视角,可以继续看备份、发布和排障相关内容
收个尾
StatefulSet 是 Kubernetes 里一个非常典型的分界点:从这里开始,你不再只是“发一个应用”,而是真正开始为身份、数据、恢复和顺序负责。它不神奇,但它很诚实,系统有多复杂,它就要求你想得有多完整。