Kubernetes技术详解-从理论到实践-(8)-控制器-StatefulSet
1 有状态服务控制器StatefulSet
有状态服务控制器StatefulSet是Kubernetes提供的“有状态副本集控制器”,用来运行顺序启停、需要稳定身份、独立持久卷的应用(如MySQL、Kafka、ZooKeeper)。
它保证每个Pod有固定名字、固定网络标识、固定存储,并且扩容/缩容/更新都按严格顺序执行。
2 StatefulSet控制器基础
一个典型的StatefulSet控制器资源文件清单
[root@master 8-statefulset]# cat nginx-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-statefulset
labels:
app: nginx-sts
spec:
serviceName: "nginx-statefulset-svc"
selector:
matchLabels:
app: nginx-sts
replicas: 3
template:
metadata:
labels:
app: nginx-sts
spec:
containers:
- name: nginx
image: nginx:1.27.3
imagePullPolicy: IfNotPresent
volumeMounts:
- name: vct-volume
mountPath: /usr/share/nginx/html
ports:
- containerPort: 80
volumeClaimTemplates:
- metadata:
name: vct-volume
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 400Mi
storageClassName: "local-path"
这个清单文件的含义是,在集群里持续保持3个nginx:1.27.3 Pod运行,并支持滚动升级/回滚。
各字段的具体含义如下:
| 字段 | 含义 |
|---|---|
| apiVersion: apps/v1 | 使用 apps/v1 API 版本,对应StatefulSet资源。 |
| kind: StatefulSet | 资源类型是StatefulSet,用于管理有状态应用。 |
| metadata.name: nginx-statefulset | StatefulSet 对象自身的名字,生成的Pod前缀。 |
| metadata.labels.app: nginx-sts | 给StatefulSet本身打标签,便于被Service或选择器引用。 |
| spec.serviceName: “nginx-statefulset-svc” | 指定关联的Headless Service名称,为每个Pod提供稳定的DNS记录。 |
| spec.selector.matchLabels.app: nginx-sts | 只管理带有app: nginx-ss标签的 Pod。 |
| spec.replicas: 3 | 期望副本数=3,依次创建nginx-statefulset-0/1/2。 |
| spec.template | 用来生成或替换 Pod 的模板。 |
| template.metadata.labels.app: nginx-sts | 新Pod会被自动打上 app: nginx-ss 标签,与 selector 保持一致。 |
| template.spec.containers[0].name: nginx | 容器名称。 |
| template.spec.containers[0].image: nginx:1.27.3 | 镜像版本。 |
| template.spec.containers[0].imagePullPolicy: IfNotPresent | 本地已存在镜像时不重新拉取。 |
| template.spec.containers[0].ports[0].containerPort: 80 | 容器监听80端口,供后续Service或集群内访问。 |
| template.spec.containers[0].volumeMounts[0].name: vct-volume | 把名为 vct-volume 的卷挂载到容器内路径。 |
| template.spec.containers[0].volumeMounts[0].mountPath: | 卷在容器内的挂载路径,用于提供持久化网页目录。 |
| spec.volumeClaimTemplates | 为每个Pod动态生成独立的PVC(持久卷声明)模板。 |
| volumeClaimTemplates[0].metadata.name: vct-volume | 生成的PVC名字前缀,最终为vct-volume-nginx-statefulset-{0,1,2}。 |
| volumeClaimTemplates[0].spec.accessModes: [“ReadWriteOnce”] | 申请的卷访问模式为单节点读写。 |
| volumeClaimTemplates[0].spec.resources.requests.storage: 400Mi | 每个PVC申请 400 MiB 的存储空间。 |
| volumeClaimTemplates[0].spec.storageClassName: [“local-path”] | 使用local-path存储类动态创建PVC(本环境local-path是默认存储类,这句也可以不写)。 |
3 StatefulSet控制器特点
StatefulSet控制器具有有序部署与扩缩容、稳定的网络标识及持久化存储的特点。
为了便于说明StatefulSet控制器特点,我们创建一个包含3个Pod副本的Deployment资源,两者相互对比。
本节用到的资源清单文件如下:
Deployment资源清单文件
[root@master 8-statefulset]# cat nginx-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
labels:
app: nginx-dm
spec:
selector:
matchLabels:
app: nginx-dm
replicas: 3
template:
metadata:
labels:
app: nginx-dm
spec:
containers:
- name: nginx
image: nginx:1.27.3
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
StatefulSet资源清单
[root@master 8-statefulset]# cat nginx-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-statefulset
labels:
app: nginx-sts
spec:
#podManagementPolicy: Parallel
serviceName: "nginx-statefulset-svc"
selector:
matchLabels:
app: nginx-sts
replicas: 5
template:
metadata:
labels:
app: nginx-sts
spec:
containers:
- name: nginx
image: nginx:1.27.3
imagePullPolicy: IfNotPresent
volumeMounts:
- name: vct-volume
mountPath: /usr/share/nginx/html
ports:
- containerPort: 80
volumeClaimTemplates:
- metadata:
name: vct-volume
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 400Mi
storageClassName: "local-path"
3.1 有序部署与扩缩容
3.1.1 有序创建
Deployment创建副本为并行方式,前一个Pod失败并不影响后一个Pod的创建。而StatefulSet控制器创建Pod副本则是顺序进行,只有前一个Pod创建成功,后一个Pod才会被创建,更适用于有状态需要依赖启动顺序的场景。
下面通过创建Deployment和StatefulSet,理解有序创建的含义。
# 创建Deploy
[root@master 8-statefulset]# kubectl apply -f nginx-deploy.yaml
deployment.apps/nginx-deploy created
# 三个Pod同时创建
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (6h56m ago) 26h
nginx-deploy-7c7bbf4cc9-j2227 0/1 ContainerCreating 0 1s
nginx-deploy-7c7bbf4cc9-qll7j 0/1 ContainerCreating 0 1s
nginx-deploy-7c7bbf4cc9-vfgk6 0/1 ContainerCreating 0 1s
# Pod就绪,处于Running状态
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (6h56m ago) 26h
nginx-deploy-7c7bbf4cc9-j2227 1/1 Running 0 3s
nginx-deploy-7c7bbf4cc9-qll7j 1/1 Running 0 3s
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 3s
# 创建StatefulSet
[root@master 8-statefulset]# kubectl apply -f nginx-statefulset.yaml
statefulset.apps/nginx-statefulset created
# 先创建第一个Pod,其序号从0开始
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (6h56m ago) 26h
nginx-deploy-7c7bbf4cc9-j2227 1/1 Running 0 19s
nginx-deploy-7c7bbf4cc9-qll7j 1/1 Running 0 19s
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 19s
nginx-statefulset-0 0/1 ContainerCreating 0 1s
# 第一个Ready,再创建第二个Pod
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (6h56m ago) 26h
nginx-deploy-7c7bbf4cc9-j2227 1/1 Running 0 22s
nginx-deploy-7c7bbf4cc9-qll7j 1/1 Running 0 22s
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 22s
nginx-statefulset-0 1/1 Running 0 4s
nginx-statefulset-1 0/1 ContainerCreating 0 3s
# 第二个Ready,再创建第三个Pod
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (6h56m ago) 26h
nginx-deploy-7c7bbf4cc9-j2227 1/1 Running 0 22s
nginx-deploy-7c7bbf4cc9-qll7j 1/1 Running 0 22s
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 22s
nginx-statefulset-0 1/1 Running 0 4s
nginx-statefulset-1 1/1 Running 0 3s
nginx-statefulset-2 0/1 ContainerCreating 0 1s
# 所有Pod均Ready。从AGE列也可以看出,deploy的三个Pod存活时间相同,StatefulSet的三个Pod存活时间并不相同,因为它们创建时间不同。
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (6h56m ago) 26h
nginx-deploy-7c7bbf4cc9-j2227 1/1 Running 0 24s
nginx-deploy-7c7bbf4cc9-qll7j 1/1 Running 0 24s
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 24s
nginx-statefulset-0 1/1 Running 0 6s
nginx-statefulset-1 1/1 Running 0 5s
nginx-statefulset-2 1/1 Running 0 3s
3.1.2 有序扩缩容
在扩缩容时,Deployment可以并行操作多个Pod,但StatefulSet每次只操作一个Pod,并且严格按照序号顺序执行。当扩容时,从当前最大序号加1开始,顺序创建,当缩容时,从最大序号开始,逆序删除,无论是扩容还是缩容,都是前一个完成后才会进行下一个的创建或删除。
# 将Deployment的Pod扩容到6个副本
[root@master 8-statefulset]# kubectl scale --replicas=6 deploy nginx-deploy
deployment.apps/nginx-deploy scaled
# 新副本同时创建
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (7h ago) 26h
nginx-deploy-7c7bbf4cc9-bjhg2 0/1 ContainerCreating 0 4s
nginx-deploy-7c7bbf4cc9-j2227 1/1 Running 0 4m9s
nginx-deploy-7c7bbf4cc9-l456j 0/1 ContainerCreating 0 4s
nginx-deploy-7c7bbf4cc9-qll7j 1/1 Running 0 4m9s
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 4m9s
nginx-deploy-7c7bbf4cc9-vnjh7 0/1 ContainerCreating 0 4s
nginx-statefulset-0 1/1 Running 0 3m51s
nginx-statefulset-1 1/1 Running 0 3m50s
nginx-statefulset-2 1/1 Running 0 3m48s
# 将StatefulSet的Pod扩容到6个副本
[root@master 8-statefulset]# kubectl scale --replicas=6 sts nginx-statefulset
statefulset.apps/nginx-statefulset scaled
# 新副本逐个创建,4没有Ready前,不会创建5
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (7h ago) 26h
nginx-deploy-7c7bbf4cc9-bjhg2 1/1 Running 0 31s
nginx-deploy-7c7bbf4cc9-j2227 1/1 Running 0 4m36s
nginx-deploy-7c7bbf4cc9-l456j 1/1 Running 0 31s
nginx-deploy-7c7bbf4cc9-qll7j 1/1 Running 0 4m36s
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 4m36s
nginx-deploy-7c7bbf4cc9-vnjh7 1/1 Running 0 31s
nginx-statefulset-0 1/1 Running 0 4m18s
nginx-statefulset-1 1/1 Running 0 4m17s
nginx-statefulset-2 1/1 Running 0 4m15s
nginx-statefulset-3 1/1 Running 0 2s
nginx-statefulset-4 0/1 ContainerCreating 0 0s
# 全部创建完毕,状态均为Running
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (7h1m ago) 26h
nginx-deploy-7c7bbf4cc9-bjhg2 1/1 Running 0 44s
nginx-deploy-7c7bbf4cc9-j2227 1/1 Running 0 4m49s
nginx-deploy-7c7bbf4cc9-l456j 1/1 Running 0 44s
nginx-deploy-7c7bbf4cc9-qll7j 1/1 Running 0 4m49s
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 4m49s
nginx-deploy-7c7bbf4cc9-vnjh7 1/1 Running 0 44s
nginx-statefulset-0 1/1 Running 0 4m31s
nginx-statefulset-1 1/1 Running 0 4m30s
nginx-statefulset-2 1/1 Running 0 4m28s
nginx-statefulset-3 1/1 Running 0 15s
nginx-statefulset-4 1/1 Running 0 13s
nginx-statefulset-5 1/1 Running 0 12s
# 将Deployment的Pod缩容为1个
[root@master 8-statefulset]# kubectl scale --replicas=1 deploy nginx-deploy
deployment.apps/nginx-deploy scaled
# 5个Pod同时销毁
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (7h1m ago) 26h
nginx-deploy-7c7bbf4cc9-bjhg2 0/1 Terminating 0 96s
nginx-deploy-7c7bbf4cc9-j2227 0/1 Terminating 0 5m41s
nginx-deploy-7c7bbf4cc9-l456j 0/1 Terminating 0 96s
nginx-deploy-7c7bbf4cc9-qll7j 0/1 Terminating 0 5m41s
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 5m41s
nginx-deploy-7c7bbf4cc9-vnjh7 0/1 Terminating 0 96s
nginx-statefulset-0 1/1 Running 0 5m23s
nginx-statefulset-1 1/1 Running 0 5m22s
nginx-statefulset-2 1/1 Running 0 5m20s
nginx-statefulset-3 1/1 Running 0 67s
nginx-statefulset-4 1/1 Running 0 65s
nginx-statefulset-5 1/1 Running 0 64s
# 将StatefulSet的Pod缩容为1个
[root@master 8-statefulset]# kubectl scale --replicas=1 sts nginx-statefulset
statefulset.apps/nginx-statefulset scaled
# 先从最大号的Pod开始销毁,5已经销毁了,现在正在销毁4
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (7h2m ago) 26h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 6m6s
nginx-statefulset-0 1/1 Running 0 5m48s
nginx-statefulset-1 1/1 Running 0 5m47s
nginx-statefulset-2 1/1 Running 0 5m45s
nginx-statefulset-3 1/1 Running 0 92s
nginx-statefulset-4 0/1 Terminating 0 90s
# 所有Pod销毁,缩容完成
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (7h2m ago) 26h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 6m11s
nginx-statefulset-0 1/1 Running 0 5m53s
3.1.3 有序更新
像Deployment一样,StatefulSet也支持滚动更新。Deployment的滚动更新是并行的,多个Pod可以同时更新,但StatefulSet在更新时,每次只操作一个Pod。StatefulSet按照序号顺序逐个Pod滚动替换,默认从最大序号开始,每次替换一个Pod,新Pod达到Ready后才继续下一个,不并行,不跳号,序号为0的Pod永远最后一个被更新。
# 将StatefulSet的Pod副本扩容到6个
[root@master 8-statefulset]# kubectl scale --replicas=6 sts nginx-statefulset
statefulset.apps/nginx-statefulset scaled
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (7h55m ago) 27h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 59m
nginx-statefulset-0 1/1 Running 0 58m
nginx-statefulset-1 1/1 Running 0 68s
nginx-statefulset-2 1/1 Running 0 66s
nginx-statefulset-3 1/1 Running 0 64s
nginx-statefulset-4 1/1 Running 0 63s
nginx-statefulset-5 1/1 Running 0 62s
# 查看其中一个Pod当前的nginx版本,为1.27.3
[root@master 8-statefulset]# kubectl describe pod nginx-statefulset-0
Name: nginx-statefulset-0
(。。。部分内容略。。。)
Containers:
nginx:
Container ID: docker://6c68d493e17aa6cbf755826993f302f042e79a868384d64453fb8116840c8c66
Image: nginx:1.27.3
(。。。部分内容略。。。)
# 通过kubectl set images命令,将nginx版本升级到1.27.5
# (如果没有1.27.5这个镜像,且仅为了实验,可以使用docker tag nginx:1.27.3 nginx:1.27.5这种新建标签方式快速生成一个新版本镜像)
[root@master 8-statefulset]# kubectl set image sts nginx-statefulset nginx=nginx:1.27.5
statefulset.apps/nginx-statefulset image updated
# 可以看到Pod正在更新,nginx-statefulset-5这个Pod已经更新完成,状态为Running了,现正在更新4号。从序号最大的开始更新,序号小的后更新。
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (7h56m ago) 27h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 60m
nginx-statefulset-0 1/1 Running 0 59m
nginx-statefulset-1 1/1 Running 0 2m7s
nginx-statefulset-2 1/1 Running 0 2m5s
nginx-statefulset-3 1/1 Running 0 2m3s
nginx-statefulset-4 0/1 Terminating 0 2m2s
nginx-statefulset-5 1/1 Running 0 1s
# 2号Pod更新
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (7h56m ago) 27h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 60m
nginx-statefulset-0 1/1 Running 0 59m
nginx-statefulset-1 1/1 Running 0 2m12s
nginx-statefulset-2 0/1 Terminating 0 2m10s
nginx-statefulset-3 1/1 Running 0 2s
nginx-statefulset-4 1/1 Running 0 4s
nginx-statefulset-5 1/1 Running 0 6s
# 1号Pod更新
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (7h56m ago) 27h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 60m
nginx-statefulset-0 1/1 Running 0 59m
nginx-statefulset-1 0/1 ContainerCreating 0 2s
nginx-statefulset-2 1/1 Running 0 4s
nginx-statefulset-3 1/1 Running 0 6s
nginx-statefulset-4 1/1 Running 0 8s
nginx-statefulset-5 1/1 Running 0 10s
# 全部Pod更新完毕
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (7h56m ago) 27h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 60m
nginx-statefulset-0 1/1 Running 0 1s
nginx-statefulset-1 1/1 Running 0 4s
nginx-statefulset-2 1/1 Running 0 6s
nginx-statefulset-3 1/1 Running 0 8s
nginx-statefulset-4 1/1 Running 0 10s
nginx-statefulset-5 1/1 Running 0 12s
# 查看其中一个Pod,镜像已经变更为新的1.27.5
[root@master 8-statefulset]# kubectl describe pod nginx-statefulset-0
Name: nginx-statefulset-0
(。。。部分内容略。。。)
Containers:
nginx:
Container ID: docker://3faa50d9355b443aba4d64354273082d3b3f341dda57759aaa52b59c3d4bcf9e
Image: nginx:1.27.5
(。。。部分内容略。。。)
# 随机查看其他Pod,镜像已经变更为新的1.27.5
[root@master 8-statefulset]# kubectl describe pod nginx-statefulset-2
Name: nginx-statefulset-2
(。。。部分内容略。。。)
Containers:
nginx:
Container ID: docker://28738e4870cc0e11114caeca9015c2e96938b8deb042ec89e7ce5a3d50ebf91b
Image: nginx:1.27.5
(。。。部分内容略。。。)
[root@master 8-statefulset]#
3.1.4 灰度发布
StatefulSet还支持灰度发布,在滚动更新时,只让高序号的Pod先升级到新版本,低序号的仍保持旧版本,从而把风险限制在部分实例。核心字段是spec.updateStrategy.rollingUpdate.partition,例如,当partition为2时,只有序号大于等于2的Pod才会更新,0和1序号的Pod不更新。该字段的默认值为0。
# 删除之前创建的StatefulSet
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (8h ago) 27h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 92m
[root@master 8-statefulset]# kubectl get sts
No resources found in default namespace.
# 重新创建StatefulSet
[root@master 8-statefulset]# kubectl apply -f nginx-statefulset.yaml
statefulset.apps/nginx-statefulset created
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (8h ago) 27h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 92m
nginx-statefulset-0 1/1 Running 0 7s
nginx-statefulset-1 1/1 Running 0 6s
nginx-statefulset-2 1/1 Running 0 5s
# 为了更直观演示,我们这里将Pod调整为6个
[root@master 8-statefulset]# kubectl scale --replicas=6 sts nginx-statefulset
statefulset.apps/nginx-statefulset scaled
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (8h ago) 27h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 93m
nginx-statefulset-0 1/1 Running 0 27s
nginx-statefulset-1 1/1 Running 0 26s
nginx-statefulset-2 1/1 Running 0 25s
nginx-statefulset-3 1/1 Running 0 5s
nginx-statefulset-4 1/1 Running 0 4s
nginx-statefulset-5 1/1 Running 0 3s
# 设置 partition=3(只升级序号 ≥3 的 Pod)。此时由于配置没有任何变动,升级还没开始。
[root@master 8-statefulset]# kubectl patch sts nginx-statefulset -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'
statefulset.apps/nginx-statefulset patched
# 把镜像配置更新到1.27.5,此时3、4、5开始更新
[root@master 8-statefulset]# kubectl set image sts nginx-statefulset nginx=nginx:1.27.5
statefulset.apps/nginx-statefulset image updated
# 使用kubectl rollout stats命令可以查看升级过程,目前的进度是三个Pod已经更新到1.27.5版本nginx了
[root@master 8-statefulset]# kubectl rollout status sts/nginx-statefulset
partitioned roll out complete: 3 new pods have been updated...
# 通过Pod的AGE列也可以看出3、4和5是新创建的
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (8h ago) 27h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 94m
nginx-statefulset-0 1/1 Running 0 2m3s
nginx-statefulset-1 1/1 Running 0 2m2s
nginx-statefulset-2 1/1 Running 0 2m1s
nginx-statefulset-3 1/1 Running 0 23s
nginx-statefulset-4 1/1 Running 0 27s
nginx-statefulset-5 1/1 Running 0 29s
# 查看3号Pod的镜像版本,可以看到已经更新到1.27.5
[root@master 8-statefulset]# kubectl describe pod nginx-statefulset-3
Name: nginx-statefulset-3
(。。。部分内容略。。。)
Containers:
nginx:
Container ID: docker://69c6b51ecbd82304aecb055944c80eae6a4c7f5aef5c23a4fdd25d22b2f3d4ea
Image: nginx:1.27.5
(。。。部分内容略。。。)
# 2号Pod仍为1.27.3。因为partition=3,所以只有大于等于3的Pod才会更新
[root@master 8-statefulset]# kubectl describe pod nginx-statefulset-2
Name: nginx-statefulset-2
(。。。部分内容略。。。)
Containers:
nginx:
Container ID: docker://03e04b5bf885fd4b38e6a9f4fe8454472060a5b0dd8da99db0a557e39d52cb1c
Image: nginx:1.27.3
(。。。部分内容略。。。)
# 继续调整partition值,改为1之后,1号和2号Pod将自动更新,不需要再次执行kubectl set image sts nginx-statefulset nginx=nginx:1.27.5
[root@master 8-statefulset]# kubectl patch sts nginx-statefulset -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":1}}}}'
statefulset.apps/nginx-statefulset patched
# 查看更新状态,5个Pod已经更新
[root@master 8-statefulset]# kubectl rollout status sts/nginx-statefulset
partitioned roll out complete: 5 new pods have been updated...
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (8h ago) 27h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 96m
nginx-statefulset-0 1/1 Running 0 3m42s
nginx-statefulset-1 1/1 Running 0 11s
nginx-statefulset-2 1/1 Running 0 14s
nginx-statefulset-3 1/1 Running 0 2m2s
nginx-statefulset-4 1/1 Running 0 2m6s
nginx-statefulset-5 1/1 Running 0 2m8s
[root@master 8-statefulset]#
# 查看已经更新过的一个Pod,镜像已经为1.27.5
[root@master 8-statefulset]# kubectl describe pod nginx-statefulset-1
Name: nginx-statefulset-1
(。。。部分内容略。。。)
Containers:
nginx:
Container ID: docker://8fa72c553914b4150d7493e557f2f560142cf685901d4b6ff741fbf4a30cabf8
Image: nginx:1.27.5
(。。。部分内容略。。。)
# 只有0号Pod没有更新,仍为1.27.3的旧镜像
[root@master 8-statefulset]# kubectl describe pod nginx-statefulset-0
Name: nginx-statefulset-0
(。。。部分内容略。。。)
Containers:
nginx:
Container ID: docker://ba34464312b24a8917f144eab4433488d09f2630a6815d54f0a94279f6d059f5
Image: nginx:1.27.3
(。。。部分内容略。。。)
[root@master 8-statefulset]#
3.1.5 Pod管理策略:有序和并行
我们知道Pod的创建、删除、滚动和更新都严格按照顺序进行,但这种方式有时候可能会导致效率变低,特别是对于Pod间并无前后依赖关系的场景。
StatefulSet有一个字段spec.podManagementPolicy,可以控制Pod是有序操作还是并行操作,该字段有OrderedReady和Parallel两个值,默认取值为OrderedReady。
如果取值为OrderedReady,则表示有序操作Pod,创建、删除、滚动更新都严格按照序号0->1->2…的顺序进行,且只有前面一个Pod Ready后才继续下一个。
如果取值为Parallel,Pod操作将会变成并行方式,所有Pod并行创建、删除及更新,不检查上一个Pod是否Ready,但最终仍会生成连续序号的Pod,不会出现跳号的情况。
[root@master 8-statefulset]# cat nginx-parallel-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-parallel-statefulset
labels:
app: nginx-p-sts
spec:
podManagementPolicy: Parallel # 默认为OrderedReady,顺序创建或更新Pod,改为Parallel后,Pod将并行创建或更新
serviceName: "nginx-sts-headless-svc"
selector:
matchLabels:
app: nginx-p-sts
replicas: 3
template:
metadata:
labels:
app: nginx-p-sts
spec:
containers:
- name: nginx
image: nginx:1.27.3
imagePullPolicy: IfNotPresent
volumeMounts:
- name: vct-volume
mountPath: /usr/share/nginx/html
ports:
- containerPort: 80
volumeClaimTemplates:
- metadata:
name: vct-volume
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 400Mi
storageClassName: "local-path"
# 删除之前创建的sts
[root@master 8-statefulset]# kubectl get sts
No resources found in default namespace.
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (9h ago) 28h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 140m
# StatefulSet使用Parallel方式创建Pod
[root@master 8-statefulset]# kubectl apply -f nginx-parallel-statefulset.yaml
statefulset.apps/nginx-parallel-statefulset created
# 可以看到和Deployment相似,三个Pod同时创建,但序号仍是连续且不重复的
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (9h ago) 28h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 141m
nginx-parallel-statefulset-0 0/1 ContainerCreating 0 1s
nginx-parallel-statefulset-1 0/1 ContainerCreating 0 1s
nginx-parallel-statefulset-2 0/1 ContainerCreating 0 1s
# 三个Pod创建完成
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (9h ago) 28h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 141m
nginx-parallel-statefulset-0 1/1 Running 0 4s
nginx-parallel-statefulset-1 1/1 Running 0 4s
nginx-parallel-statefulset-2 1/1 Running 0 4s
# 查看StatefulSet的podManagementPolicy字段,其他字段也可以通过本方式查看
[root@master 8-statefulset]# kubectl get sts nginx-parallel-statefulset -o jsonpath='{.spec.podManagementPolicy}'
# 一次查看所有字段,grep查找StatefulSet的podManagementPolicy
[root@master 8-statefulset]# kubectl get sts nginx-parallel-statefulset -o yaml | grep podManagementPolicy
{"apiVersion":"apps/v1","kind":"StatefulSet",(...略...),"spec":{"podManagementPolicy":"Parallel",(...略...)}}
podManagementPolicy: Parallel
# 删除StatefulSet,可以使用创建资源清单的文件删除资源对象
[root@master 8-statefulset]# kubectl delete -f nginx-parallel-statefulset.yaml
statefulset.apps "nginx-parallel-statefulset" deleted
# 可以看到,Parallel方式下,Pod同时删除
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (9h ago) 28h
nginx-deploy-7c7bbf4cc9-vfgk6 1/1 Running 0 145m
nginx-parallel-statefulset-0 0/1 Terminating 0 37s
nginx-parallel-statefulset-1 0/1 Terminating 0 37s
nginx-parallel-statefulset-2 0/1 Terminating 0 37s
3.2 稳定的网络标识
StatefulSet控制器稳定的网络标识包括名称稳定、网络身份稳定和索引稳定。
StatefulSet控制器产生的Pod拥有稳定的Pod名称,借助Headless Service还可以拥有稳定的网络身份(即Pod级DNS域名),StatefulSet扩缩容时Pod仍可保持稳定的序号索引。
3.2.1 稳定的Pod名称
可以看到Deploy创建的Pod,名字包含随机字符,重启或被调度到其他节点,名字会发生变化,但StatefulSet创建的Pod,是以<statefulset-name>-0/1/2...命名,重启和调度后名字也不会发生变化。
# 创建deploy
[root@master 8-statefulset]# kubectl apply -f nginx-deploy.yaml
deployment.apps/nginx-deploy created
# 创建statefulset
[root@master 8-statefulset]# kubectl apply -f nginx-statefulset.yaml
statefulset.apps/nginx-statefulset created
# 查看deploy
[root@master 8-statefulset]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nfs-client-provisioner 1/1 1 1 15h
nginx-deploy 3/3 3 3 14s
# 查看statefulset控制器
[root@master 8-statefulset]# kubectl get statefulset
NAME READY AGE
nginx-statefulset 3/3 16s
# sts是statefulset的缩写
[root@master 8-statefulset]# kubectl get sts
NAME READY AGE
nginx-statefulset 3/3 18s
# 可以看到创建的Pod,前三个是deploy创建的,Pod名不固定,后三个是Statefulset创建的,以0,1,2..结尾
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 0 15h
nginx-deploy-7c7bbf4cc9-7q8qs 1/1 Running 0 23s
nginx-deploy-7c7bbf4cc9-njrg2 1/1 Running 0 23s
nginx-deploy-7c7bbf4cc9-nl9wp 1/1 Running 0 23s
nginx-statefulset-0 1/1 Running 0 16s
nginx-statefulset-1 1/1 Running 0 10s
nginx-statefulset-2 1/1 Running 0 5s
3.2.2 稳定的网络身份
StatefulSet控制器稳定的网络身份是值每个Pod有独立的DNS域名,在集群内部可以通过该域名直接访问指定的Pod。
由于StatefulSet控制器的稳定网络身份离不开Headless Service,所以我们本节先介绍这种特殊的服务类型——Headless Service,然后再为Deployment和StatefulSet分别创建两个Headless Service,通过对比解释Pod级DNS域名这一概念。
3.2.2.1 Headless Service
3.2.2.1.1 什么是Headless Service
Headless Service,也称为无头服务,或无Cluster IP服务,它是一种特殊的Service资源,体现在资源清单yaml上就是把Service的clusterIP字段设置为None,它的特点是去掉了kube-proxy的虚拟IP(VIP)和负载均衡,只留下DNS里的一组Pod ID或主机名,让客户端自己决定连接谁。
一般来讲,Headless Service和StatefulSet经常搭配使用,因为kubedns/coreDNS会在Headless Service创建时,为StatefulSet这种Pod名字稳定的控制器额外生成一个Pod级域名,相比Deployment,由于Pod名是随机字符串,因此只有服务级域名。
如果使用域名方式访问,总结下来就是:
- 如果没有创建service,就只能通过下面这种方式访问pod:
<pod-ip-with-dashes>.<namespace>.pod.cluster.local(Pod DNS A记录,即使没有Service与其绑定也会产生这条A记录) - 创建service后,可以用如下两种方式访问pod:
<pod-ip-with-dashes>.<namespace>.pod.cluster.local(Pod DNS A记录,即使没有Service与其绑定也会产生这条A记录)<service-name>.<namespace>.svc.cluster.local(服务级域名) - 如果创建的是Headless Service,并且Pod是StatefulSet控制器生成的Pod,可以用如下三种方式访问Pod:
<pod-ip-with-dashes>.<namespace>.pod.cluster.local(Pod DNS A记录,即使没有Service与其绑定也会产生这条A记录)<service-name>.<namespace>.svc.cluster.local(服务级域名)<pod-name><service-name>.<namespace>.svc.cluster.local(Pod级域名)
Pod DNS A记录最不稳定,因为其包含了IP地址,而Pod的IP地址随时会发生变化。
服务级域名相对稳定,因为Service名字不太会发生变化,但它无法区分其代理的Pod,访问该域名时,kube-proxy负载均衡可能会将流量引入任何一个Pod。
Pod级域名不但稳定,而且精准,通过指定Pod名,可以绕过负载均衡,直接与指定Pod连接,这在有状态单节点精准访问时非常有用。
3.2.2.1.2 一个典型的Headless Service资源清单
Headless Service不会端口映射,它只是把请求直接打到Pod IP+targetPort上,因此这里spec.ports.port和spec.ports.targetPort两者值必须相同。
[root@master 8-statefulset]# cat nginx-headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-headless-svc
spec:
clusterIP: None # 这里ip为None
selector:
app: nginx-dm
ports:
- port: 80 # port必须和下面的targetPort值相同
targetPort: 80
3.2.2.1.3 Headless Service和ClusterIP Service对比
Headless Service和ClusterIP Service相似度很高,对于一个ClusterIP Service,将其spec.clusterIP字段设置为None,即成为Headless Service(但要注意,Headless Service的port与targetPort值必须相同)。
我们这里创建一个ClusterIP Service,将其与Headless Service进行比较。
# Headless Service资源清单
[root@master 8-statefulset]# cat nginx-headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-headless-svc
spec:
clusterIP: None # 这里ip为None
selector:
app: nginx-dm
ports:
- port: 80 # port必须和下面的targetPort值相同
targetPort: 80
# ClusterIP Service资源清单
[root@master 8-statefulset]# cat nginx-clusterip-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-clusterip-svc
spec:
type: ClusterIP
selector:
app: nginx-dm
ports:
- port: 8080
targetPort: 80
# 创建Headless Service
[root@master 8-statefulset]# kubectl apply -f nginx-headless-svc.yaml
service/nginx-headless-svc configured
# 创建ClusterIP Service
[root@master 8-statefulset]# kubectl apply -f nginx-clusterip-svc.yaml
service/nginx-clusterip-svc configured
#Headless Service创建成功,它的类型仍是ClusterIP类型,但没有CLUSTER-IP,无法通过ip地址访问。由于service都有dns,因此可以使用dns访问。
[root@master 8-statefulset]# kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 13h <none>
nginx-clusterip-svc ClusterIP 10.43.142.182 <none> 8080/TCP 20s app=nginx-dm
nginx-headless-svc ClusterIP None <none> 80/TCP 18s app=nginx-dm
3.2.2.1.4 Headless Service应用场景
Headless Service的核心价值是把后端Pod的IP列表(或外部域名)直接暴露给客户端,让调用方自己决定如何连接,从而跳过kube-proxy的VIP、负载均衡。典型场景如下:
| 场景 | 说明 | yaml片段 |
|---|---|---|
| StatefulSet 网络标识 | 每个Pod有自己的独立固定子域名 | clusterIP: None + StatefulSet |
| 客户端侧负载均衡 | gRPC/Thrift自己做连接池/权重/故障转移 | clusterIP: None + StatefulSet |
| 分布式存储/批处理 | Spark、Ceph、MinIO需要全部Pod IP列表 | clusterIP: None + StatefulSet |
| 灰度/金丝雀 | 灰度版本独立DNS,流量直连灰度Pod | Headless + 手动创建Endpoints |
| 指向外部域名 | 把集群外MySQL、Redis等映射进来 | type: ExternalName |
| 无代理服务网格 | Istio sidecar直接发现Pod,避免双层代理 | Headless + sidecar |
3.2.2.2 Pod级域名示例
接下来,我们为前面创建的Deployment和StatefulSet分别关联一个Headless Service。
在为StatefulSet关联Headless Service后,kube-dns/CoreDNS会为StatefulSet产生一个稳定的Pod级域名,但为Deployment关联Headless Service并不会产生Pod级域名,只会像关联诸如ClusterIP Service那样,产生一个Service级域名,这里为Deployment关联Headless Service只是为了与StatefulSet对比。
(1) 创建资源清单文件
[root@master 8-statefulset]# cat nginx-deploy-headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-deploy-headless-svc
spec:
clusterIP: None
selector:
app: nginx-dm
ports:
- port: 80
targetPort: 80
[root@master 8-statefulset]# cat nginx-sts-headless-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-sts-headless-svc
spec:
clusterIP: None
selector:
app: nginx-sts
ports:
- port: 80
targetPort: 80
(2) 创建两个Service
[root@master 8-statefulset]# kubectl apply -f nginx-deploy-headless-svc.yaml
service/nginx-deploy-headless-svc configured
[root@master 8-statefulset]# kubectl apply -f nginx-sts-headless-svc.yaml
service/nginx-sts-headless-svc configured
# 创建成功,其CLUSTER-IP字段为None
[root@master 8-statefulset]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 3d15h
nginx-deploy-headless-svc ClusterIP None <none> 80/TCP 23s
nginx-sts-headless-svc ClusterIP None <none> 80/TCP 26s
# 查看两个Service的Endpoints
[root@master 8-statefulset]# kubectl get ep
NAME ENDPOINTS AGE
kubernetes 192.168.88.132:6443 3d15h
nfs-client-provisioner <none> 16h
nginx-deploy-headless-svc 10.42.0.163:80,10.42.0.164:80,10.42.0.165:80 33s
nginx-sts-headless-svc 10.42.0.167:80,10.42.0.169:80,10.42.0.171:80 36s
[root@master 8-statefulset]#
(3) DNS方式访问Service
Deployment创建的Pod支持两种DNS方式访问,StatefulSet创建的Pod支持三种DNS方式访问。
# 查看Service名字
[root@master 8-statefulset]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 3d15h
nginx-deploy-headless-svc ClusterIP None <none> 80/TCP 43s
nginx-sts-headless-svc ClusterIP None <none> 80/TCP 46s
# 查看Pod,主要是查看Pod名字和IP地址
[root@master 8-statefulset]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 0 16h 10.42.0.99 master <none> <none>
nginx-deploy-7c7bbf4cc9-7q8qs 1/1 Running 0 78s 10.42.0.164 master <none> <none>
nginx-deploy-7c7bbf4cc9-njrg2 1/1 Running 0 78s 10.42.0.165 master <none> <none>
nginx-deploy-7c7bbf4cc9-nl9wp 1/1 Running 0 78s 10.42.0.163 master <none> <none>
nginx-statefulset-0 1/1 Running 0 83s 10.42.0.167 master <none> <none>
nginx-statefulset-1 1/1 Running 0 88s 10.42.0.169 master <none> <none>
nginx-statefulset-2 1/1 Running 0 93s 10.42.0.171 master <none> <none>
# 进入其中一个Pod。
[root@master 8-statefulset]# kubectl exec -it nginx-deploy-7c7bbf4cc9-7q8qs -- /bin/bash
# 通过Pod DNS A记录访问一个Deployment创建的Pod。
root@nginx-deploy-7c7bbf4cc9-7q8qs:/# curl 10-42-0-163.default.pod.cluster.local
...(略)<h1>Welcome to nginx!</h1>(略)...
# 通过服务级域名访问Deployment创建的Pod。
root@nginx-deploy-7c7bbf4cc9-7q8qs:/# curl nginx-deploy-headless-svc.default.svc.cluster.local
...(略)<h1>Welcome to nginx!</h1>(略)...
# 在本Pod通过服务级短域名访问。
root@nginx-deploy-7c7bbf4cc9-7q8qs:/# curl nginx-deploy-headless-svc
...(略)<h1>Welcome to nginx!</h1>(略)...
# 通过Pod级域名访问,由于是Deployment创建的Pod,因此没有这个域名,curl返回失败。
root@nginx-deploy-7c7bbf4cc9-7q8qs:/# curl nginx-deploy-7c7bbf4cc9-njrg2.nginx-deploy-headless-svc.default.svc.cluster.local
curl: (6) Could not resolve host: nginx-deploy-7c7bbf4cc9-njrg2.nginx-deploy-headless-svc.default.svc.cluster.local
# 通过Pod DNS A记录访问一个StatefulSet创建的Pod。
root@nginx-deploy-7c7bbf4cc9-7q8qs:/# curl 10-42-0-167.default.pod.cluster.local
...(略)<center><h1>403 Forbidden</h1></center>(略)...
# 通过服务级域名访问StatefulSet创建的Pod,nginx服务可以成功访问,由于web根目录无index.html文件,返回403错误。
root@nginx-deploy-7c7bbf4cc9-7q8qs:/# curl nginx-sts-headless-svc.default.svc.cluster.local
...(略)<center><h1>403 Forbidden</h1></center>(略)...
# 在本Pod通过服务级短域名访问,nginx服务可以成功访问,由于web根目录无index.html文件,返回403错误。
root@nginx-deploy-7c7bbf4cc9-7q8qs:/# curl nginx-sts-headless-svc
...(略)<center><h1>403 Forbidden</h1></center>(略)...
# 通过Pod级域名访问,nginx服务可以成功访问,由于web根目录无index.html文件,返回403错误。
root@nginx-deploy-7c7bbf4cc9-7q8qs:/# curl nginx-statefulset-1.nginx-sts-headless-svc.default.svc.cluster.local
...(略)<center><h1>403 Forbidden</h1></center>(略)...
# 通过Pod级域名访问一个不存在的Pod,curl返回失败。
root@nginx-deploy-7c7bbf4cc9-7q8qs:/# curl nginx-statefulset-4.nginx-sts-headless-svc.default.svc.cluster.local
curl: (6) Could not resolve host: nginx-statefulset-4.nginx-sts-headless-svc.default.svc.cluster.local
root@nginx-deploy-7c7bbf4cc9-7q8qs:/#
关于短域名
相比nginx-deploy-headless-svc.default.svc.cluster.local,nginx-deploy-headless-svc是一个短域名。在Pod内,/etc/resolv.conf自带search后缀,在搜索时会自动补全,因此在Pod内,这两条都能通,但是,在宿主机或其他调试容器中,没有搜索域,使用短域名可能无法访问,因此需要使用完整的长域名访问Pod。为避免出错,尽量选择使用长域名。
综上,StatefulSet + Headless Service可以为每个Pod提供一个稳定访问的Pod级域名,这就是StatefulSet控制器稳定网络标识的特点。
3.2.3 稳定的序号索引
Pod扩缩容,Pod名字也不会发生变化,并且序号仍是稳定的。如果缩容到1个,再扩容到4个,其序号为0/1/2/3。
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 0 17h
nginx-deploy-7c7bbf4cc9-7q8qs 1/1 Running 0 123m
nginx-deploy-7c7bbf4cc9-njrg2 1/1 Running 0 123m
nginx-deploy-7c7bbf4cc9-nl9wp 1/1 Running 0 123m
nginx-statefulset-0 1/1 Running 0 123m
nginx-statefulset-1 1/1 Running 0 123m
nginx-statefulset-2 1/1 Running 0 122m
Pod缩容到1个
[root@master 8-statefulset]# kubectl scale --replicas=1 sts nginx-statefulset
statefulset.apps/nginx-statefulset scaled
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 0 17h
nginx-deploy-7c7bbf4cc9-7q8qs 1/1 Running 0 123m
nginx-deploy-7c7bbf4cc9-njrg2 1/1 Running 0 123m
nginx-deploy-7c7bbf4cc9-nl9wp 1/1 Running 0 123m
nginx-statefulset-0 1/1 Running 0 123m
Pod扩容到4个
[root@master 8-statefulset]# kubectl scale --replicas=4 sts nginx-statefulset
statefulset.apps/nginx-statefulset scaled
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 0 17h
nginx-deploy-7c7bbf4cc9-7q8qs 1/1 Running 0 123m
nginx-deploy-7c7bbf4cc9-njrg2 1/1 Running 0 123m
nginx-deploy-7c7bbf4cc9-nl9wp 1/1 Running 0 123m
nginx-statefulset-0 1/1 Running 0 123m
nginx-statefulset-1 1/1 Running 0 2s
nginx-statefulset-2 1/1 Running 0 1s
nginx-statefulset-3 0/1 Pending 0 0s
3.3 持久化存储
StatefulSet能够管理有状态应用程序的持久性存储。每个Pod都可以有自己的持久性存储卷,并且这些存储卷可以在Pod重新启动时保持不变。这使得有状态应用程序能够在重新启动时保留其状态和数据。
3.3.1 卷凭据模板volumeClaimTemplates
我们在存储Storage一章,讲了StorageClass的概念,它可以自动创建PV。对于StatefulSet而言,还有一种自动创建PVC的方法,那就是卷凭据模版volumeClaimTemplates。volumeClaimTemplates是StatefulSet控制器的一段PVC模板,控制器拿它批量给每个Pod生成一条独立的PVC,不同Pod有自己的持久化存储空间。这个字段仅支持StatefulSet控制器,其他控制器如果有该字段,将会报unknown field "volumeClaimTemplates"错误。
前面我们创建StatefulSet时,就使用了volumeClaimTemplates。
[root@master 8-statefulset]# cat nginx-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-statefulset
labels:
app: nginx-sts
spec:
#podManagementPolicy: Parallel
serviceName: "nginx-statefulset-svc"
selector:
matchLabels:
app: nginx-sts
replicas: 5
template:
metadata:
labels:
app: nginx-sts
spec:
containers:
- name: nginx
image: nginx:1.27.3
imagePullPolicy: IfNotPresent
volumeMounts:
- name: vct-volume
mountPath: /usr/share/nginx/html
ports:
- containerPort: 80
volumeClaimTemplates:
- metadata:
name: vct-volume
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 400Mi
storageClassName: "local-path"
这个yaml中,volumeClaimTemplates字段含义是,使用PVC模板自动生成PVC,访问模式为RWO,声明容量为400MB,storageClassName意思是使用local-path这个存储类自动生成符合条件的PV。这样,我们创建StatefulSet控制器时,PVC和PV均会自动生成。
3.3.2 自动生成的PVC及PV
我们创建StatefulSet控制器后,PVC和PV会自动生成,下面通过命令查看这些自动生成的PVC和PV。
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (14m ago) 19h
nginx-deploy-7c7bbf4cc9-7q8qs 1/1 Running 0 3h48m
nginx-deploy-7c7bbf4cc9-njrg2 1/1 Running 0 3h48m
nginx-deploy-7c7bbf4cc9-nl9wp 1/1 Running 0 3h48m
nginx-statefulset-0 1/1 Running 0 3h48m
nginx-statefulset-1 1/1 Running 0 105m
nginx-statefulset-2 1/1 Running 0 105m
nginx-statefulset-3 1/1 Running 0 105m
# 查看PVC,每个PVC命名也是从0开始,每一个PVC对应一个Pod。PVC已经绑定了PV,这些PV是由local-path这个StorageClass自动创建的。
[root@master 8-statefulset]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
vct-volume-nginx-statefulset-0 Bound pvc-f8754cbf-fd58-42f5-8d98-5902ca4edef8 400Mi RWO local-path <unset> 3h49m
vct-volume-nginx-statefulset-1 Bound pvc-2226e798-ffd5-467c-892f-2773580e2a88 400Mi RWO local-path <unset> 3h49m
vct-volume-nginx-statefulset-2 Bound pvc-470b2e79-98d5-4a96-96c7-dd86d38135e5 400Mi RWO local-path <unset> 3h49m
vct-volume-nginx-statefulset-3 Bound pvc-9efbf451-01ef-44de-bf88-4143c81919bd 400Mi RWO local-path <unset> 106m
# 查看PV,StorageClass自动创建的PV,名字没有规律,但已经绑定了PVC。
[root@master 8-statefulset]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-2226e798-ffd5-467c-892f-2773580e2a88 400Mi RWO Delete Bound default/vct-volume-nginx-statefulset-1 local-path <unset> 3h49m
pvc-470b2e79-98d5-4a96-96c7-dd86d38135e5 400Mi RWO Delete Bound default/vct-volume-nginx-statefulset-2 local-path <unset> 3h49m
pvc-9efbf451-01ef-44de-bf88-4143c81919bd 400Mi RWO Delete Bound default/vct-volume-nginx-statefulset-3 local-path <unset> 106m
pvc-f8754cbf-fd58-42f5-8d98-5902ca4edef8 400Mi RWO Delete Bound default/vct-volume-nginx-statefulset-0 local-path <unset> 3h50m
# 通过kubectl describe查看Pod与PVC的对应关系,可以看出每个Pod都有自己独立的PVC,PVC与PV又是一一绑定,所以Pod有自己独立存储空间。
# nginx-statefulset-0这个Pod对应的PVC为vct-volume-nginx-statefulset-0
[root@master 8-statefulset]# kubectl describe pod nginx-statefulset-0
Name: nginx-statefulset-0
Namespace: default
(。。。部分内容略。。。)
Volumes:
vct-volume:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: vct-volume-nginx-statefulset-0
ReadOnly: false
(。。。部分内容略。。。)
# nginx-statefulset-2这个Pod对应的PVC为vct-volume-nginx-statefulset-2
[root@master 8-statefulset]# kubectl describe pod nginx-statefulset-2
Name: nginx-statefulset-2
Namespace: default
(。。。部分内容略。。。)
Volumes:
vct-volume:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: vct-volume-nginx-statefulset-2
ReadOnly: false
(。。。部分内容略。。。)
# 通过kubectl describe pv查看底层真实存储路径
# 底层真实路径为/var/lib/rancher/k3s/storage/pvc-2226e798-ffd5-467c-892f-2773580e2a88_default_vct-volume-nginx-statefulset-1
[root@master 8-statefulset]# kubectl describe pv pvc-2226e798-ffd5-467c-892f-2773580e2a88
Name: pvc-2226e798-ffd5-467c-892f-2773580e2a88
Labels: <none>
Annotations: local.path.provisioner/selected-node: master
pv.kubernetes.io/provisioned-by: rancher.io/local-path
Finalizers: [kubernetes.io/pv-protection]
StorageClass: local-path
Status: Bound
Claim: default/vct-volume-nginx-statefulset-1
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 400Mi
Node Affinity:
Required Terms:
Term 0: kubernetes.io/hostname in [master]
Message:
Source:
Type: LocalVolume (a persistent volume backed by local storage on a node)
Path: /var/lib/rancher/k3s/storage/pvc-2226e798-ffd5-467c-892f-2773580e2a88_default_vct-volume-nginx-statefulset-1
Events: <none>
3.3.3 Pod扩缩容与重新关联PVC
[root@master 8-statefulset]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
vct-volume-nginx-statefulset-0 Bound pvc-f8754cbf-fd58-42f5-8d98-5902ca4edef8 400Mi RWO local-path <unset> 4h2m
vct-volume-nginx-statefulset-1 Bound pvc-2226e798-ffd5-467c-892f-2773580e2a88 400Mi RWO local-path <unset> 4h2m
vct-volume-nginx-statefulset-2 Bound pvc-470b2e79-98d5-4a96-96c7-dd86d38135e5 400Mi RWO local-path <unset> 4h2m
vct-volume-nginx-statefulset-3 Bound pvc-9efbf451-01ef-44de-bf88-4143c81919bd 400Mi RWO local-path <unset> 119m
# Pod缩容为2个副本
[root@master 8-statefulset]# kubectl scale --replicas=2 sts nginx-statefulset
statefulset.apps/nginx-statefulset scaled
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (29m ago) 19h
nginx-deploy-7c7bbf4cc9-7q8qs 1/1 Running 0 4h3m
nginx-deploy-7c7bbf4cc9-njrg2 1/1 Running 0 4h3m
nginx-deploy-7c7bbf4cc9-nl9wp 1/1 Running 0 4h3m
nginx-statefulset-0 1/1 Running 0 4h3m
nginx-statefulset-1 1/1 Running 0 120m
# PVC和PV仍是4个,没有变化
[root@master 8-statefulset]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
vct-volume-nginx-statefulset-0 Bound pvc-f8754cbf-fd58-42f5-8d98-5902ca4edef8 400Mi RWO local-path <unset> 4h3m
vct-volume-nginx-statefulset-1 Bound pvc-2226e798-ffd5-467c-892f-2773580e2a88 400Mi RWO local-path <unset> 4h3m
vct-volume-nginx-statefulset-2 Bound pvc-470b2e79-98d5-4a96-96c7-dd86d38135e5 400Mi RWO local-path <unset> 4h3m
vct-volume-nginx-statefulset-3 Bound pvc-9efbf451-01ef-44de-bf88-4143c81919bd 400Mi RWO local-path <unset> 120m
# Pod副本重新调整为4个
[root@master 8-statefulset]# kubectl scale --replicas=4 sts nginx-statefulset
statefulset.apps/nginx-statefulset scaled
[root@master 8-statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65d6f566b9-77ksd 1/1 Running 2 (29m ago) 19h
nginx-deploy-7c7bbf4cc9-7q8qs 1/1 Running 0 4h3m
nginx-deploy-7c7bbf4cc9-njrg2 1/1 Running 0 4h3m
nginx-deploy-7c7bbf4cc9-nl9wp 1/1 Running 0 4h3m
nginx-statefulset-0 1/1 Running 0 4h3m
nginx-statefulset-1 1/1 Running 0 120m
nginx-statefulset-2 1/1 Running 0 4s
nginx-statefulset-3 1/1 Running 0 4s
# 还是原来的4个PVC及PV,没有创建新的
[root@master 8-statefulset]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
vct-volume-nginx-statefulset-0 Bound pvc-f8754cbf-fd58-42f5-8d98-5902ca4edef8 400Mi RWO local-path <unset> 4h3m
vct-volume-nginx-statefulset-1 Bound pvc-2226e798-ffd5-467c-892f-2773580e2a88 400Mi RWO local-path <unset> 4h3m
vct-volume-nginx-statefulset-2 Bound pvc-470b2e79-98d5-4a96-96c7-dd86d38135e5 400Mi RWO local-path <unset> 4h3m
vct-volume-nginx-statefulset-3 Bound pvc-9efbf451-01ef-44de-bf88-4143c81919bd 400Mi RWO local-path <unset> 120m
# 查看其中一个新扩容的Pod,不但名字和缩容前相同,关联的PVC也是同一个。
[root@master 8-statefulset]# kubectl describe pod nginx-statefulset-2
Name: nginx-statefulset-2
Namespace: default
(。。。部分内容略。。。)
Volumes:
vct-volume:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: vct-volume-nginx-statefulset-2
ReadOnly: false
(。。。部分内容略。。。)
在Pod缩容后,PVC并没有删除,因为PVC永远不会因为Pod、Deployment、StatefulSet的消失而自动消失,只有人为删除。
至于PV是否会随着PVC的删除而删除,则取决于创建这个PV的存储类StorageClass。
- 如果StorageClass的回收策略为Delete,则PVC删除时PV也会自动删除,并清除底层存储目录及文件
- 如果StorageClass的回收策略为Retain,则PVC删除时PV仍会保留。
我们通过kubectl get pv,查看其RECLAIM POLICY字段可以知道本PV的回收策略。
Pod与PV是一种稳定的关联关系,不会随着Pod的重启发生变化。一个StatefulSet使用的PV,即使Pod缩容了,再次扩容,也会继续使用该PV。如果是网络存储的PV(如nfs),即使Pod被调度到其他节点,其PV也不会发生变化,仍是原来的PV。所以,有状态控制器StatefulSet经常与nfs存储及Headless Service一起使用,nfs提供持久化存储,Headless提供稳定的网络标识。
4 StatefulSet与Deployment对比
4.1 特性对比
| 特性 | StatefulSet | Deployment |
|---|---|---|
| 适用场景 | 有状态应用(如数据库、消息队列) | 无状态应用(如Web服务、API) |
| Pod 身份标识 | 稳定且唯一(如pod-0、pod-1) | 随机且不稳定(如pod-vfgk6) |
| 网络标识 | 通过Headless Service 提供稳定的 DNS 名称 | 通过普通 Service 提供负载均衡的虚拟 IP |
| 存储 | 每个Pod 拥有独立的持久卷(PVC),数据持久化 | 通常使用共享卷或无持久化存储,数据易丢失 |
| 扩缩容顺序 | 有序扩缩容(从0顺序创建,逆序删除) | 并行扩缩容(无顺序保证) |
| 更新策略 | 支持有序滚动更新(按Pod顺序更新) | 支持并行滚动更新(无顺序保证) |
4.2 使用场景
- 使用StatefulSet的场景:
- 需要持久化存储(如数据库 MySQL、PostgreSQL)
- 需要稳定且唯一的网络标识(如分布式系统节点间的通信)
- 需要有序部署和扩缩容(如主从架构的数据库集群)
- 使用Deployment的场景:
- 无状态应用(如Web前端、API服务)
- 无需持久化存储或稳定网络标识
- 需要快速弹性扩缩容和高并发处理能力
更多推荐


所有评论(0)