十四、K8s 控制器 StatefulSet 从入门到企业实战应用
14.1 StatefulSet 控制器概述
StatefulSet是为了管理有状态服务的问题而设计的
14.1.1 有、无状态服务概念
有状态服务:StatefulSet 是有状态的集合,管理有状态的服务,它所管理的 Pod 的名称不能随意变化。数据持久化的目录也是不一样,每一个 Pod 都有自己独有的数据持久化存储目录。比如 MySQL 主从、redis 集群等。
无状态服务:RC、Deployment、DaemonSet都是管理无状态的服务,它们所管理的Pod的IP、名字,启停顺序等都是随机的。个体对整体无影响,所有pod都是共用一个数据卷的,部署的tomcat就是无状态的服务,tomcat被删除,在启动一个新的tomcat,加入到集群即可,跟tomcat的名字无关。
14.2 St atefulSet 资源清单文件编写
14.2.1 statefulset.spec字段
bash
[root@k8s-master1 ~]# kubectl explain statefulset.spec
minReadySeconds <integer>
# 指定新创建的 Pod 在被标记为就绪(Ready)后,还需等待多少秒才被视为"可用"。该值用于控制滚动更新或扩缩容时对服务可用性的影响。默认为 0。
ordinals <StatefulSetOrdinals>
# (Kubernetes 1.26+ 引入)用于自定义 StatefulSet Pod 的起始序号(ordinal)。默认从 0 开始,可通过此字段设置起始值(如从 10 开始),适用于需要与外部系统序号对齐的场景。
persistentVolumeClaimRetentionPolicy <StatefulSetPersistentVolumeClaimRetentionPolicy>
# 定义在 StatefulSet 被删除或缩容时,其关联的 PersistentVolumeClaim(PVC)的保留策略。可配置 whenDeleted 和 whenScaled 字段,分别控制删除 StatefulSet 或缩容时是否保留 PVC。默认行为是保留 PVC。
podManagementPolicy <string>
# Pod 管理策略,控制 Pod 的创建、删除和更新顺序。
1.OrderedReady(默认):按序创建/更新(从 0 到 N-1),按逆序删除(从 N-1 到 0),且每个 Pod 必须就绪后才继续下一个。
2.Parallel:并行创建、删除和更新所有 Pod,不等待前一个 Pod 就绪。
replicas <integer>
# 副本数
revisionHistoryLimit <integer>
# 保留的历史 ReplicaSet(或 ControllerRevision)数量,用于支持回滚操作。默认为 10。设为 0 将禁用回滚能力。
selector <LabelSelector> -required-
# 标签选择器
serviceName <string> -required-
# 用于提供 Pod 网络标识的 Headless Service 名称。StatefulSet 依赖此 Service 为每个 Pod 提供稳定的 DNS 记录(格式:<pod-name>.<service-name>.<namespace>.svc.cluster.local)。
template <PodTemplateSpec> -required-
# pod资源模版
updateStrategy <StatefulSetUpdateStrategy>
# 定义 StatefulSet 的更新策略。
volumeClaimTemplates <[]PersistentVolumeClaim>
# 存储卷申请模板列表。每个模板会为每个 Pod 自动生成一个独立的 PVC,命名格式为 <volumeClaimTemplate.name>-<statefulset.name>-<ordinal>。这些 PVC 与 Pod 生命周期解耦,即使 Pod 被删除,PVC 和其绑定的 PV 通常仍保留(除非通过 persistentVolumeClaimRetentionPolicy 显式删除)。
14.3 StatefulSet 使用案例:部署web站点
bash
# 编写一个包含service及statefulset的资源清单文件
[root@k8s-master1 statefulset]# vim sts.yaml
apiVersion: v1
kind: Service # Service
metadata:
name: nginx
labels:
app: nginx
spec:
clusterIP: None
ports:
- port: 80
name: web
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet # StatefulSet
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels:
app: nginx
serviceName: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: docker.io/library/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" # 指定从哪个存储类申请 pv资源
resources:
requests:
storage: 1Gi # 需要1Gi的 pvc,会自动跟符合条件的 pv进行绑定
[root@k8s-master1 statefulset]# kubectl apply -f sts.yaml
# 查看statefulset资源
[root@k8s-master1 statefulset]# kubectl get sts
web 2/2 5s
# 查看headless service(无头服务)
[root@k8s-master1 statefulset]# kubectl get svc | grep nginx
nginx ClusterIP None <none> 80/TCP 9s
# 查看pod资源:创建出来是有序的资源
[root@k8s-master1 statefulset]# kubectl get pods | grep web
web-0 1/1 Running 0 21s
web-1 1/1 Running 0 19s
# 查看pvc资源
[root@k8s-master1 sc]# kubectl get pvc
www-web-0 Bound pvc-814dc3bd-d61f-401e-997e-026cb983f47a 1Gi RWO nfs
www-web-1 Bound pvc-ca801dcd-f3ca-4d52-b864-44c8af962839 1Gi RWO nfs
# 查看pv资源
[root@k8s-master1 sc]# kubectl get pv
pvc-814dc3bd-d61f-401e-997e-026cb983f47a 1Gi RWO Delete Bound default/www-web-0 nfs
pvc-ca801dcd-f3ca-4d52-b864-44c8af962839 1Gi RWO Delete Bound default/www-web-1 nfs
# 使用 kubectl run 运行一个提供 nslookup命令 的容器的,这个命令来自于 dnsutils包,通过对 pod主机名 执行nslookup,可以检查它们在集群内部的DNS地址:
[root@k8s-master1 sc]# kubectl run busybox --image docker.io/library/busybox:1.28 --image-pull-policy=IfNotPresent --restart=Never --rm -it busybox -- sh
If you don't see a command prompt, try pressing enter.
/ # nslookup 10.244.90.132
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: 10.244.90.132
Address 1: 10.244.90.132 web-0.nginx.default.svc.cluster.local
/ #
/ #
/ # nslookup 10.244.147.202
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: 10.244.147.202
Address 1: 10.244.147.202 web-1.nginx.default.svc.cluster.local
/ #
/ #
/ # nslookup web-0.nginx.default.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx.default.svc.cluster.local
Address 1: 10.244.90.132 web-0.nginx.default.svc.cluster.local
/ #
/ #
/ # nslookup web-1.nginx.default.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx.default.svc.cluster.local
Address 1: 10.244.147.202 web-1.nginx.default.svc.cluster.local

14.4 Headless Service(无头服务)总结
14.4.1 Headless Service 概述
Headless Service(无头服务) 是 Kubernetes 中一种特殊的 Service 类型,其核心特点是 不分配 ClusterIP,也不提供负载均衡功能。
关键特征
配置方式
yamlspec: clusterIP: None # 关键配置:显式设置为 None与普通 Service 的区别
特性 普通 Service Headless Service ClusterIP 分配虚拟 IP(如 10.96.123.45) 无(None) 负载均衡 自动将请求分发到后端 Pod 不提供负载均衡 DNS 解析 返回 Service 的 ClusterIP 直接返回后端 Pod 的 IP 列表 适用场景 无状态应用(Deployment) 有状态应用(StatefulSet) DNS 行为差异
- 普通 Service:
nginx.default.svc.cluster.local→ 解析为 ClusterIP- Headless Service:
nginx.default.svc.cluster.local→ 直接解析为所有匹配 Pod 的 IP 列表(如10.244.1.10, 10.244.2.15)- 对于 StatefulSet Pod:支持通过稳定 DNS 记录访问单个 Pod,例如:
web-0.nginx.default.svc.cluster.local→ 解析为 web-0 Pod 的 IPweb-1.nginx.default.svc.cluster.local→ 解析为 web-1 Pod 的 IP在 StatefulSet 中的作用
StatefulSet 依赖 Headless Service 实现:
- 稳定的网络标识 :每个 Pod 拥有固定的 DNS 名称(如
web-0.nginx)- Pod 间直接通信:应用可直接通过 Pod 的 DNS 名称互相访问,无需经过 Service 负载均衡
- 有状态应用需求:适用于数据库集群(如 MySQL MGR、MongoDB 副本集)、ZooKeeper 等需要节点身份识别的场景
配置分析
yamlapiVersion: v1 kind: Service metadata: name: nginx spec: clusterIP: None # ← 这正是 Headless Service 的标志 selector: app: nginx此配置正确创建了 Headless Service,为 StatefulSet 的每个 Pod(web-0、web-1)提供独立的 DNS 记录,满足有状态应用的网络需求。当前 StatefulSet 无法创建 Pod 的问题与 Headless Service 无关,而是由
mountPath未配置导致。Headless service不分配clusterIP,headless service可以通过解析service的DNS,返回所有Pod的dns和ip地址 (statefulSet部署的Pod才有DNS),普通的service,只能通过解析service的DNS返回service的ClusterIP。
headless service会为service分配一个域名
<service name>.$<namespace name>.svc.cluster.local
14.4.2 举例说明:Headless Service 与 Service 的区别
bash
# 通过deployment创建pod,pod前端创建一个service
[root@k8s-master1 sc]# vim deploy_svc.yaml
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: ClusterIP
ports:
- port: 80 # service的端口,暴露给k8s集群内部服务访问
protocol: TCP
targetPort: 80 # pod容器中定义的端口
selector:
run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 2
selector:
matchLabels:
run: my-nginx
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: docker.io/library/busybox:1.28
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
command: ["sleep","3600"]
[root@k8s-master1 sc]# kubectl apply -f deploy_svc.yaml
# 查看service资源
[root@k8s-master1 sc]# kubectl get svc | grep my-nginx
my-nginx ClusterIP 10.107.206.98 <none> 80/TCP 4m47s
# 查看pod资源:发现通过deploy创建出pod是随机生成的,而不是规范顺序的
[root@k8s-master1 sc]# kubectl get pods -owide | grep my-nginx
my-nginx-56946449b9-97qh2 1/1 Running 10.244.147.203 k8s-node1.kaser.org
my-nginx-56946449b9-wwdrg 1/1 Running 10.244.90.135 k8s-node2.kaser.org
14.5 StatefulSet 总结
- Statefulset 管理的 pod,pod 名字是有序的,由 statefulset 的名字-0、1、2这种格式组成
- 创建 statefulset 资源的时候,必须事先创建好一个 service
- 如果创建的 service 没有 ip,那对这个 service 做 dns解析,会找到它所关联的 pod ip
- 如果创建的 service 有 ip,那对这个 service 做 dns解析,会解析到 service 本身ip
- statefulset 管理的pod,删除pod,新创建的pod名字跟删除的pod名字是一样的
- statefulset 具有 volumeclaimtemplate 这个字段,这个是卷申请模板,会自动创建 pv
- pvc 也会自动生成,跟 pv 进行绑定
- 那如果创建的 statefulset 使用了 volumeclaimtemplate 这个字段,那创建 pod,数据目录是独享的
- ststefulset 创建的 pod 域名
- 域名组成:
pod-name.svc-name.svc-namespace.svc.cluster.local
- 域名组成:
14.6 StatefulSet 实现 pod 资源扩缩容
14.6.1 操作文件实现动态扩缩容
bash# 开启watch监控观察 watch -n1 kubectl get pods
14.6.1.1 扩容
bash
# 编辑文件
[root@k8s-master1 statefulset]# vim sts.yaml
.......
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
replicas: 4 # 2 --> 4
.......
[root@k8s-master1 statefulset]# kubectl apply -f sts.yaml
[root@k8s-master1 statefulset]# kubectl get pods | grep web
web-0 1/1 Running
web-1 1/1 Running
web-2 1/1 Running
web-3 1/1 Running

14.6.1.2 缩容
bash
# 编辑文件
[root@k8s-master1 statefulset]# vim sts.yaml
.......
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
replicas: 2 # 4 --> 2
.......
[root@k8s-master1 statefulset]# kubectl apply -f sts.yaml
[root@k8s-master1 statefulset]# kubectl get pods | grep web
web-0 1/1 Running
web-1 1/1 Running

14.6.2 编辑控制器实现动态扩缩容
14.6.2.1 扩容
bash
[root@k8s-master1 statefulset]# kubectl edit sts web
22 replicas: 5 # 2 -- 5
# 不需要更新资源文件
[root@k8s-master1 statefulset]# kubectl get pods | grep web
web-0 1/1 Running
web-1 1/1 Running
web-2 1/1 Running
web-3 1/1 Running
web-4 1/1 Running

14.6.2.2 缩容
bash
[root@k8s-master1 statefulset]# kubectl edit sts web
22 replicas: 2 # 5 -- 2
# 不需要更新资源文件
[root@k8s-master1 statefulset]# kubectl get pods | grep web
web-0 1/1 Running
web-1 1/1 Running

14.7 StatefulSet 资源更新策略
14.7.1 sts.spec.updateStrategy字段
bash
]# kubectl explain sts.spec.updateStrategy
rollingUpdate <RollingUpdateStatefulSetStrategy>
type <string>
# 更新策略类型(两种)
- `"OnDelete"`
# 手动删除时更新
- `"RollingUpdate"`
# 滚动更新
14.7.2 sts.spec.updateStrategy.rollingUpdate字段
bash
]# kubectl explain sts.spec.updateStrategy.rollingUpdate
maxUnavailable <IntOrString>
# 在更新过程中,允许同时不可用的 Pod 的最大数量(或百分比)
partition <integer>
# 用于 StatefulSet 的有序更新,指定从哪个序号开始更新 Pod
# 大于等于这个序号的pod进行更新
14.7.3 RollingUpdate滚动更新策略
bash
[root@k8s-master1 statefulset]# vim sts.yaml
......
spec:
replicas: 3
selector:
matchLabels:
app: nginx
serviceName: nginx
updateStrategy: # 策略
type: RollingUpdate # 默认就为滚动更新,可以不写
rollingUpdate: # 滚动更新
maxUnavailable: 0
partition: 1 # 从序号1之后(包括1)开始进行更新
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: docker.io/ikubernetes/myapp:v1 # 更新镜像(nginx --> myapp)
imagePullPolicy: IfNotPresent
......
[root@k8s-master1 statefulset]# kubectl apply -f sts.yaml
# 另外打开终端观察
# 发现,从序号1且包含1之后开始更新
]# watch -n1 kubectl get pods -owide \| grep web
# web-0 web-1 进行测试,查看镜像是否一样
[root@k8s-master1 statefulset]# kubectl exec -it web-0 -- /bin/bash
root@web-0:/# cd /usr/share/nginx/html/
root@web-0:/usr/share/nginx/html# ls
[root@k8s-master1 statefulset]# kubectl exec -it web-1 -- /bin/sh
/ # ls -l /usr/share/nginx/html/
total 0

14.7.4 OnDelete手动删除更新策略
bash
# 此时如果将策略改变为 `OnDelete`,会发生什么呢?
[root@k8s-master1 statefulset]# vim sts.yaml
......
spec:
replicas: 3
selector:
matchLabels:
app: nginx
serviceName: nginx
updateStrategy: # 策略
type: OnDelete
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: docker.io/library/nginx:latest # 更新镜像(myapp --> nginx)
imagePullPolicy: IfNotPresent
......
[root@k8s-master1 statefulset]# kubectl apply -f sts.yaml
# 另外打开终端观察,发现没有变化
[root@k8s-master1 statefulset]# watch -n1 kubectl get pods -owide \| grep web
# 手动删除之后,才会发现进行变化
[root@k8s-master1 statefulset]# kubectl delete pods web-0 web-1 web-2
