深色模式
StatefulSet 部署有状态应用
摘要:Deployment 的 Pod 是无差别副本,数据库不行。本文用 StatefulSet 部署一个 MySQL 实例,讲清稳定名称、稳定 DNS、独立 PVC 模板与有序扩缩容,以及它做不到的事。
适用环境
- 可用 K8s 集群,已配置可用 StorageClass
kubectl可用- 内存 ≥ 4G
操作步骤
一、StatefulSet 相比 Deployment 多了什么
| 能力 | Deployment | StatefulSet |
|---|---|---|
| Pod 名称 | 随机后缀 | web-0、web-1 固定有序 |
| 重建后名称/存储 | 全变 | 保持,PVC 自动挂回原 Pod |
| 存储 | 所有副本共享卷声明 | 每个副本独立 PVC 模板 |
| 部署顺序 | 并行 | 按序 0→1→2 |
| DNS | 只有 Service 名 | 每个 Pod 有独立 DNS 记录 |
二、先建 headless Service
yaml
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
clusterIP: None # headless,不分配集群 IP
selector:
app: mysql
ports:
- port: 3306
name: mysql1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
这是 StatefulSet 的必备件,每个 Pod 通过 mysql-0.mysql.default.svc.cluster.local 这样的名字被单独解析。
三、创建 StatefulSet
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql # 必须与 headless Service 同名
replicas: 2
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates: # 每个副本自动生成一个 PVC
- metadata:
name: data
spec:
storageClassName: nfs-client
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
bash
kubectl create secret generic mysql-secret --from-literal=password='ExamplePass123'
kubectl apply -f statefulset.yaml
kubectl get pod -l app=mysql -w # 观察 mysql-0 起来后才是 mysql-1
kubectl get pvc -l app=mysql # 自动生成 data-mysql-0、data-mysql-11
2
3
4
2
3
4
四、验证稳定标识
bash
kubectl delete pod mysql-0
kubectl get pod -l app=mysql -w # 重建后仍然叫 mysql-0
kubectl exec -it mysql-0 -- mysql -uroot -pExamplePass123 -e "SHOW DATABASES;"1
2
3
2
3
被删的 Pod 会用同一个名字重建,并挂回原来那块 PVC,数据完好无损。
五、DNS 解析验证
bash
kubectl run dns-test --rm -it --image=busybox --restart=Never -- nslookup mysql-0.mysql
kubectl run dns-test --rm -it --image=busybox --restart=Never -- nslookup mysql1
2
2
headless Service 的 DNS 会返回所有 Pod IP,可做客户端负载均衡。
六、扩缩容行为
bash
kubectl scale sts mysql --replicas=3 # 按序创建 mysql-2
kubectl scale sts mysql --replicas=1 # 从最大序号开始删:mysql-2 → mysql-11
2
2
注意
缩容不会自动删除对应 PVC(这是设计上的保护)。残留的 PVC 会持续占费和占空间,需人工确认后 kubectl delete pvc data-mysql-2。
七、更新策略
yaml
spec:
updateStrategy:
type: OnDelete # 默认 RollingUpdate;OnDelete 表示手动删 Pod 才更新
rollingUpdate:
partition: 1 # 只有序号 >= 1 的 Pod 会被更新,用于灰度1
2
3
4
5
2
3
4
5
八、它做不到的事
StatefulSet 只提供稳定的身份和存储,不负责主从选举、故障切换、备份。真正的生产数据库应交给 Operator(见 Operator 相关章节)或云托管服务。
验证
- [ ] Pod 名称为
mysql-0、mysql-1且有序启动 - [ ] 删除 Pod 后名称不变、数据仍在
- [ ]
nslookup mysql-0.mysql能解析到具体 IP - [ ]
kubectl get pvc看到data-mysql-0、data-mysql-1
常见坑
- PVC 全部 Pending:
volumeClaimTemplates里的storageClassName不存在,或访问模式不被支持。 - Pod 卡在 Pending 不启动:StatefulSet 严格有序,前一个未 Ready 时后一个永远不会创建,先解决 mysql-0 的问题。
- 访问不到单个 Pod:忘了建 headless Service 或
serviceName写错。 - 缩容后磁盘没释放:PVC 需手动删除,见上文。
- 拿它跑主从 MySQL:StatefulSet 不会配置主从复制,需要 initContainer + 脚本或直接用 Operator。