摘要:通过稳定网络身份、独立持久化存储和有序生命周期理解 StatefulSet,掌握更新策略、分区发布、扩缩容与常见故障排查。
本节学习 Kubernetes 中用于管理有状态应用的核心控制器——StatefulSet。
Deployment 主要用于管理无状态应用。
但在实际生产环境中,还有很多应用不能简单地通过删除旧 Pod、创建新 Pod 来替换。
例如:
- MySQL 数据库
- Redis 集群
- Kafka 消息队列
- ZooKeeper 集群
- Elasticsearch 集群
这些应用通常需要稳定的网络身份、独立的数据存储,以及特定的启动和关闭顺序。
这就是 StatefulSet 存在的意义。
一、为什么需要 StatefulSet?
1. Deployment 的局限性
先回顾 Deployment。
假设我们部署一个有 3 个副本的 Web 应用:
flowchart TB
N0["Deployment"]
N1["ReplicaSet"]
N2["Pod-A"]
N3["Pod-B"]
N4["Pod-C"]
N0 --> N1
N1 --> N2
N1 --> N3
N1 --> N4
这些 Pod 通常具有以下特点:
- 可以被随时替换。
- 不要求固定的 Pod 名称。
- 不要求固定的网络身份。
- 可以根据需要扩容和缩容。
- 应用状态通常保存在外部数据库或共享存储中。
例如:
Pod-A 删除后,Deployment 创建的新 Pod 可能叫 Pod-D。
这对于无状态 Web 服务没有问题。
但是考虑数据库:
MySQL 集群
mysql-0mysql-1mysql-2假设 mysql-1 保存着某个节点特有的数据或承担固定的集群角色。
如果它被删除后,重新创建成一个完全不同身份的 Pod,就可能给集群成员识别、数据挂载和复制关系带来问题。
因此,有状态应用需要更加稳定的身份管理。
2. StatefulSet 的核心设计
StatefulSet 是 Kubernetes 用于管理有状态应用的工作负载控制器。
它为每个 Pod 提供:
- 稳定的名称。
- 稳定的网络标识。
- 稳定的持久化存储关联。
- 有序的创建和删除机制(默认模式)。
例如:
flowchart TB
N0["StatefulSet"]
N1["Pod-0"]
N2["Pod-1"]
N3["Pod-2"]
N4["PVC-0"]
N5["PVC-1"]
N6["PVC-2"]
N7["PV-0"]
N8["PV-1"]
N9["PV-2"]
N0 --> N1
N0 --> N2
N0 --> N3
N1 --> N4
N2 --> N5
N3 --> N6
N4 --> N7
N5 --> N8
N6 --> N9
注意:
每个 Pod 都可以拥有自己独立的 PVC,并关联自己的持久化卷。
这使得 Pod 被删除后,重新创建的同名 Pod 可以重新使用原来的 PVC。
二、StatefulSet 与 Deployment 的区别
这是 CKA 必须掌握的知识点。
| 对比维度 | Deployment | StatefulSet |
|---|---|---|
| 主要用途 | 无状态应用 | 有状态应用 |
| Pod 名称 | 通常不固定 | 稳定、有序 |
| Pod 身份 | 可替换 | 稳定 |
| 网络标识 | 通常不固定 | 稳定 |
| 存储关联 | 通过 PVC 等方式配置 | 可通过模板为每个 Pod 创建独立 PVC |
| 创建顺序 | 通常不保证 | 默认有序 |
| 删除顺序 | 不保证 | 默认逆序 |
| 扩缩容 | 支持 | 支持 |
例如:
Deployment:
nginx-7d8f9c-abc12nginx-7d8f9c-def34nginx-7d8f9c-ghi56StatefulSet:
mysql-0mysql-1mysql-2StatefulSet 的名称由 StatefulSet 名称和序号组成。
序号默认从 0 开始。
重点:StatefulSet 并不是只能用于数据库,而是适合任何需要稳定身份或有序管理的应用。
三、StatefulSet 的稳定网络标识
这是 StatefulSet 的核心知识之一。
1. 为什么需要 Headless Service?
StatefulSet 通常需要配合 Headless Service 使用。
先了解普通 Service。
普通 ClusterIP Service:
flowchart TB
N0["Client"]
N1["ClusterIP Service"]
N2["Pod-0"]
N3["Pod-1"]
N4["Pod-2"]
N0 --> N1
N1 --> N2
N1 --> N3
N1 --> N4
普通 Service 提供一个稳定的虚拟 IP,客户端通过 Service 访问后端 Pod。
但是对于数据库集群,客户端可能需要直接定位某一个具体的 Pod。
例如:
- mysql-0 是主节点。
- mysql-1 是从节点。
- mysql-2 是从节点。
这时仅有一个统一的 Service IP 不够方便。
因此可以使用 Headless Service。
2. Headless Service 是什么?
Headless Service 是一种没有 ClusterIP 的 Service。
定义方式:
spec: clusterIP: None例如:
apiVersion: v1kind: Service
metadata: name: mysql
spec: clusterIP: None
selector: app: mysql
ports: - port: 3306 targetPort: 3306它的特点:
- 不分配普通 ClusterIP。
- 不提供普通 ClusterIP Service 的虚拟 IP 负载均衡入口。
- DNS 可以返回符合条件的 Pod 地址。
- 可以通过 DNS 直接定位具体 Pod。
3. StatefulSet 的 DNS 命名规则
假设:
StatefulSet 名称:mysqlHeadless Service:mysqlNamespace:defaultCluster Domain:cluster.localPod 的稳定 DNS 名称:
<Pod 名称 >.<Headless Service 名称 >.<Namespace>.svc.<Cluster Domain>因此:
mysql-0.mysql.default.svc.cluster.local
mysql-1.mysql.default.svc.cluster.local
mysql-2.mysql.default.svc.cluster.local即使 mysql-1 被删除后重新创建,只要它重新加入同一个 StatefulSet,其稳定 DNS 名称仍然是:
mysql-1.mysql.default.svc.cluster.local注意:DNS 记录的实际可解析性还取决于 Pod 是否满足 DNS 发布条件、Service 配置以及集群 DNS 状态。
四、StatefulSet 的持久化存储
这是 StatefulSet 最重要的功能之一。
- PV(PersistentVolume):持久化存储资源。
- PVC(PersistentVolumeClaim):存储申请。
- StorageClass:动态创建存储资源的机制。
StatefulSet 可以通过 volumeClaimTemplates 自动为每个 Pod 创建独立 PVC。
1. 为什么每个 Pod 需要独立存储?
假设有一个 MySQL 集群:
mysql-0:保存数据 Amysql-1:保存数据 Bmysql-2:保存数据 C如果三个 Pod 共用同一个数据目录,可能造成数据冲突,甚至破坏数据库的数据一致性。
因此通常采用:
mysql-0 ---- PVC-mysql-0 ---- PV-0
mysql-1 ---- PVC-mysql-1 ---- PV-1
mysql-2 ---- PVC-mysql-2 ---- PV-2每个 Pod 都有自己的 PVC。
2. volumeClaimTemplates
这是 StatefulSet 特有的重要字段。
示例:
volumeClaimTemplates: - metadata: name: mysql-data
spec: accessModes: - ReadWriteOnce
resources: requests: storage: 10Gi
storageClassName: standard含义:
- name:PVC 模板名称。
- accessModes:存储访问模式。
- requests.storage:申请存储容量。
- storageClassName:使用的存储类。
当 StatefulSet 创建 Pod 时,会自动创建对应 PVC。
例如:
StatefulSet: mysql
Pod: mysql-0PVC: mysql-data-mysql-0
Pod: mysql-1PVC: mysql-data-mysql-1
Pod: mysql-2PVC: mysql-data-mysql-2注意 PVC 名称规则:
<volumeClaimTemplates 名称 >-<StatefulSet 名称 >-<Pod 序号 >3. Pod 删除后,PVC 会怎样?
这是一个非常重要的生产知识点。
假设:
flowchart TB
N0["mysql-0"]
N1["mysql-data-mysql-0"]
N2["PV-0"]
N0 --> N1
N1 --> N2
现在删除 Pod:
kubectl delete pod mysql-0通常:
- mysql-0 被删除。
- mysql-data-mysql-0 保留。
- PV-0 仍然保留,具体取决于存储及回收策略。
- StatefulSet 创建新的 mysql-0。
- 新的 mysql-0 重新挂载原来的 PVC。
重点
删除 StatefulSet 管理的 Pod,默认不会删除其 PVC。
因此,Pod 重建后通常可以重新使用原有数据。
4. StatefulSet 删除后,PVC 会怎样?
默认情况下,即使删除 StatefulSet,volumeClaimTemplates 创建的 PVC 也会保留。
例如:
kubectl delete statefulset mysqlStatefulSet 被删除,但 PVC 默认不会因此自动删除。
这样设计是为了保护有状态应用的数据。
如果希望管理 PVC 的保留和删除行为,可以使用:
spec: persistentVolumeClaimRetentionPolicy: whenDeleted: Retain whenScaled: Retain支持两种策略:
| 策略 | 含义 |
|---|---|
| Retain | 保留 PVC |
| Delete | 删除符合条件的 PVC |
其中:
whenDeleted:删除 StatefulSet 时的 PVC 处理策略。whenScaled:缩容时,被移除 Pod 对应 PVC 的处理策略。
默认两者都是 Retain。
注意:PVC 被删除不等于底层数据一定立即销毁。PV 的回收行为还取决于 PV 的 persistentVolumeReclaimPolicy,例如 Retain 或 Delete。
五、StatefulSet 的有序管理机制
这是 StatefulSet 与 Deployment 的另一个重要区别。
StatefulSet 默认采用有序创建和删除机制。
1. OrderedReady(默认策略)
配置:
spec: podManagementPolicy: OrderedReady创建时:
flowchart TB
N0["创建 mysql-0"]
N1["mysql-0 Ready"]
N2["创建 mysql-1"]
N3["mysql-1 Ready"]
N4["创建 mysql-2"]
N0 --> N1
N1 --> N2
N2 --> N3
N3 --> N4
删除时:
flowchart TB
N0["删除 mysql-2"]
N1["删除 mysql-1"]
N2["删除 mysql-0"]
N0 --> N1
N1 --> N2
也就是说:
- 创建顺序:从小到大。
- 删除顺序:从大到小。
- 扩容时:从当前最大序号继续增加。
- 缩容时:从最大序号开始移除。
例如,从 3 个副本扩容到 5 个:
mysql-0mysql-1mysql-2mysql-3mysql-4默认情况下,需要前一个 Pod 达到 Ready,才会继续创建下一个 Pod。
2. Parallel(并行策略)
如果不希望 Pod 按顺序创建,可以使用:
spec: podManagementPolicy: Parallel这样:
- 创建时可以并行创建 Pod。
- 删除时也可以并行删除 Pod。
- 不再要求前一个 Pod Ready 后才创建下一个 Pod。
对比:
| 特性 | OrderedReady | Parallel |
|---|---|---|
| 创建方式 | 顺序创建 | 并行创建 |
| 删除方式 | 逆序删除 | 并行删除 |
| 默认策略 | 是 | 否 |
| 适用场景 | 有顺序要求的应用 | 不依赖启动顺序的应用 |
注意:podManagementPolicy 主要影响扩缩容过程中的 Pod 创建和删除,不改变 StatefulSet 的稳定身份机制,也不改变其滚动更新策略。
六、StatefulSet 更新策略
StatefulSet 也需要升级应用版本。
例如:
flowchart TB
N0["nginx:1.27"]
N1["nginx:1.28"]
N0 --> N1
StatefulSet 主要有两种更新策略:
- RollingUpdate
- OnDelete
1. RollingUpdate
默认更新策略。
spec: updateStrategy: type: RollingUpdate假设当前有:
mysql-0mysql-1mysql-2升级时默认按照逆序更新:
flowchart TB
N0["mysql-2"]
N1["mysql-2 更新完成并 Ready"]
N2["mysql-1 更新"]
N3["mysql-1 更新完成并 Ready"]
N4["mysql-0 更新"]
N0 --> N1
N1 --> N2
N2 --> N3
N3 --> N4
注意:
StatefulSet 默认滚动更新顺序是从最大序号到最小序号。
2. OnDelete
配置:
spec: updateStrategy: type: OnDelete含义:
修改 StatefulSet 的 Pod 模板后,不会自动删除旧 Pod。
需要手动删除旧 Pod:
kubectl delete pod mysql-2StatefulSet 才会根据最新模板重新创建 mysql-2。
这种方式适合需要人工控制每个实例升级时间的场景。
3. RollingUpdate 与 OnDelete 对比
| 对比 | RollingUpdate | OnDelete |
|---|---|---|
| 自动更新 | 是 | 否 |
| 更新顺序 | 默认逆序 | 手动控制 |
| 适用场景 | 常规升级 | 人工控制升级 |
| 是否需要手动删除 Pod | 不需要 | 需要 |
七、StatefulSet 高级更新:Partition(分区更新)
这是 StatefulSet 的进阶知识点,也是生产环境中实用的灰度升级机制。
1. 什么是 Partition?
Partition 可以控制 StatefulSet 滚动更新时,哪些 Pod 需要升级。
例如:
当前有 5 个副本:
mysql-0mysql-1mysql-2mysql-3mysql-4配置:
spec: updateStrategy: type: RollingUpdate rollingUpdate: partition: 3含义:
只更新序号大于或等于 3 的 Pod。
因此:
mysql-0 旧版本mysql-1 旧版本mysql-2 旧版本mysql-3 新版本mysql-4 新版本这实际上是一种按照 Pod 序号控制更新范围的机制。
2. Partition 的工作原理
假设:
replicas: 5
updateStrategy: type: RollingUpdate rollingUpdate: partition: 3更新镜像:
kubectl set image statefulset/mysql nginx=nginx:1.28结果:
| Pod | 是否更新 |
|---|---|
| mysql-0 | 否 |
| mysql-1 | 否 |
| mysql-2 | 否 |
| mysql-3 | 是 |
| mysql-4 | 是 |
如果后续将 Partition 修改为 0:
partition: 0那么所有 Pod 都会逐步更新到新版本。
3. Partition 的应用场景
例如:
数据库集群有 5 个节点。
你不想一次性升级所有节点,而是:
- 先升级部分节点。
- 验证应用运行情况。
- 确认没有问题。
- 再逐步升级剩余节点。
这就是 Partition 的价值。
注意:Partition 只控制 StatefulSet 的 Pod 更新范围,不会自动判断数据库主从角色,也不等同于完整的金丝雀发布系统。
八、StatefulSet 扩容与缩容
1. 扩容
假设当前有三个副本:
mysql-0mysql-1mysql-2扩容到五个:
kubectl scale statefulset mysql --replicas=5默认 OrderedReady 策略下:
mysql-0mysql-1mysql-2mysql-3mysql-4新增两个 Pod,并为其创建独立 PVC。
2. 缩容
缩容到两个:
kubectl scale statefulset mysql --replicas=2默认有序策略下:
删除 mysql-4删除 mysql-3最终保留:
mysql-0mysql-1注意:默认情况下,mysql-3 和 mysql-4 对应的 PVC 会保留。
3. 为什么缩容不会自动删除 PVC?
因为 StatefulSet 默认优先保护数据。
例如:
flowchart TB
N0["mysql-2"]
N1["mysql-data-mysql-2"]
N0 --> N1
缩容时 Pod 被删除,但 PVC 保留。
以后再次扩容到三个副本时:
flowchart TB
N0["重新创建 mysql-2"]
N1["重新挂载 mysql-data-mysql-2"]
N0 --> N1
这样可以避免因为缩容而直接丢失数据库数据。
生产环境特别注意:缩容数据库 StatefulSet 之前,必须先确认数据库集群成员退出、数据复制状态和存储安全。Kubernetes 删除 Pod 并不等于数据库节点已经安全退群。
九、StatefulSet 的删除机制
StatefulSet 的删除和普通 Deployment 有一个重要区别。
1. 删除 StatefulSet
kubectl delete statefulset mysql默认级联删除其管理的 Pod。
但是不会自动删除 volumeClaimTemplates 创建的 PVC。
2. 只删除 Pod,不删除 StatefulSet
kubectl delete pod mysql-0StatefulSet 会重新创建 mysql-0。
3. 缩容 StatefulSet
kubectl scale statefulset mysql --replicas=0所有 Pod 被删除,但默认保留 PVC。
4. 删除 StatefulSet 时保留 Pod
如果想删除 StatefulSet 但保留 Pod,可以:
kubectl delete statefulset mysql --cascade=orphan这种操作需要谨慎。
因为此时 Pod 不再由原来的 StatefulSet 自动管理。
如果之后重新创建同名 StatefulSet,还需要注意 Pod 的认领和管理关系。
十、StatefulSet 故障排查
实际生产环境中,StatefulSet 常见问题主要有以下几类。
1. Pod 一直 Pending
例如:
mysql-0 Pendingmysql-1 Pending常见原因:
- PVC 无法绑定。
- StorageClass 不存在。
- PV 容量不足。
- 节点资源不足。
- 存储拓扑不匹配。
排查:
kubectl describe pod mysql-0查看 PVC:
kubectl get pvc查看 PV:
kubectl get pv查看 StorageClass:
kubectl get storageclass2. StatefulSet 只有第一个 Pod 正常运行
例如:
mysql-0 Runningmysql-1 Pendingmysql-2 Pending默认 OrderedReady 策略下,后续 Pod 必须等待前一个 Pod 达到 Ready 才能继续创建。
所以需要重点检查 mysql-0 是否 Ready。
kubectl describe pod mysql-0可能原因:
- Readiness Probe 失败。
- 容器启动异常。
- 数据库初始化失败。
- 依赖服务不可用。
3. StatefulSet 无法完成滚动更新
查看:
kubectl rollout status statefulset/mysql查看:
kubectl describe statefulset mysql重点检查:
- 新版本 Pod 是否启动失败。
- Readiness Probe 是否通过。
- 镜像是否能够正常拉取。
- Partition 是否限制了更新范围。
- 是否有较低序号 Pod 阻塞更新过程。
特别注意:StatefulSet 的滚动更新默认从最大序号向最小序号执行,较高序号的 Pod 更新不成功时,可能阻止更低序号的 Pod 继续更新。
十一、StatefulSet 必须掌握的命令
| 操作 | 命令 |
|---|---|
| 查看 StatefulSet | kubectl get sts |
| 查看详细信息 | kubectl describe sts mysql |
| 创建 StatefulSet | kubectl apply -f sts.yaml |
| 扩容 | kubectl scale sts mysql --replicas=5 |
| 缩容 | kubectl scale sts mysql --replicas=2 |
| 查看 Pod | kubectl get pods |
| 查看 PVC | kubectl get pvc |
| 更新镜像 | kubectl set image sts/mysql nginx=nginx:1.28 |
| 查看更新状态 | kubectl rollout status sts/mysql |
| 查看更新历史 | kubectl rollout history sts/mysql |
| 回滚 | kubectl rollout undo sts/mysql |
| 删除 StatefulSet | kubectl delete sts mysql |
十二、StatefulSet 与 Deployment 综合对比
到这里,我们已经学习了两种最重要的工作负载控制器。
建议重点掌握下面这张表:
| 知识点 | Deployment | StatefulSet |
|---|---|---|
| 应用类型 | 无状态 | 有状态 |
| Pod 名称 | 通常不固定 | 固定序号 |
| 网络身份 | 通常通过 Service 统一访问 | 支持稳定 Pod DNS |
| 独立 PVC | 需要自行配置 | 支持自动创建 |
| 启动顺序 | 不保证 | 默认有序 |
| 删除顺序 | 不保证 | 默认逆序 |
| 滚动更新 | 支持 | 支持 |
| 版本回滚 | 支持 | 支持 |
| 分区更新 | 不支持该机制 | 支持 Partition |
| 并行创建 | 通常并行 | 支持 Parallel |
典型应用场景
Web 应用
Deployment Nginx、Spring Boot、Go Web 服务。
数据库集群
StatefulSet MySQL、PostgreSQL 等有状态实例。
消息队列集群
StatefulSet Kafka、ZooKeeper 等需要稳定节点身份的集群。
缓存服务
视架构选择 Redis 单实例、主从或集群部署,需要根据状态管理方式选择控制器。
注意:StatefulSet 只提供 Kubernetes 层面的稳定身份、存储关联和生命周期管理,不会自动实现 MySQL 主从复制、Redis 数据同步或 Kafka 集群成员管理。这些仍需要应用自身的集群机制或 Operator 配合。