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.localnginx-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服务)
    • 无需持久化存储或稳定网络标识
    • 需要快速弹性扩缩容和高并发处理能力
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐