StatefulSet 解决的是一类 Deployment 不擅长的问题:副本之间不是完全可互换的。数据库、消息队列、带复制关系的中间件,往往都需要稳定名字、稳定卷,以及更可控的启动和更新顺序,这正是 StatefulSet 出场的地方。

什么时候该用 StatefulSet

  • 每个副本都需要自己的持久卷
  • 副本身份不能乱
  • 启动和关闭顺序很重要
  • 集群内部发现依赖固定 DNS 名称

如果你的副本本质上可以互换、数据可丢弃、顺序也不重要,Deployment 通常会更简单。

StatefulSet 和 Deployment 的区别

适合 Deployment:

  • 副本完全可互换
  • 不需要稳定身份
  • 数据可以外置或不持久化

适合 StatefulSet:

  • 每个副本有自己的身份和数据
  • 有序更新比速度更重要
  • 客户端或集群节点需要直接识别具体副本

很多数据库部署不稳定,不是因为 YAML 写错,而是一开始就选错了控制器。

StatefulSet 真正提供了什么

  • 稳定 Pod 名称,比如 mysql-0mysql-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

然后依次确认:

  1. 是哪个序号的副本卡住了?
  2. 存储是否已经绑定并成功挂载?
  3. readiness 是否阻断了后续顺序推进?
  4. 应用自身是否依赖固定启动顺序或成员关系?

恢复思维一定要单独建立

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 里一个非常典型的分界点:从这里开始,你不再只是“发一个应用”,而是真正开始为身份、数据、恢复和顺序负责。它不神奇,但它很诚实,系统有多复杂,它就要求你想得有多完整。

参考链接