Appearance
StatefulSet
Deployment 管理无状态副本,Pod 名字是随机后缀,PVC 也是共享一个。StatefulSet 管理有状态副本,给每个 Pod 提供三样东西:稳定名称、稳定网络身份和独立 PVC。
稳定名称意味着 Pod 被重建后名字不变(mysql-0 永远是 mysql-0),稳定网络身份意味着配合 Headless Service 后每个 Pod 有固定 DNS,独立 PVC 意味着缩容后 PVC 不删,下次扩容同序号 Pod 会重新挂上同一块盘。
单独的 StatefulSet 不提供高可用。复制、选主、备份、恢复这些能力要由数据库或 Operator 自己处理。StatefulSet 只负责把"每个副本有固定身份和独立存储"这件事做好。
稳定网络身份和 Headless Service
StatefulSet 的每个 Pod 有两个名字:
- Pod 名:
<statefulset-name>-<序号>,如mysql-0、mysql-1 - DNS 名:
<pod-name>.<headless-service-name>.<namespace>.svc.cluster.local
DNS 名的前提是创建了一个 clusterIP: None 的 Headless Service:
yaml
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: demo
spec:
clusterIP: None # Headless——DNS 返回 Pod IP 而非 ClusterIP
selector:
app: mysql
ports:
- name: mysql
port: 3306Headless Service 的 DNS 查询不回一个虚拟 IP,而是返回所有 Ready Pod 的 IP 列表。调用方拿到的是真实 Pod IP,直连后端。这对 Kafka、ZooKeeper、数据库集群非常关键:节点之间通信需要知道彼此的真实地址,而不是经过 ClusterIP 再做一层转发。
Pod 的 DNS 名则是独立解析的:
text
mysql-0.mysql.demo.svc.cluster.local → mysql-0 的 Pod IP
mysql-1.mysql.demo.svc.cluster.local → mysql-1 的 Pod IPvolumeClaimTemplates
Deployment 里所有 Pod 共享同一个 PVC 模板(写在 spec.template.spec.volumes 里)。StatefulSet 用 volumeClaimTemplates,会为每个 Pod 单独创建一个 PVC,命名规则是 <template名称>-<statefulset名称>-<序号>:
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: demo
spec:
serviceName: mysql # 必须指定,对应 Headless Service 名称
replicas: 2
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.4
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-root
key: password
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates: # 每个 Pod 独立 PVC 的来源
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: nfs-client
resources:
requests:
storage: 10Gi创建后查看 PVC 命名:
bash
kubectl -n demo get pod -l app=mysql
kubectl -n demo get pvc会看到 data-mysql-0 和 data-mysql-1 两个 PVC,分别绑定到两个 Pod。mysql-0 Pod 删掉重建后,名字不变,会重新挂上 data-mysql-0 这块 PVC——数据还在。
扩缩容
扩容按序号递增,先建 mysql-2,Ready 后才建 mysql-3:
bash
kubectl -n demo scale statefulset mysql --replicas=3缩容按序号递减,先删 mysql-2,再删 mysql-1:
bash
kubectl -n demo scale statefulset mysql --replicas=1缩容时 StatefulSet 默认不删除 PVC。这是有意为之:Pod 删了数据还在,万一误缩容还能扩回去。但这也意味着 PVC 会一直占用后端存储空间,需要在确认数据不再需要后手动清理 PVC。
扩缩容的默认有序行为可以通过 podManagementPolicy: Parallel 改成并行,适合不需要启动顺序的场景。
更新策略
yaml
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 0 # 只更新序号 ≥ partition 的 Pod,可用于金丝雀StatefulSet 的滚动更新是从最大序号向最小序号逐个进行的。mysql-2 更新完成并 Ready 后,才会更新 mysql-1。这和 Deployment 的滚动更新逻辑不同。
对于数据库类服务,默认的滚动更新机制可能不够。主从复制场景下,先更新从库再更新主库的顺序需求、更新前的数据一致性检查、更新后的复制状态验证,StatefulSet 本身并不处理。这些通常由 Operator 接管。
partition 是一个实用的字段:设为 2 时只更新序号 ≥2 的 Pod,可以先在一两个副本上验证新版本,再逐步调小 partition 推向全部。
适用和不适用
适合 StatefulSet 的场景:
| 场景 | StatefulSet 提供的价值 |
|---|---|
| 需要固定网络身份的中间件 | Kafka broker ID、ZooKeeper myid 和配置里硬编码的地址可以一一对应 |
| 每个副本需要独立数据盘 | 数据库 data 目录、索引、WAL 各自独立 |
| 有启动顺序要求的集群 | 先启动种子节点,再启动普通节点 |
| 稳定的 Pod 名用于服务发现 | 配置文件和应用代码里能写出确定的地址 |
不适合直接套用的情况:
| 场景 | 原因 |
|---|---|
| 无状态 Web 服务 | Deployment 更简单,没有序号和 PVC 的额外管理成本 |
| MySQL 高可用 | 还要考虑复制、选主、读写分离、备份、故障切换 |
| 复杂的分布式数据库 | 通常有专门的 Operator 来处理配置、扩缩、备份和升级 |
| 不了解底层存储 | PVC 和后端存储的故障处理比 Deployment 的排查链路更长 |
排查
bash
kubectl -n demo get sts mysql
kubectl -n demo get pod -l app=mysql -o wide
kubectl -n demo get pvc
kubectl -n demo describe pod mysql-0常见问题:
| 现象 | 判断方向 |
|---|---|
| Pod 卡在 Pending | PVC 未绑定、节点资源不够、调度亲和性/污点不满足 |
| Pod 卡在 ContainerCreating | 存储挂载失败、节点缺存储客户端、CSI 异常 |
| 扩容新副本异常 | volumeClaimTemplates 里 StorageClass 不存在、镜像错误、资源不足 |
| 删除 StatefulSet 后 PVC 和数据还在 | 设计行为——PVC 不随 StatefulSet 级联删除 |
| 新 Pod 挂上了旧数据 | 同序号 PVC 被复用,这正是 StatefulSet 的设计预期 |
| 缩容后 PVC 仍占用空间 | 需手动删除不再需要的 PVC |