Kubernetes(K8s)学习笔记(第十四期):集群存储与有状态应用(下篇):StatefulSet 有状态应用管理
本笔记为 Kubernetes 系列第十四期,聚焦有状态应用的管理。涵盖:StatefulSet 核心特性、Headless Service 与固定 DNS、PVC 模板自动创建存储、Nginx/etcd/Redis/MySQL 集群部署实践。所有命令和 YAML 示例均已经过整理和注释。全文约 4700 字 ,包含 16 个 YAML 示例 、55+ 命令示例 和 12 张对比表格,是 Kubernetes 有状态应用管理的完整指南。
知识衔接:Kubernetes(K8s)学习笔记(第十三期):集群存储与有状态应用(上篇):动态卷供应------StorageClass + Provisioner
更多kubernetes系列知识:kubernetes从入门到进阶
我的主页:AOwhisky,这里有更多运维系统性知识整理和其他有趣内容,欢迎与我一起探讨学习~
--- Compiled and Authored by Whisky --- July 4 th, 2026
目录
- 引子:从"无状态"到"有状态"
- 无状态 vs 有状态应用
- StatefulSet 核心特性
- StatefulSet vs Deployment 对比
- 实验一:部署 Nginx 集群
- 实验二:部署 etcd 集群
- 实验三:部署 Redis 集群(主从 + 哨兵)
- 实验四:部署 MySQL 集群(主从同步)
- StatefulSet 更新策略
- StatefulSet Pod 管理
- 总结与知识点一览表
引子:从"无状态"到"有状态"
在前三期(ResourceQuota + LimitRange、HPA、动态卷供应)中,我们完成了集群资源管理和存储供应的基础建设。现在,我们面临一个更重要的问题:如何管理有状态应用?
在 Kubernetes 中,Deployment 是管理无状态应用的利器------所有 Pod 完全相同,可以随意扩缩容、滚动更新、重启重建,无需担心数据丢失或身份变化。但现实世界中,大多数应用都是有状态的:
- 数据库:每个节点有独立的存储,主从身份不能混淆
- 分布式中间件:每个节点有固定的网络标识,集群需要按顺序启动
- 消息队列:节点间需要稳定的通信地址
这些应用需要:
- 固定的网络标识:Pod 重启后 IP 会变化,但 hostname 必须保持不变
- 独立的持久化存储:每个 Pod 有自己的数据卷,不能共享
- 有序的启动和停止:主节点先启动,从节点后启动;缩容时从后往前删除
StatefulSet 正是为这些需求而设计的。
一句话概括本期:StatefulSet 让有状态应用在 Kubernetes 上跑得像无状态应用一样丝滑------固定身份、稳定存储、有序操作,三大特性缺一不可。
一、无状态 vs 有状态应用
1.1 核心区别
| 维度 | 无状态应用(Stateless) | 有状态应用(Stateful) |
|---|---|---|
| 核心特征 | 不保存任何请求/会话数据,每次请求独立 | 依赖持久化的会话/数据,请求间有依赖关系 |
| 数据存储 | 数据仅在请求期间存在,不落地 | 数据需持久化,依赖固定存储 |
| 实例一致性 | 所有实例完全相同,可随意扩缩容/替换 | 实例有身份(如编号),启动/扩缩容有顺序要求 |
| 网络标识 | 共享 IP/域名,无固定网络标识 | 有固定网络标识(如 Headless Service + DNS) |
| 典型应用 | Web 服务、API 网关、静态页面 | 数据库、Redis、ZooKeeper、Kafka |
1.2 为什么无状态应用优先推荐?
无状态应用是云原生的最佳实践:
- 实例可随意替换,故障自愈能力强
- 扩缩容无需等待数据迁移,秒级完成
- 滚动更新简单,无需考虑顺序
最佳实践:尽可能将应用拆分为"无状态业务层 + 有状态数据层",既提升扩展能力,又保障数据安全。
二、StatefulSet 核心特性
2.1 三大核心特性
| 特性 | 说明 |
|---|---|
| 固定身份标识 | 每个 Pod 拥有唯一的序号(从 0 开始递增),hostname 固定为「StatefulSet 名称-序号」 |
| 有序操作 | 部署/扩容按 0→N-1 顺序创建;缩容/删除按 N-1→0 逆序删除;更新按 N-1→0 逆序滚动 |
| 稳定存储 | 通过 PVC 模板为每个 Pod 自动创建独立的 PVC,Pod 重建后自动复用原 PVC |
2.2 固定网络标识
StatefulSet 必须关联一个 Headless Service(ClusterIP: None),为每个 Pod 提供固定的 DNS 解析:
text
<statefulset-name>-<ordinal>.<service-name>.<namespace>.svc.cluster.local
示例 :StatefulSet nginx,Service nginx,Namespace default
nginx-0.nginx.default.svc.cluster.localnginx-1.nginx.default.svc.cluster.localnginx-2.nginx.default.svc.cluster.local
Pod 名称的稳定来源:
StatefulSet 中 Pod 的名称由两部分组成:<statefulset-name>-<ordinal>。这个名称是稳定且可预测的,不随 Pod 重启或重建而改变。
ordinal从 0 开始递增,永不重复- Pod 重建后,
ordinal保持不变 - 通过
metadata.name字段,Pod 可以获取自己的名称
环境变量获取 Pod 名称:
yaml
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
2.3 有序操作详解
| 操作 | 顺序 | 说明 |
|---|---|---|
| 创建/扩容 | 0 → 1 → 2 ... | 前一个 Pod 必须处于 Running 和 Ready 状态,才会创建下一个 |
| 删除/缩容 | ... → 2 → 1 → 0 | 前一个 Pod 彻底删除后,才会删除下一个 |
| 滚动更新 | ... → 2 → 1 → 0 | 默认从最后一个序号开始更新(逆序) |
为什么需要有序操作?
有状态应用通常有启动依赖关系:
- 主节点(ordinal=0)必须先启动,初始化集群
- 从节点(ordinal>0)依赖主节点的网络地址加入集群
- 如果无序启动,从节点可能无法找到主节点
StatefulSet 的有序操作保证了:
- 启动顺序:确保集群先有"主人",再有"随从"
- 终止顺序:缩容时先删除"随从",再删除"主人",避免数据丢失
- 更新顺序:从最后一个节点开始更新,保持集群最小可用性
2.4 稳定存储
PVC 模板(volumeClaimTemplates):
- 为每个 Pod 自动创建独立的 PVC
- PVC 命名格式:
<template-name>-<statefulset-name>-<ordinal> - Pod 删除后,PVC 和 PV 仍然保留,数据不丢失
- Pod 重建后自动复用原 PVC
示例 :StatefulSet nginx,PVC 模板 nginx-data
nginx-data-nginx-0nginx-data-nginx-1nginx-data-nginx-2
重要 :删除 StatefulSet 时,PVC 不会自动删除。如需清理数据,需要手动删除 PVC。
三、StatefulSet vs Deployment 对比
| 控制器 | StatefulSet | Deployment |
|---|---|---|
| 适用场景 | 有状态应用(数据库、分布式集群、消息队列) | 无状态应用(Web 服务、API 接口、静态服务) |
| 网络 | Headless Service(固定 DNS 解析) | ClusterIP/LoadBalancer(共享 IP) |
| 存储 | 必须 PVC + PV(固定存储卷绑定) | 可选 PVC(无数据持久化要求) |
| 扩缩容 | 按实例编号顺序扩缩容(如从 0→1→2) | 无顺序,瞬间完成 |
| 更新策略 | 有序更新(如从最后一个实例开始) | 滚动更新/重建,无顺序 |
| 重启/重建 | 实例重启后需恢复原有数据/身份 | 实例重启后无影响 |
四、实验一:部署 Nginx 集群
需求:部署 3 节点 Nginx 集群,实现数据持久化、固定网络标识、有序部署。
4.1 环境准备
bash
root@master30:~# kubectl create ns statefulset
root@master30:~# kubectl config set-context --current --namespace statefulset
4.2 创建 Headless Service
yaml
root@master30:~# cat > nginx-service.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: statefulset
spec:
clusterIP: None
selector:
app: nginx
ports:
- port: 80
targetPort: 80
name: nginx-port
EOF
root@master30:~# kubectl apply -f nginx-service.yaml
root@master30:~# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 5s
4.3 创建 StatefulSet
yaml
root@master30:~# cat > nginx-statefulset.yaml <<'EOF'
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
namespace: statefulset
spec:
serviceName: nginx
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: nginx-data
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: nginx-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "nfs-storage" # 使用上一期部署的 NFS Provisioner
resources:
requests:
storage: 10Gi
EOF
root@master30:~# kubectl apply -f nginx-statefulset.yaml
4.4 观察 Pod 创建顺序
bash
root@master30:~# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
nginx-0 1/1 Running 0 10s
nginx-1 1/1 Running 0 30s
nginx-2 1/1 Running 0 50s
关键观察 :Pod 按 nginx-0 → nginx-1 → nginx-2 顺序创建,每个 Pod 间隔约 20 秒。
4.5 验证 PVC
bash
root@master30:~# kubectl get pvc
NAME STATUS VOLUME CAPACITY
nginx-data-nginx-0 Bound pvc-8eb06bf1-a5de-4e94-bf83-514941f73c9f 10Gi
nginx-data-nginx-1 Bound pvc-e853eccd-8060-4326-8c45-0b88c34a3d97 10Gi
nginx-data-nginx-2 Bound pvc-27d944f7-1e62-4352-9023-3874e6867ef9 10Gi
4.6 验证固定 DNS
bash
root@master30:~# kubectl run test --image=nginx -it -- bash
root@test:/# curl nginx-0.nginx
nginx-data-nginx-0
root@test:/# curl nginx-1.nginx
nginx-data-nginx-1
root@test:/# curl nginx-2.nginx
nginx-data-nginx-2
4.7 验证数据持久化
bash
# 删除 nginx-2
root@master30:~# kubectl delete pod nginx-2
# 等待重新创建
root@master30:~# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-0 1/1 Running 0 10m
nginx-1 1/1 Running 0 10m
nginx-2 1/1 Running 0 8s
# 验证数据仍然存在
root@master30:~# kubectl exec test -- curl nginx-2.nginx
nginx-data-nginx-2
4.8 实验小结
- StatefulSet 保证 Pod 按序号从 0 到 N-1 顺序创建
- 每个 Pod 获得独立的 PVC,数据持久化
- Pod 重建后 DNS 名称不变,数据不丢失
- Headless Service 为每个 Pod 提供固定 DNS 解析
4.9 清理环境
bash
root@master30:~# kubectl delete sts nginx
root@master30:~# kubectl delete svc nginx
root@master30:~# kubectl delete pvc nginx-data-nginx-{0..2}
五、实验二:部署 etcd 集群
需求:部署 3 节点 etcd 集群,实现数据持久化、固定网络标识、集群自动组网。
5.1 准备 NFS 存储
bash
root@master30:~# mkdir -m 777 -p /shares/etcd/data-{0..2}
root@master30:~# systemctl restart nfs-server.service
5.2 创建 Headless Service
yaml
root@master30:~# cat > etcd-service.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: etcd
namespace: statefulset
spec:
clusterIP: None
selector:
app: etcd
ports:
- port: 2379
name: client
- port: 2380
name: peer
EOF
root@master30:~# kubectl apply -f etcd-service.yaml
5.3 创建 PV(静态方式)
yaml
root@master30:~# cat > etcd-pv.yaml <<'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
name: etcd-pv-0
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: etcd-storage
nfs:
server: 10.1.8.30
path: /shares/etcd/data-0
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: etcd-pv-1
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: etcd-storage
nfs:
server: 10.1.8.30
path: /shares/etcd/data-1
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: etcd-pv-2
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: etcd-storage
nfs:
server: 10.1.8.30
path: /shares/etcd/data-2
EOF
root@master30:~# kubectl apply -f etcd-pv.yaml
提示:生产环境建议使用动态卷供应(StorageClass),而非手动创建 PV。
5.4 创建 StatefulSet
yaml
root@master30:~# cat > etcd-statefulset.yaml <<'EOF'
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: etcd
namespace: statefulset
spec:
serviceName: etcd
replicas: 3
selector:
matchLabels:
app: etcd
template:
metadata:
labels:
app: etcd
spec:
containers:
- name: etcd
image: registry.aliyuncs.com/google_containers/etcd:3.5.10-0
command:
- /usr/local/bin/etcd
ports:
- containerPort: 2379
name: client
- containerPort: 2380
name: peer
env:
- name: ETCD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: ETCD_DATA_DIR
value: /var/lib/etcd
- name: ETCD_INITIAL_ADVERTISE_PEER_URLS
value: "http://$(ETCD_NAME).etcd.statefulset.svc.cluster.local:2380"
- name: ETCD_LISTEN_PEER_URLS
value: "http://0.0.0.0:2380"
- name: ETCD_LISTEN_CLIENT_URLS
value: "http://0.0.0.0:2379"
- name: ETCD_ADVERTISE_CLIENT_URLS
value: "http://$(ETCD_NAME).etcd.statefulset.svc.cluster.local:2379"
- name: ETCD_INITIAL_CLUSTER
value: "etcd-0=http://etcd-0.etcd.statefulset.svc.cluster.local:2380,etcd-1=http://etcd-1.etcd.statefulset.svc.cluster.local:2380,etcd-2=http://etcd-2.etcd.statefulset.svc.cluster.local:2380"
- name: ETCD_INITIAL_CLUSTER_TOKEN
value: "etcd-token"
- name: ETCD_INITIAL_CLUSTER_STATE
value: "new"
volumeMounts:
- name: etcd-data
mountPath: /var/lib/etcd
resources:
requests:
cpu: 100m
memory: 256Mi
volumeClaimTemplates:
- metadata:
name: etcd-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "etcd-storage"
resources:
requests:
storage: 10Gi
EOF
root@master30:~# kubectl apply -f etcd-statefulset.yaml
5.5 验证 etcd 集群状态
bash
root@master30:~# kubectl exec etcd-0 -- etcdctl member list
8747632212a8465, started, etcd-2, http://etcd-2.etcd.statefulset.svc.cluster.local:2380, http://etcd-2.etcd.statefulset.svc.cluster.local:2379, false
881e7c876792f042, started, etcd-0, http://etcd-0.etcd.statefulset.svc.cluster.local:2380, http://etcd-0.etcd.statefulset.svc.cluster.local:2379, false
b6d311da39fc1138, started, etcd-1, http://etcd-1.etcd.statefulset.svc.cluster.local:2380, http://etcd-1.etcd.statefulset.svc.cluster.local:2379, false
5.6 验证数据持久化
bash
# 写入数据
root@master30:~# kubectl exec etcd-0 -- etcdctl put /users/user1/name laowang
# 删除 etcd-2
root@master30:~# kubectl delete pod etcd-2
# 等待重建后验证
root@master30:~# kubectl exec etcd-2 -- etcdctl get /users/user1/name
/users/user1/name
laowang
5.7 实验小结
- etcd 集群通过 StatefulSet 实现了固定网络标识
- 每个 etcd 节点通过
${ETCD_NAME}环境变量获取自己的名称 ETCD_INITIAL_CLUSTER使用固定的 DNS 名称,实现集群自动组网- 删除 Pod 后数据不丢失(PVC 保留)
5.8 清理环境
bash
root@master30:~# kubectl delete sts etcd
root@master30:~# kubectl delete svc etcd
root@master30:~# kubectl delete pvc etcd-data-etcd-{0..2}
root@master30:~# kubectl delete pv etcd-pv-{0..2}
六、实验三:部署 Redis 集群(主从 + 哨兵)
需求:部署 3 节点 Redis 集群(1 主 2 从),实现固定网络标识、数据持久化、哨兵高可用。
6.1 准备 NFS 存储
bash
root@master30:~# mkdir -m 777 -p /shares/redis/data-{0..2}
root@master30:~# systemctl restart nfs-server.service
6.2 创建 Headless Service
yaml
root@master30:~# cat > redis-service.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: statefulset
spec:
clusterIP: None
selector:
app: redis
ports:
- port: 6379
name: redis
- port: 26379
name: sentinel
EOF
root@master30:~# kubectl apply -f redis-service.yaml
6.3 创建 PV
yaml
root@master30:~# cat > redis-pv.yaml <<'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
name: redis-pv-0
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: redis-storage
nfs:
server: 10.1.8.30
path: /shares/redis/data-0
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: redis-pv-1
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: redis-storage
nfs:
server: 10.1.8.30
path: /shares/redis/data-1
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: redis-pv-2
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: redis-storage
nfs:
server: 10.1.8.30
path: /shares/redis/data-2
EOF
root@master30:~# kubectl apply -f redis-pv.yaml
6.4 创建 StatefulSet
yaml
root@master30:~# cat > redis-statefulset.yaml <<'EOF'
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
namespace: statefulset
spec:
serviceName: redis
replicas: 3
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7.0-alpine
ports:
- containerPort: 6379
name: redis
- containerPort: 26379
name: sentinel
command: ["/bin/sh", "-c"]
args:
- |
NODE_ID=$(hostname | awk -F'-' '{print $2}')
cat > /data/redis.conf << EOF
bind 0.0.0.0
protected-mode no
port 6379
dir /data
appendonly yes
requirepass "redis123"
masterauth "redis123"
EOF
if [ "$NODE_ID" != "0" ]; then
echo "replicaof redis-0.redis.statefulset.svc.cluster.local 6379" >> /data/redis.conf
fi
redis-server /data/redis.conf &
until nslookup redis-0.redis.statefulset.svc.cluster.local; do
echo "Waiting for DNS resolution..."
sleep 2
done
cat > /data/sentinel.conf << EOF
bind 0.0.0.0
protected-mode no
port 26379
sentinel resolve-hostnames yes
sentinel announce-hostnames yes
sentinel monitor mymaster redis-0.redis.statefulset.svc.cluster.local 6379 2
sentinel auth-pass mymaster redis123
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
EOF
redis-sentinel /data/sentinel.conf
volumeMounts:
- name: redis-data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: redis-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "redis-storage"
resources:
requests:
storage: 10Gi
EOF
root@master30:~# kubectl apply -f redis-statefulset.yaml
6.5 验证主从状态
bash
root@master30:~# kubectl exec redis-0 -- redis-cli -a redis123 info replication
Warning: Using a password with '-a' option may not be safe.
# Replication
role:master
connected_slaves:2
slave0:ip=10.224.113.181,port=6379,state=online,lag=1
slave1:ip=10.224.19.24,port=6379,state=online,lag=1
6.6 验证哨兵状态
bash
root@master30:~# kubectl exec redis-0 -- redis-cli -p 26379 sentinel master mymaster
1) "name"
2) "mymaster"
3) "ip"
4) "redis-0.redis.statefulset.svc.cluster.local"
5) "port"
6) "6379"
6.7 实验小结
- 使用
hostname | awk -F'-' '{print $2}'获取 Pod 序号,决定主从角色 - redis-0 固定为主节点,其他节点为从节点
- 哨兵配置
sentinel monitor mymaster redis-0.redis.statefulset.svc.cluster.local 6379 2监控主节点 sentinel resolve-hostnames yes启用 hostname 解析,确保哨兵能通过 DNS 名称找到主节点
6.8 清理环境
bash
root@master30:~# kubectl delete sts redis
root@master30:~# kubectl delete svc redis
root@master30:~# kubectl delete pvc redis-data-redis-{0..2}
root@master30:~# kubectl delete pv redis-pv-{0..2}
七、实验四:部署 MySQL 集群(主从同步)
需求:部署 2 节点 MySQL 集群(1 主 1 从),实现数据持久化、固定网络标识、主从自动同步。
7.1 准备 NFS 存储
bash
root@master30:~# mkdir -m 777 -p /shares/mysql/data-{0,1}
root@master30:~# systemctl restart nfs-server.service
7.2 创建 Headless Service
yaml
root@master30:~# cat > mysql-service.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: statefulset
spec:
clusterIP: None
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
name: mysql-port
EOF
root@master30:~# kubectl apply -f mysql-service.yaml
7.3 创建 PV
yaml
root@master30:~# cat > mysql-pv.yaml <<'EOF'
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv-0
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: mysql-storage
nfs:
server: 10.1.8.30
path: /shares/mysql/data-0
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv-1
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: mysql-storage
nfs:
server: 10.1.8.30
path: /shares/mysql/data-1
EOF
root@master30:~# kubectl apply -f mysql-pv.yaml
7.4 创建 StatefulSet
yaml
root@master30:~# cat > mysql-statefulset.yaml <<'EOF'
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: statefulset
spec:
serviceName: mysql
replicas: 2
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:5.7
ports:
- containerPort: 3306
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: mysql-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "mysql-storage"
resources:
requests:
storage: 10Gi
EOF
root@master30:~# kubectl apply -f mysql-statefulset.yaml
7.5 验证 Pod 域名解析
bash
root@master30:~# kubectl run test-pod --image=busybox -- sleep 3600
root@master30:~# kubectl exec test-pod -- ping -c2 mysql-1.mysql
PING mysql-1.mysql (10.224.113.148): 56 data bytes
64 bytes from 10.224.113.148: seq=0 ttl=62 time=1.586 ms
7.6 验证数据持久化
bash
# 创建数据库
root@master30:~# kubectl exec -it mysql-1 -- mysql -u root -p123456 -e 'create database wordpress;'
# 删除 mysql-1
root@master30:~# kubectl delete pod mysql-1
# 等待重建后验证数据
root@master30:~# kubectl exec -it mysql-1 -- mysql -u root -p123456 -e 'show databases;'
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| wordpress |
+--------------------+
7.7 实验小结
- MySQL 主从同步需要额外配置,本实验主要演示 StatefulSet 的数据持久化能力
- 删除 Pod 后数据不丢失,验证了 PVC 的持久化效果
- 固定 DNS 名称使主从配置更为稳定
7.8 清理环境
bash
root@master30:~# kubectl delete sts mysql
root@master30:~# kubectl delete svc mysql
root@master30:~# kubectl delete pvc mysql-data-mysql-{0..1}
root@master30:~# kubectl delete pv mysql-pv-{0..1}
八、StatefulSet 更新策略
8.1 更新策略类型
| 策略 | 行为 | 适用场景 |
|---|---|---|
| RollingUpdate(默认) | 从最后一个序号开始逆序滚动更新 | 大多数场景 |
| OnDelete | 只有手动删除 Pod 时才更新 | 需要完全控制更新节奏 |
8.2 配置更新策略
yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 2 # 只更新序号 >= 2 的 Pod
partition 参数:
- 用于金丝雀发布或灰度更新
- 序号 >= partition 的 Pod 会更新
- 序号 < partition 的 Pod 保持不变
九、StatefulSet Pod 管理
9.1 Pod 替换
bash
# 删除指定 Pod(会触发重建,且数据保留)
kubectl delete pod <pod-name>
9.2 获取 Pod 状态
bash
kubectl get sts
kubectl get pods -l app=<label>
kubectl describe sts <name>
9.3 扩缩容
bash
# 扩容
kubectl scale sts <name> --replicas=5
# 缩容(从最大序号开始删除)
kubectl scale sts <name> --replicas=2
十、总结与知识点一览表
10.1 StatefulSet 核心特性总结
| 特性 | 说明 | 关键配置 |
|---|---|---|
| 固定身份 | 每个 Pod 有唯一序号和固定 hostname | Headless Service + serviceName |
| 有序操作 | 创建/更新按顺序,缩容逆序 | 由 StatefulSet 控制器自动保证 |
| 稳定存储 | 每个 Pod 独立 PVC,删除后保留 | volumeClaimTemplates |
| 固定 DNS | Pod 可通过 <name>.<service>.<ns>.svc.cluster.local 访问 |
Headless Service |
10.2 StatefulSet vs Deployment 对比
| 控制器 | StatefulSet | Deployment |
|---|---|---|
| 适用场景 | 有状态应用 | 无状态应用 |
| 网络 | Headless Service(固定 DNS) | ClusterIP/LoadBalancer(共享 IP) |
| 存储 | 必须 PVC + PV | 可选 PVC |
| 扩缩容 | 有序(0→1→2) | 无序 |
| 更新策略 | 有序更新(逆序) | 滚动更新 |
10.3 各实验对比
| 实验 | 应用 | Pod 数量 | 主要特点 |
|---|---|---|---|
| 实验一 | Nginx | 3 | 演示基础 StatefulSet 特性 |
| 实验二 | etcd | 3 | 集群自动组网,固定 DNS |
| 实验三 | Redis | 3 | 主从 + 哨兵高可用 |
| 实验四 | MySQL | 2 | 数据持久化验证 |
10.4 常用命令速查
| 操作 | 命令 |
|---|---|
| 创建 StatefulSet | kubectl apply -f sts.yaml |
| 查看 StatefulSet | kubectl get sts |
| 查看 StatefulSet 详情 | kubectl describe sts <name> |
| 查看 Pod 顺序 | kubectl get pods -w |
| 查看 PVC | kubectl get pvc |
| 扩缩容 StatefulSet | kubectl scale sts <name> --replicas=<n> |
| 删除 StatefulSet | kubectl delete sts <name> |
10.5 常见错误排查
| 错误 | 原因 | 解决方法 |
|---|---|---|
| Pod 一直 Pending | PVC 无法绑定或节点资源不足 | 检查 PV 状态和节点资源 |
| 创建顺序异常 | Headless Service 未正确配置 | 确认 clusterIP: None |
| 数据丢失 | PVC 被误删 | 使用 Retain 回收策略 |
| DNS 解析失败 | Service 名称不匹配 | 确认 serviceName 与 Service 名称一致 |
| 主从同步失败 | 网络不通或配置错误 | 检查 Pod 间网络连通性 |
10.6 生产环境最佳实践
| 实践 | 说明 |
|---|---|
| 使用动态卷供应 | 避免手动创建 PV,使用 StorageClass 自动管理 |
| 设置资源限制 | 为有状态应用配置合理的 CPU/内存请求和限制 |
| 合理设置副本数 | 生产环境至少 3 节点(满足 etcd/Redis 多数派要求) |
| 定期备份数据 | 有状态应用的数据是关键资产,需定期备份 |
| 使用 Retain 回收策略 | 防止误删 PVC 导致数据丢失 |
| 配置 PodDisruptionBudget | 保护有状态应用不被同时驱逐 |
笔记完结说明:至此,Kubernetes 系列笔记(第一期至第十四期)已全部完成,覆盖从基础架构、集群部署、容器管理、网络策略、调度管理、资源管理、存储管理到有状态应用的全链路内容。建议读者按照"先理解架构 → 动手部署集群 → 掌握日常运维 → 进阶存储与有状态应用"的路径循序渐进地学习。如有后续进阶内容(如服务网格 Istio、Kubernetes 安全策略、GitOps 持续部署等),可根据需要继续扩展。感谢学习!
--- Compiled and Authored by Whisky --- July 4 th, 2026