StatefulSet 是什么
StatefulSet 为有状态应用提供三样 Deployment 给不了的东西:稳定的 Pod 名称(名字带固定序号,重建后不变)、稳定的网络标识(配合无头服务得到可解析的实例地址)、每个副本独享的存储(卷声明随副本一起创建,不随重建消失)。数据库、消息队列、协调服务这类需要区分彼此的成员,靠的就是这三样。
和 Deployment 的本质差别
Deployment 的副本是可互换的,谁被删掉都无所谓;StatefulSet 的副本是有身份的,序号即角色,扩缩容必须按序进行。默认情况下创建是按序号从小到大逐个来,且前一个就绪才继续下一个;缩容则从大到小。删除对象时通常不会连带删除它创建的卷声明,这是有意为之的数据保护设计。
基本信息
| 适用环境 | 任何 Kubernetes 发行版,API 版本 apps/v1 |
|---|---|
| Pod 命名 | 负载名加序号,从 0 开始,重建后名称不变 |
| 网络标识 | 依赖 serviceName 指定的无头服务提供实例级解析 |
| 存储 | 通过卷声明模板为每个副本生成独立声明 |
| 默认顺序 | 按序号顺序创建与更新,逆序缩容与删除 |
| 常用字段 | serviceName、replicas、selector、volumeClaimTemplates、podManagementPolicy |
典型使用场景
- 数据库集群:主从角色与序号绑定,每个实例需要独立持久化目录
- 消息队列:成员间需要稳定的网络标识与独立存储
- 分布式协调组件:节点身份固定,重启后要回到同一份数据上
- 有状态中间件的迁移:把传统部署方式搬到集群里,保留身份语义
- 顺序化任务:需要严格按序执行的初始化或升级流程
- 本地缓存类服务:每个实例绑定自己的卷,重建后数据仍在
StatefulSet 清单
StatefulSet 清单
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
spec:
serviceName: db-headless # 关联的无头服务,提供稳定解析
replicas: 3
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
spec:
containers:
- name: db
image: registry.example.com/postgres:16.2
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates: # 每个副本各生成一份,名称带序号
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 20Gi日常操作
日常操作
kubectl get sts db -o wide # 看就绪副本数与序号范围
kubectl get pods -l app=db -w # 观察按序创建过程
kubectl get pvc -l app=db # 每个副本的卷声明
kubectl exec -it db-0 -- nslookup db-1.db-headless # 验证实例级解析
kubectl scale sts/db --replicas=3 # 扩缩容,注意顺序关键字段读法
| 字段 | 含义 | 异常表现 |
|---|---|---|
| serviceName | 关联的无头服务名 | 写错则实例解析不可用,成员互相找不到 |
| replicas | 副本数,决定序号范围 | 缩容会先删最大序号,注意数据迁移 |
| volumeClaimTemplates | 每个副本的独立卷声明模板 | 删除对象后声明通常保留,需手工清理 |
| podManagementPolicy | 是否严格按序管理 | 并行模式启动快但失去顺序保证 |
| selector | 认领哪些 Pod | 创建后不可修改,写错只能重建 |
使用前要确认的事
- 应用自身是否支持成员动态加入与离开,扩缩容前先确认它能处理
- 数据是否需要迁移或再平衡,缩容不是简单减一个副本
- 存储的访问模式与副本调度是否冲突,单挂载模式的卷会把副本限制在特定节点
删除 StatefulSet 对象默认不会删除它创建的卷声明,数据仍在;需要恢复时按同样的名称与模板重建即可重新挂上。
Pod 销毁重建后名称与卷都不变,因此应用可以依赖「名字即身份」这一前提做配置。
就绪判断写错会让顺序创建停在某个序号上,后面的副本永远不会创建,表面看是「副本数不够」,实际是第一个卡住了。