Kubernetes 控制器:StatefulSet 详解与实战
在 Kubernetes 中,Deployment、ReplicaSet 等控制器主要用于管理无状态服务(如 Web 应用),这类服务的 Pod 实例可随意替换,IP、名称不影响服务可用性。但对于 MySQL 主从、Redis 集群、ZooKeeper 等有状态服务,Pod 实例需保持固定标识、独立存储和有序启停,StatefulSet 正是为解决这类需求而设计的核心控制器。
一、StatefulSet 核心概念与原理
1.1 有状态服务 vs 无状态服务
StatefulSet 的本质是为 “有状态服务” 提供稳定的运行环境,需先明确两类服务的核心差异:
| 维度 | 无状态服务(如 Web、Nginx) | 有状态服务(如 MySQL 主从、Redis 集群) |
|---|---|---|
| Pod 标识 | 名称、IP 随机,可随意替换 | 名称、IP 需固定(如 mysql-0、mysql-1) |
| 存储需求 | 所有 Pod 共享同一存储卷(如 NFS 共享目录) | 每个 Pod 需独立存储卷(数据不互通) |
| 启停顺序 | 无要求,可并行创建 / 删除 | 需有序(如先启动主库 mysql-0,再启动从库 mysql-1) |
| 服务发现 | 通过 Service ClusterIP 访问,无需区分实例 | 需通过固定标识访问特定实例(如主库只能是 mysql-0) |
1.2 StatefulSet 的核心组成
StatefulSet并非独立工作,需依赖两个关键组件实现“状态稳定”,三者协同构成有状态服务的管理体系:
(1)Headless Service(无头服务)
Headless Service是StatefulSet的“网络标识核心”,与普通Service的最大区别区别是不分配ClusterIP。其核心作用是:
- 为 StatefulSet 管理的每个 Pod 生成固定且可解析的 DNS 记录,确保 Pod 重建后仍能通过原标识被访问;
- 提供 Pod 实例的 “服务发现” 能力:通过解析 Service DNS,可直接获取所有 Pod 的 IP 列表(普通 Service 仅返回自身 ClusterIP)。
DNS 记录格式(K8s 集群内全局唯一):
- Pod 级 DNS:
$(Pod 名称).$(Headless Service 名称).$(命名空间).svc.cluster.local例:StatefulSet 名称为mysql,副本数 2,Headless Service 名为mysql-svc,则 Pod DNS 为mysql-0.mysql-svc.default.svc.cluster.local、mysql-1.mysql-svc.default.svc.cluster.local; - Service 级 DNS:
$(Headless Service 名称).$(命名空间).svc.cluster.local解析此 DNS 会返回所有关联 Pod 的 IP 列表,而非 ClusterIP。
(2)VolumeClaimTemplates(存储卷申请模板)
有状态服务的每个 Pod 需独立存储(如 MySQL 主从不能共用数据目录),VolumeClaimTemplates 是 StatefulSet 的 “存储保障核心”,其作用是:
- 自动为每个 Pod 生成对应的 PersistentVolumeClaim(PVC),无需手动创建;
- 每个 PVC 会自动绑定符合条件的 PersistentVolume(PV),最终实现 “一个 Pod 对应一个独立存储卷”,数据永久独立。
绑定关系:StatefulSet 副本数为 N 时,会生成 N 个 PVC,命名格式为 $(模板名)-$(StatefulSet 名)-$(Pod 序号)(如 data-mysql-0、data-mysql-1),每个 PVC 绑定独立 PV。
(3)StatefulSet 控制器本身
StatefulSet 控制器负责协调 Pod 的全生命周期管理,核心能力包括:
- 有序创建 / 删除:创建时按
0→1→2顺序启动 Pod,删除时按2→1→0顺序停止(确保依赖关系,如从库需等主库就绪); - 固定 Pod 名称:Pod 名称格式固定为
$(StatefulSet 名)-$(Pod 序号)(如web-0、web-1),重建后名称不变(区别于 Deployment 的随机名称); - 状态恢复:Pod 因故障重建时,会自动挂载原 PVC(数据不丢失),并复用原 DNS 记录(网络标识不变)。
二、StatefulSet 资源清单编写技巧
StatefulSet 的资源清单需包含 “Headless Service 定义” 和 “StatefulSet 定义” 两部分,核心字段需严格匹配(如 Service 选择器与 Pod 标签一致)。
2.1 核心字段解析
通过 kubectl explain statefulset.spec 可查看关键字段,重点关注以下必填 / 核心配置:
| 字段 | 作用说明 |
|---|---|
spec.serviceName(必填) | 关联的 Headless Service 名称(必须与 Headless Service 的 metadata.name 一致) |
spec.selector(必填) | 标签选择器,需匹配 spec.template.metadata.labels(否则创建失败) |
spec.template(必填) | Pod 模板,与 Deployment 的 Pod 模板格式一致(定义容器、端口、资源等) |
spec.replicas(可选) | 副本数,默认 1(需有序时建议不超过 10) |
spec.volumeClaimTemplates(可选) | 存储卷申请模板,为每个 Pod 生成独立 PVC(有状态服务必配) |
spec.updateStrategy(可选) | 更新策略,默认 RollingUpdate(有序更新,先更新高序号 Pod),也可设为 OnDelete(手动删除后重建) |
2.2 资源清单结构示例
StatefulSet 清单通常采用 “YAML 多文档” 格式,同时定义 Headless Service 和 StatefulSet:
# 1. 定义 Headless Service(为 Pod 提供 DNS 标识)
apiVersion: v1
kind: Service
metadata:
name: nginx-svc # Headless Service 名称,后续 StatefulSet 需引用
labels:
app: nginx
spec:
ports:
- port: 80 # 服务端口
name: web
clusterIP: None # 关键:设为 None 表示 Headless Service,不分配 ClusterIP
selector:
app: nginx # 匹配 StatefulSet 管理的 Pod 标签
---
# 2. 定义 StatefulSet(管理有状态 Pod)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-sts # StatefulSet 名称
spec:
serviceName: "nginx-svc" # 关联 Headless Service(必须与上面的 name 一致)
replicas: 2 # 副本数:2 个 Pod(nginx-sts-0、nginx-sts-1)
selector:
matchLabels:
app: nginx # 匹配 Pod 标签(与 Service selector 一致)
template:
metadata:
labels:
app: nginx # Pod 标签(与 selector 匹配)
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www # 挂载卷名称,需与 volumeClaimTemplates 中的 name 一致
mountPath: /usr/share/nginx/html # 容器内挂载路径
# 3. 存储卷申请模板:为每个 Pod 生成独立 PVC
volumeClaimTemplates:
- metadata:
name: www # 卷名称,需与容器 volumeMounts.name 一致
spec:
accessModes: ["ReadWriteOnce"] # 访问模式:单节点读写
storageClassName: "nfs-sc" # 关联存储类(需提前创建,用于动态生成 PV)
resources:
requests:
storage: 1Gi # 每个 PVC 请求 1Gi 存储
三、StatefulSet 实战:部署 Web 站点
以 “多实例 Web 站点” 为例,演示 StatefulSet 的创建、验证与核心特性,需提前准备:
- 已部署 NFS 服务(或其他分布式存储);
- 已创建存储类
nfs-sc(参考前文 StorageClass 章节,用于动态生成 PV)。
3.1 步骤 1:创建 StatefulSet 与 Headless Service
-
编写资源清单(
statefulset-web.yaml):
# Headless Service
apiVersion: v1
kind: Service
metadata:
name: web-svc
labels:
app: web
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: web
---
# StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "web-svc"
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "nfs-sc" # 引用提前创建的存储类
resources:
requests:
storage: 1Gi
2.应用清单并验证基础资源:
# 创建资源
kubectl apply -f statefulset-web.yaml
# 1. 查看 StatefulSet 状态(READY 为 2/2 表示正常)
kubectl get statefulset
# 输出:
# NAME READY AGE
# web 2/2 2m
# 2. 查看 Pod(名称有序:web-0、web-1,调度节点可能不同)
kubectl get pods -l app=web -o wide
# 输出:
# NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
# web-0 1/1 Running 0 2m 10.244.2.10 hd2 <none>
# web-1 1/1 Running 0 1m 10.244.3.15 hd3 <none>
# 3. 查看 Headless Service(CLUSTER-IP 为 None)
kubectl get svc web-svc
# 输出:
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# web-svc ClusterIP None <none> 80/TCP 2m
# 4. 查看自动生成的 PVC(每个 Pod 对应一个 PVC,命名格式:www-web-0、www-web-1)
kubectl get pvc
# 输出:
# NAME STATUS VOLUME CAPACITY ACCESS MODES
# www-web-0 Bound pvc-xxx-xxx-xxx-xxx-xxx 1Gi RWO
# www-web-1 Bound pvc-yyy-yyy-yyy-yyy-yyy 1Gi RWO
3.2 步骤 2:验证 StatefulSet 核心特性
StatefulSet 的核心价值在于 “标识固定”“存储独立”“有序管理”,通过以下验证确认特性生效:
(1)验证固定 DNS 与服务发现
Headless Service 为每个 Pod 生成固定 DNS,可在集群内通过 nslookup 解析:
# 1. 启动一个带 dnsutils 工具的临时 Pod(用于解析 DNS)
kubectl run -it --rm dns-test --image=busybox:1.35 -- sh
# 2. 在临时 Pod 内解析 Pod 级 DNS(web-0 的 DNS)
nslookup web-0.web-svc.default.svc.cluster.local
# 输出(IP 与 web-0 的实际 IP 一致):
# Name: web-0.web-svc.default.svc.cluster.local
# Address 1: 10.244.2.10 web-0
# 3. 解析 Service 级 DNS(返回所有 Pod 的 IP)
nslookup web-svc.default.svc.cluster.local
# 输出(包含 web-0 和 web-1 的 IP):
# Name: web-svc.default.svc.cluster.local
# Address 1: 10.244.2.10 web-0
# Address 2: 10.244.3.15 web-1
(2)验证独立存储
每个 Pod 挂载独立 PVC,数据不互通,可通过写入测试文件验证:
# 1. 向 web-0 的存储卷写入文件(容器内路径 /usr/share/nginx/html)
kubectl exec -it web-0 -- sh -c 'echo "web-0: StatefulSet Test" > /usr/share/nginx/html/index.html'
# 2. 向 web-1 的存储卷写入不同内容
kubectl exec -it web-1 -- sh -c 'echo "web-1: StatefulSet Test" > /usr/share/nginx/html/index.html'
# 3. 访问两个 Pod 的 IP,验证内容不同(独立存储生效)
curl 10.244.2.10 # 输出:web-0: StatefulSet Test
curl 10.244.3.15 # 输出:web-1: StatefulSet Test
(3)验证 Pod 重建后状态不变
删除 web-0,观察重建后的名称、存储和 DNS 是否保持一致:
# 1. 删除 web-0
kubectl delete pod web-0
# 2. 查看重建后的 Pod(名称仍为 web-0,IP 可能变化)
kubectl get pods -l app=web
# 输出:
# NAME READY STATUS RESTARTS AGE
# web-0 1/1 Running 0 30s # 重建完成
# web-1 1/1 Running 0 5m
# 3. 访问重建后的 web-0(数据仍存在,独立存储生效)
curl $(kubectl get pod web-0 -o jsonpath='{.status.podIP}')
# 输出:web-0: StatefulSet Test(原数据未丢失)
# 4. 解析 web-0 的 DNS(仍指向新 IP,DNS 标识不变)
kubectl exec -it dns-test -- nslookup web-0.web-svc.default.svc.cluster.local
# 输出(新 IP 已更新到 DNS):
# Name: web-0.web-svc.default.svc.cluster.local
# Address 1: 10.244.2.11 web-0 # 新 IP,但 DNS 名称不变
四、StatefulSet 日常运维:扩容、缩容与更新
StatefulSet 的运维操作需遵循 “有序” 原则,确保有状态服务的稳定性(如从库不先于主库更新)。
4.1 扩容(增加副本数)
扩容时 StatefulSet 按 0→1→2... 顺序创建新 Pod,每个新 Pod 会自动生成对应的 PVC:
# 方式 1:编辑 StatefulSet 配置,修改 replicas 为 3
kubectl edit statefulset web
# 将 spec.replicas: 2 改为 spec.replicas: 3,保存退出
# 方式 2:使用 kubectl scale 命令(更便捷)
kubectl scale statefulset web --replicas=3
# 验证扩容结果(新 Pod 名称为 web-2,有序创建)
kubectl get pods -l app=web
# 输出:
# NAME READY STATUS RESTARTS AGE
# web-0 1/1 Running 0 10m
# web-1 1/1 Running 0 15m
# web-2 1/1 Running 0 1m # 新扩容的 Pod
# 验证新 PVC(自动生成 www-web-2)
kubectl get pvc | grep web-2
# 输出:www-web-2 Bound pvc-zzz-zzz-zzz-zzz-zzz 1Gi RWO
4.2 缩容(减少副本数)
缩容与扩容逻辑相反,StatefulSet 会按 高序号→低序号(如 2→1→0)的顺序删除 Pod,确保不影响核心实例(如主库 web-0 最后删除)。需注意:默认情况下,缩容仅删除 Pod,不会自动删除对应的 PVC 和 PV(避免误删数据),需手动清理无用存储资源。
操作步骤
-
执行缩容(以从 3 个副本缩容到 2 个为例):
# 方式 1:编辑 StatefulSet,修改 replicas 为 2
kubectl edit statefulset web
# 将 spec.replicas: 3 改为 spec.replicas: 2,保存退出
# 方式 2:使用 kubectl scale 命令(更便捷)
kubectl scale statefulset web --replicas=2
2.验证缩容结果:
# 查看 Pod 状态:高序号 Pod(web-2)被删除,保留 web-0、web-1
kubectl get pods -l app=web
# 输出:
# NAME READY STATUS RESTARTS AGE
# web-0 1/1 Running 0 20m
# web-1 1/1 Running 0 18m
# 查看 PVC 状态:web-2 对应的 PVC(www-web-2)仍存在(状态为 Bound)
kubectl get pvc | grep web
# 输出:
# www-web-0 Bound pvc-xxx-xxx-xxx-xxx-xxx 1Gi RWO nfs-sc 20m
# www-web-1 Bound pvc-yyy-yyy-yyy-yyy-yyy 1Gi RWO nfs-sc 18m
# www-web-2 Bound pvc-zzz-zzz-zzz-zzz-zzz 1Gi RWO nfs-sc 5m # 缩容后仍保留
3.清理无用存储资源(可选,确认数据无需保留后执行):
# 删除缩容后无用的 PVC(www-web-2)
kubectl delete pvc www-web-2
# 若 PV 的回收策略为 Delete,PVC 删除后 PV 会自动删除;若为 Retain,需手动删除 PV
kubectl delete pv pvc-zzz-zzz-zzz-zzz-zzz # 替换为实际 PV 名称
4.3 镜像更新
StatefulSet 的镜像更新需遵循 “有序更新” 原则,默认采用 RollingUpdate(滚动更新)策略,按 高序号→低序号 顺序重建 Pod(如先更新 web-1,再更新 web-0),避免核心实例(如主库)先下线导致服务中断。也可配置为 OnDelete 策略(需手动删除 Pod 才触发更新)。
4.3.1 查看默认更新策略
# 查看 StatefulSet 的更新策略(默认 type: RollingUpdate)
kubectl get statefulset web -o jsonpath='{.spec.updateStrategy}' | jq
# 输出:
# {
# "rollingUpdate": {
# "partition": 0 # 分区更新参数,默认 0 表示全量更新
# },
# "type": "RollingUpdate"
# }
4.3.2 执行镜像更新(以更新 Nginx 镜像为例)
-
方式 1:使用 kubectl set image(推荐,便捷):
# 格式:kubectl set image statefulset/<StatefulSet 名称> <容器名>=<新镜像>
kubectl set image statefulset/web nginx=nginx:1.23 # 将 nginx 镜像从 latest 改为 1.23
2.方式 2:编辑 StatefulSet 配置:
kubectl edit statefulset web
# 将 spec.template.spec.containers[0].image 改为 nginx:1.23,保存退出
3.观察更新过程:
# 实时查看 Pod 状态:高序号 Pod 先重建,完成后再更新低序号 Pod
kubectl get pods -l app=web -w
# 输出(过程示例):
# NAME READY STATUS RESTARTS AGE
# web-0 1/1 Running 0 25m
# web-1 1/1 Running 0 23m
# web-1 1/1 Terminating 0 23m # 先终止 web-1
# web-1 0/1 Pending 0 0s # 重建 web-1
# web-1 0/1 ContainerCreating 0 0s
# web-1 1/1 Running 0 3s # web-1 重建完成
# web-0 1/1 Terminating 0 25m # 再终止 web-0
# web-0 0/1 Pending 0 0s
# web-0 0/1 ContainerCreating 0 0s
# web-0 1/1 Running 0 2s # web-0 重建完成
4.验证更新结果:
# 查看 Pod 使用的镜像是否为新镜像(nginx:1.23)
kubectl get pods -l app=web -o jsonpath='{range .items[*]}{.metadata.name}{": "}{.spec.containers[0].image}{"\n"}{end}'
# 输出:
# web-0: nginx:1.23
# web-1: nginx:1.23
4.3.3 配置 OnDelete 更新策略(手动触发更新)
若需更严格的更新控制(如数据库主从更新需先手动同步数据),可将更新策略改为 OnDelete:
# 编辑 StatefulSet,修改 updateStrategy.type 为 OnDelete
kubectl edit statefulset web
# 修改内容:
# spec:
# updateStrategy:
# type: OnDelete # 替换默认的 RollingUpdate
# 之后更新镜像后,需手动删除 Pod 才会触发重建(更新)
kubectl set image statefulset/web nginx=nginx:1.24 # 更新镜像配置
kubectl delete pod web-1 # 手动删除 web-1,触发重建(使用新镜像)
kubectl delete pod web-0 # 再删除 web-0,完成更新
4.4 版本回滚
若更新后出现问题(如镜像兼容问题),可通过 StatefulSet 的 “历史版本” 回滚到之前的稳定状态。StatefulSet 会保留 revisionHistoryLimit(默认 10)个历史版本,可通过 kubectl rollout 命令管理。
操作步骤
-
查看历史版本:
# 查看 StatefulSet 的历史版本(每个版本对应一个 ReplicaSet)
kubectl rollout history statefulset web
# 输出:
# deployments "web"
# REVISION CHANGE-CAUSE
# 1 kubectl apply --filename=statefulset-web.yaml --record=true
# 2 kubectl set image statefulset/web nginx=nginx:1.23
2.查看指定版本的详细变更(可选):
# 方式 1:回滚到上一个版本(快捷命令)
kubectl rollout undo statefulset web
# 方式 2:回滚到指定版本(如版本 1)
kubectl rollout undo statefulset web --to-revision=1
3.执行回滚(以回滚到版本 1 为例):
# 方式 1:回滚到上一个版本(快捷命令)
kubectl rollout undo statefulset web
# 方式 2:回滚到指定版本(如版本 1)
kubectl rollout undo statefulset web --to-revision=1
4.验证回滚结果:
# 查看 Pod 镜像是否回滚到旧版本(如 nginx:latest)
kubectl get pods -l app=web -o jsonpath='{range .items[*]}{.metadata.name}{": "}{.spec.containers[0].image}{"\n"}{end}'
# 输出:
# web-0: nginx:latest
# web-1: nginx:latest
五、StatefulSet 进阶:关键配置与注意事项
5.1 关键配置优化
(1)调整历史版本保留数
默认保留 10 个历史版本,若集群资源有限,可减少保留数:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
revisionHistoryLimit: 5 # 保留 5 个历史版本(建议至少保留 2-3 个)
# 其他配置...
(2)配置分区更新(金丝雀发布)
通过 spec.updateStrategy.rollingUpdate.partition 实现 “分区更新”,仅更新序号大于等于 partition 的 Pod(适用于金丝雀发布或灰度测试):
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
updateStrategy:
rollingUpdate:
partition: 1 # 仅更新序号 >=1 的 Pod(即 web-1,不更新 web-0)
type: RollingUpdate
# 其他配置...
使用场景:先更新从库(web-1)验证稳定性,确认无误后将 partition 改为 0,再更新主库(web-0)。
5.2 注意事项
- 存储必须独立:S tatefulSet 依赖
volumeClaimTemplates为每个 Pod 分配独立 PVC,不可使用共享存储(如 NFS 的ReadWriteMany模式),否则会导致数据冲突。 - 避免频繁扩缩容:扩缩容会创建 / 删除 PVC,若存储类使用动态 PV(如云厂商存储),可能产生额外费用;缩容后需手动清理无用 PVC/PV,避免资源浪费。
- 主从集群需额外配置:StatefulSet 仅保证 “标识固定” 和 “存储独立”,主从同步(如 MySQL 主从复制、Redis 主从)需通过初始化脚本(如
initContainers)或配置中心(如 ConfigMap)实现。 - 节点亲和性与污点容忍:若需将 StatefulSet Pod 调度到指定节点,可配置
nodeAffinity;若节点有污点(如控制节点的node-role.kubernetes.io/control-plane),需添加对应的容忍(tolerations)。
StatefulSet 是 Kubernetes 管理有状态服务的核心控制器,通过 Headless Service 实现固定网络标识、VolumeClaimTemplates 实现独立存储、有序生命周期管理保障服务稳定性,适用于 MySQL 主从、Redis 集群、ZooKeeper 等场景。
点赞+收藏+关注,下期我们讲讲DaemonSet控制器!不见不散
更多推荐



所有评论(0)