运行有状态应用
StatefulSet 适合副本不能简单互换的服务,例如数据库、Kafka、ZooKeeper 或需要固定网络身份的集群软件。它提供稳定的 Pod 名称、可配套独立 PVC,并按顺序创建和终止副本。
本篇目标:能判断何时该用 StatefulSet,写出配套的 Headless Service 和 volumeClaimTemplates,理解有序启动/终止和分区更新。

1. StatefulSet 与 Deployment 的关键区别
| 能力 | Deployment | StatefulSet |
|---|---|---|
| Pod 名称 | 随机后缀,随重建变化 | 稳定编号,如 mysql-0、mysql-1 |
| 存储 | 通常共享或无状态 | 常为每副本创建独立 PVC |
| 创建/删除顺序 | 并行、以可用性为主 | 默认有序,便于集群成员初始化 |
| 网络身份 | 无稳定 DNS 名称 | 配合 Headless Service 提供稳定 DNS |
| 典型用途 | API、Web、Worker | 数据库、消息队列、协调服务 |
StatefulSet 不是"更高级的 Deployment"。如果服务不要求稳定身份或独立数据卷,优先选择更简单的 Deployment。
2. Headless Service 提供稳定 DNS
StatefulSet 必须配一个 headless Service(clusterIP: None),用于为每个 Pod 提供稳定 DNS:
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: learning
spec:
clusterIP: None
selector:
app.kubernetes.io/name: mysql
ports:
- port: 3306
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: learning
spec:
serviceName: mysql
replicas: 3
selector:
matchLabels:
app.kubernetes.io/name: mysql
template:
metadata:
labels:
app.kubernetes.io/name: mysql
spec:
containers:
- name: mysql
image: mysql:8.4
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
对应 Pod 可以通过 mysql-0.mysql.learning.svc.cluster.local 这类地址互相发现。实际数据库镜像还需数据目录挂载、初始化脚本、备份和 TLS 配置,不能直接拿此示例上生产。
3. 有序启动与终止
3.1 podManagementPolicy
| 策略 | 行为 | 适用场景 |
|---|---|---|
OrderedReady(默认) | mysql-0 启动 → Ready → mysql-1 启动 → Ready → … | 需要 leader 先就绪的集群 |
Parallel | 所有 Pod 同时启动/终止 | 各副本之间无启动顺序依赖 |
删除 StatefulSet 时终止顺序与创建相反:mysql-2 先终止 → mysql-1 → mysql-0。
3.2 Init 容器做集群初始化
典型模式是用 Init 容器在有序启动的基础上判断 leader/follower:
spec:
podManagementPolicy: OrderedReady
template:
spec:
initContainers:
- name: init-cluster
image: registry.example.com/mysql-init:1.0
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
# mysql-0 初始化为 leader,mysql-1/2 作为 follower 加入
command:
- sh
- -c
- |
if [ "$POD_NAME" = "mysql-0" ]; then
echo "初始化为主节点"
else
echo "等待 mysql-0 就绪后加入集群"
fi
containers:
- name: mysql
image: mysql:8.4
4. 每个副本的持久化卷
spec:
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi
这会为 mysql-0 创建 data-mysql-0 PVC,mysql-1 创建 data-mysql-1 PVC,依此类推。
4.1 PVC 保留策略
spec:
persistentVolumeClaimRetentionPolicy:
whenDeleted: Retain # 删除 StatefulSet 时保留 PVC
whenScaled: Delete # 缩容时删除对应 PVC(数据丢失!)
| 策略值 | 行为 |
|---|---|
Retain | PVC 保留,需手动清理 |
Delete | PVC 随 StatefulSet 删除或缩容自动删除 |
whenScaled: Delete + 缩容 = 对应 Pod 的 PVC 和数据被删除。生产上建议 whenScaled: Retain,缩容后手动确认数据不再需要再删除 PVC。
5. 分区更新
StatefulSet 的 rollingUpdate.partition 让你只更新序号 ≥ N 的 Pod,适合分阶段灰度:
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # 只更新 mysql-2 及更大序号的 Pod
# 阶段 1:只更新 mysql-2(partition=2)
# 验证 mysql-2 的日志和指标
# 阶段 2:partition=1 → 更新 mysql-1 和 mysql-2
# 阶段 3:partition=0 → 更新全部
kubectl patch statefulset mysql -n learning \
-p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":1}}}}'
分区更新是 StatefulSet 特有的能力,Deployment 没有等价功能。
6. 生产检查项
- 优先使用云厂商托管数据库;自建前先明确高可用、备份、恢复演练和升级责任;
- 把数据备份恢复流程视为工作负载的一部分,不能只依赖 PVC;
- 升级镜像前阅读应用自身的兼容性矩阵——Kubernetes 能更新 Pod,不会替你完成数据格式迁移;
- 避免把单副本 StatefulSet 误当成高可用方案,它仍是单点;
- 确认
persistentVolumeClaimRetentionPolicy与团队的备份策略一致; - 缩容前先备份数据——Kubernetes 不保证缩掉的 Pod 的数据还能找回。
7. 练习
练习 A
- 创建上面的 Headless Service + StatefulSet(3 副本,用
nginx:alpine代替 MySQL),观察 Pod 命名是否依次为mysql-0、mysql-1、mysql-2 - 用
kubectl run tmp --image=busybox --rm -it --restart=Never -- nslookup mysql-0.mysql.learning测试 DNS - 删除
mysql-1Pod,观察它重新创建后名称是否还是mysql-1(验证稳定身份)
练习 B
- 给 StatefulSet 添加
volumeClaimTemplates,查看是否为每个 Pod 创建了独立 PVC - 将
podManagementPolicy改为Parallel,重建 StatefulSet,观察 Pod 是否同时启动而非依次
验收清单
- 能说明何时需要 StatefulSet,何时应使用 Deployment
- 知道 Pod 稳定身份来自名称和 headless Service
- 理解
volumeClaimTemplates为每个副本创建独立存储 - 知道
OrderedReady和Parallel的启动差异及分区更新的做法 - 上线前会确认 PVC 保留策略和备份恢复方案
下一篇:为节点运行系统 Agent。