Skip to content

官方文档,https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/statefulset/

1. StatefulSet基本概述

对于我们部署的应⽤,⼤体可以分为两类,⼀类是⽆状态应⽤,⼀类是有状态应⽤

  • 像web这种类型的应用,就属于无状态应用,他们不需要存储任何数据至本地,也没有任何次序之分,同时所提供的服务也是完全一样的,像这类应用,就可以通过Deployment控制器来进行编排和部署;

  • 像MySQL、Redis这类需要存储数据的应用程序,称之为有状态应用,它们有的有角色之分,有的是主节点,有的是从节点,而有的又存在先后次序之分。

1.0 应用场景

StatefulSet是一种用于运行有状态应用程序的控制器,它具有稳定的网络标识符有序的、逐个更新的部署方式持久性存储等特点,适用于需要这些特性的有状态应用程序

简单来说:

  • Pod有序的部署、扩容、删除和停止
  • Pod分配一个稳定的且唯一的网络标识
  • Pod分配一个独享的存储

1.1 什么是StatefulSet

StatefulSet 是用来管理有状态应用的工作负载 API 对象。

StatefulSet 用来管理某 Pod 集合的部署和扩缩, 并为这些 Pod 提供持久存储和持久标识符。

Kubernetes StatefulSet是一种用于运行有状态应用的控制器。

StatefulSet是一个有序的、可标识的Pod组,并且每个Pod都有一个独特的标识符。

这使得StatefulSet能够管理有状态应用程序,例如数据库或队列服务,这些应用程序需要稳定的网络标识符或持久性存储,并且需要有序的、逐个更新的部署方式。

ReplicaSet 控制器能够从一个预置的 Pod 模板创建一个或者多个 Pod 资源,除了主机名和 IP 地址之外,这些 Pod 资源并没有本质上的区别,就连 Pod 的名称也是使用同一种散列模式生成,具有很强的相似性。

若 ReplicaSet 控制器在 Pod 模板中包含了某些 PVC(Persistent Volume Claim)的引用,则由它创建的所有 Pod 资源都将共享此存储卷。PVC 后端的 PV 访问模型配置为 ReadOnlyMany 或者 ReadWriteMany 时,这些 Pod 资源中的容器应用挂载存储卷后也就有了相同的数据集。

不过大多数情况是,一个集群系统的分布式应用中,每个实例都有可能需要存储使用不同的数据集,或者各自拥有其专有的数据副本,例如:分布式系统 GlusterFS 和 分布式文档存储 MongoDB 中的每个实例各自使用专有的数据集,分布式服务框架 Zookeeper 以及主从复制集群中的 Redis 的每个实例各自拥有其专用的数据副本。

由于 ReplicaSet 控制器使用同一个模板生成 Pod 资源,显然,它无法实现为每个 Pod 资源创建专用的存储卷,以及组织多个只负责生成一个 Pod 资源的 ReplicaSet 控制器则有规模扩展不变的尴尬。自主式 Pod 资源又没有自愈能力。

其次,除了要用到专用持久化存储卷外,有些集群类的分布式应用实例在运行期间还存在角色上的差异,它们存在 单向/双向 的基于 IP 地址或 主机名 的引用关系,例如 主从复制集群中的 MySQL 从节点的引用。这类应用实例,每一个都应当作一个独立的个体对待。ReplicaSet 对象控制下的 Pod 资源重构后,其 名称 和 IP地址 都存在变动的可能性,因此无法适配此种场景需求。

因此,StatefulSet(有副本状态集)则是专门用来满足此类应用的控制器类型,由其管控的每个 Pod 对象都有着固定的主机名和专用存储卷,即便被重构后亦能保持不变。

1.2 StatefulSet特点

bash
1. 稳定且唯一的网络标识符
2. 稳定且持久的存储.
3. 有序、平滑地部署和扩展.
4. 有序、平滑的删除和终止.
5. 有序的滚动更新
1. 稳定且唯一的网络标识符
2. 稳定且持久的存储.
3. 有序、平滑地部署和扩展.
4. 有序、平滑的删除和终止.
5. 有序的滚动更新

1.(StatefulSet提供稳定的Pod名称,结合HeadLessService提供Pod唯⼀的DNS标识)

2.为每个Pod提供⼀个PVC,作为后端的存储,Pod故障重新启动任然会使⽤此前的PVC

3.⽐如redis主从复制r1-r6,⼀般先启动主节点r1,⽽后启动从节点r2-r6

4.⽐如redis主从复制r1-r6,⼀般先从r6-r2从节点开始删除,最后删除r1主节点

5.⽐如Redis主从复制r1-r6,⼀般先更新r2-r6从节点,⽽后更新r1主节点,前提是能兼容

在上⾯描述中,“稳定的”意味着 Pod 调度或重调度的整个过程是有持久性的(名称不变、pvc不变)。如果应⽤程序不需要任何稳定的标识符或有序的部署以及删除,则应该使⽤⽆状态的副本控制器来部署应⽤程序,⽐如Deployment或者ReplicaSet

1.3 StatefulSet资源状态

StatefulSet 资源的状态主要包括以下几个方面:

  1. Replicas: 指定的副本数,即 .spec.replicas字段的值。表示 StatefulSet 管理的副本数量。
  2. ReadyReplicas: 表示已经就绪的副本数量,即当前运行且已经READY的Pod数。
  3. CurrentReplicas: 表示当前正在运行的副本数量,即运行中的Pod总数。
  4. UpdatedReplicas: 表示已经更新的副本数量,即最近一次更新中,更新成功的Pod数。
  5. CurrentRevision: 当前正在执行的修订版本号。
  6. UpdateRevision: StatefulSet 的当前修订版本号。 StatefulSet 的模板(.spec.template)每次更新时,这个值就会增加1
  7. collisionCount:表示在创建 Pod 时发生的命名冲突的次数。
  8. UpdateStatus: 最近一次更新的状态,可以是"Running"或者"Failed"。
  9. ObservedGeneration: 最近一次对 StatefulSet 资源的更改,已经被看到的 Generation 数。也就是说如果 StatefulSet 的 .spec 字段被修改,该值会更新。
yaml
 status:
    observedGeneration: 2
    replicas: 3
    readyReplicas: 3
    currentReplicas: 3
    updatedReplicas: 3
    updateRevision: "2"
    currentRevision: "2"
 
observedGeneration 是 2,表示 StatefulSet 的 .spec 字段已经修改过两次。
replicas 是 3,表示指定的副本数为 3。
readyReplicas, currentReplicas 和 updatedReplicas 都是 3,表示所有的 3 个副本都已经准备就绪。
updateRevision 和 currentRevision 都是 "2",表示 StatefulSet 的模板已经更新两次,当前正在运行的也是第 2 个版本。
updateStatus 没有出现,表示最近一次更新状态是正常的。
 status:
    observedGeneration: 2
    replicas: 3
    readyReplicas: 3
    currentReplicas: 3
    updatedReplicas: 3
    updateRevision: "2"
    currentRevision: "2"
 
observedGeneration 是 2,表示 StatefulSet 的 .spec 字段已经修改过两次。
replicas 是 3,表示指定的副本数为 3。
readyReplicas, currentReplicas 和 updatedReplicas 都是 3,表示所有的 3 个副本都已经准备就绪。
updateRevision 和 currentRevision 都是 "2",表示 StatefulSet 的模板已经更新两次,当前正在运行的也是第 2 个版本。
updateStatus 没有出现,表示最近一次更新状态是正常的。

1.4 StatefulSet组成部分

StatefulSet由三个组件组成:HeadLessServiceStateFulSet控制器VolumeClaimTemplate

image-20240605182154021

StatefulSet控制器

StatefulSet名称是固定,且创建时按照顺序进⾏创建,并固定对应的Pod名称

Headless Service

用于为Pod资源标识符生成可解析的DNS记录

为什么要有headless

在deployment中,每一个pod是没有名称,是随机字符串,是无序的。而statefulset中是要求有序的,每一个pod的名称必须是固定的。当节点挂了,重建之后的标识符是不变的,每一个节点的节点名称是不能改变的。pod名称是作为pod识别的唯一标识符,必须保证其标识符的稳定并且唯一。     为了实现标识符的稳定,这时候就需要一个headless service 解析直达到pod,还需要给pod配置一个唯一的名称。

headless 使用场景

集群内部通信:Headless Service可以被用来实现Pod之间的直接通信,例如数据库集群中的各个节点之间的通信。

分布式计算:Headless Service可以被用来实现分布式计算中的任务分发和结果收集,例如MapReduce中的Map和Reduce节点之间的通信。

自定义服务发现:Headless Service可以被用来实现一些自定义的服务发现机制,例如一些复杂的应用程序中的服务发现和路由。

Headless 和 Service对比

image-20240606111619188

⽤来配置每个Pod的DNS名称,只要Pod的名称不变化,他们的DNS就是稳定且持久的

Headless Service是没有Cluster IP的Service(与普通Service的区别),在Headless Service中可以看到spec.ClusterIP=None。

若解析Headless Service的DNS域名,返回的是该Service对应的全部pod的Endpoint列表,StatefulSet在Headless Service基础上为StatefulSet控制的每个pod实例创建DNS域名,格式为$(pod_name).$(headless_service_name),全限定域名为:FQDN:$(pod_name).$(headless_service_name).$(namespace_name).svc.cluster.local

案例

yaml
apiVersion: v1
kind: Service #资源类型
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None # 为none,
  selector:
    app: nginx
apiVersion: v1
kind: Service #资源类型
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None # 为none,
  selector:
    app: nginx

这个 Service 被创建后并不会被分配一个 VIP,而是会以 DNS 记录的方式暴露出它所代理的 Pod格式为

yaml
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
<pod-name>.<svc-name>.<namespace>.svc.cluster.local

VolumeClaimTemplate

作为分布式系统,每个Pod的数据是不⼀样的,所以每个Pod应该有⾃⼰的专有数据,所以他们不能使⽤同⼀个存储卷,应该使⽤各⾃⾃⼰的存储卷。(存储卷申请模板)

为什么要 有volumeClainTemplate

大部分有状态副本集都会用到持久存储,比如分布式系统来说,由于数据是不一样的,每个节点都需要自己专用的存储节点。而在deployment中pod模板中创建的存储卷是一个共享的存储卷,多个pod使用同一个存储卷,而statefulset定义中的每一个pod都不能使用同一个存储卷,由此基于pod模板创建pod是不适应的,这就需要引入volumeClainTemplate,当在使用statefulset创建pod时,会自动生成一个PVC,从而请求绑定一个PV,从而有自己专用的存储卷。

1.4 StatefulSet更新策略

Kubernetes 1.7 版本起,StatefulSet 资源支持自动更新机制,其更新策略由 spec.updateStrategy 字段定义,默认为 RollingUpdate,即滚动更新

StatefulSet 的更新⽀持两种策略:onDeleteRollingUpdate,在.spec.updateStrategy 进⾏设定.并且滚动更新支持分区滚动更新

RollingUpdate滚动更新

RollingUpdate 更新策略在 StatefulSet 中实现 Pod 的自动滚动更新。

当StatefulSet的 .spec.updateStrategy.type 设置为 RollingUpdate 时,默认为:RollingUpdate。

StatefulSet 控制器将在 StatefulSet 中删除并重新创建每个 Pod。 它将以与 Pod 终止相同的顺序进行(从最大的序数到最小的序数),每次更新一个 Pod。 在更新其前身之前,它将等待正在更新的 Pod 状态变成正在运行并就绪。

bash
#默认更新策略RollingUpdate
  updateStrategy:
    rollingUpdate:
      partition: 0       #不更新小于 N 的副本
    type: RollingUpdate
#默认更新策略RollingUpdate
  updateStrategy:
    rollingUpdate:
      partition: 0       #不更新小于 N 的副本
    type: RollingUpdate
bash
#命令跟踪 StatefulSet 资源滚动更新过程中的状态信息
kubectl rollout status
#命令跟踪 StatefulSet 资源滚动更新过程中的状态信息
kubectl rollout status

OnDelete更新

当 StatefulSet 的 .spec.updateStrategy.type 设置为OnDelete 时, 控制器将不会⾃动更新 StatefulSet 中的 Pod。⽤户必须⼿动删除 Pod 以便让控制器创建新的 Pod,以此来对StatefulSet 的 .spec.template 的变动作出反应。

bash
修改:
   updateStrategy:
    rollingUpdate:
      partition: 0
    type: RollingUpdate
 
 改为:
   updateStrategy:
    type: OnDelete   #修改为OnDelete更新模式并保存,该更新策略是,删除时才会进行更新
修改:
   updateStrategy:
    rollingUpdate:
      partition: 0
    type: RollingUpdate
 
 改为:
   updateStrategy:
    type: OnDelete   #修改为OnDelete更新模式并保存,该更新策略是,删除时才会进行更新

💡 总结

StatefulSet 这个控制器的主要作用之一,就是使用 Pod 模板创建 Pod 的时候,对它们进行编号,并且按照编号顺序逐一完成创建工作。而当 StatefulSet 的“控制循环”发现 Pod 的“实际状态”与“期望状态”不一致,需要新建或者删除 Pod 进行“调谐”的时候,它会严格按照这些 Pod 编号的顺序,逐一完成这些操作.

在StatefulSet中,每个Pod都会被分配一个唯一的标识符和DNS记录,例如web-0.web.default.svc.cluster.local、web-1.web.default.svc.cluster.local等

1.5 暂存更新操作

当用户需要设定一个更新操作,但又不希望它立即执行时,可将更新操作予以 "暂存",待条件满足后再手动触发其执行更新。

StatefulSet 资源的分区更新机制能够实现此项功能。在设定更新操作之前,将.spec.updateStrategy.rollingUpdate.partition 字段的值设置为 Pod 资源的副本数量,即比 Pod 资源的最大索引号大 1,这就意味着,所有的 Pod 资源都不会处于可直接更新的分区之内,那么于其后设定的更新操作也就不会真正执行,直到用户降低分区编号至现有 Pod 资源索引号范围之内。

bash
#查看帮助
kubectl explain statefulset.spec.updateStrategy.rollingUpdate.partition
#查看帮助
kubectl explain statefulset.spec.updateStrategy.rollingUpdate.partition

下面测试滚动更新暂存更新操作,首先将 StatefulSet 资源滚动更新分区值设定为 3:

kubectl patch statefulset myapp -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":3}}}}'
kubectl patch statefulset myapp -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":3}}}}'

暂存状态的更新操作对所有的 Pod 资源均不产生影响,比如即使删除某 Pod 资源,它依然会基于旧的版本镜像进行重建

2. 案例

💡 说明

创建顺序如下:

1、Volume

2、Persistent Volume

3、Persistent Volume Claim

4、Headless Service

5、StatefulSet

Volume可以有很多种类型,比如nfs、glusterfs,ceph RBD等

2.1 Statefulset资源规范

bash
#查看帮助
kubectl explain statefulset

[root@kube-master statefulSet]# kubectl explain statefulset.spec
KIND:     StatefulSet
VERSION:  apps/v1

RESOURCE: spec <Object>

DESCRIPTION:
     Spec defines the desired identities of pods in this set.

     A StatefulSetSpec is the specification of a StatefulSet.

FIELDS:
   podManagementPolicy	<string>  #Pod管理策略
   replicas	<integer>    #副本数量
   revisionHistoryLimit	<integer>   #历史版本限制
   selector	<Object> -required-    #选择器,必选项
   serviceName	<string> -required-  #服务名称,必选项
   template	<Object> -required-    #模板,必选项
   updateStrategy	<Object>       #更新策略
   volumeClaimTemplates	<[]Object>   #存储卷申请模板,列表对象形式
#查看帮助
kubectl explain statefulset

[root@kube-master statefulSet]# kubectl explain statefulset.spec
KIND:     StatefulSet
VERSION:  apps/v1

RESOURCE: spec <Object>

DESCRIPTION:
     Spec defines the desired identities of pods in this set.

     A StatefulSetSpec is the specification of a StatefulSet.

FIELDS:
   podManagementPolicy	<string>  #Pod管理策略
   replicas	<integer>    #副本数量
   revisionHistoryLimit	<integer>   #历史版本限制
   selector	<Object> -required-    #选择器,必选项
   serviceName	<string> -required-  #服务名称,必选项
   template	<Object> -required-    #模板,必选项
   updateStrategy	<Object>       #更新策略
   volumeClaimTemplates	<[]Object>   #存储卷申请模板,列表对象形式

2.2 创建Headless

yaml
[root@kube-master statefulSet]# cat 01-web-headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: web-svc  #这里要创建statefulset 里面的serviceName的名字保持一致
spec:
  selector:
    app: my-app
  clusterIP: None
  ports:
  - port: 80
    targetPort: 80
[root@kube-master statefulSet]# cat 01-web-headless.yaml
apiVersion: v1
kind: Service
metadata:
  name: web-svc  #这里要创建statefulset 里面的serviceName的名字保持一致
spec:
  selector:
    app: my-app
  clusterIP: None
  ports:
  - port: 80
    targetPort: 80
  • 执行
yaml
[root@kube-master statefulSet]# kubectl apply -f 01-web-headless.yaml
service/my-service-headless created
[root@kube-master statefulSet]# kubectl apply -f 01-web-headless.yaml
service/my-service-headless created

2.3 创建StatefulSet服务

yaml
[root@kube-master statefulSet]# cat 2-web-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app        #必须匹配 .spec.template.metadata.labels
  serviceName: web-svc   #声明它属于哪个Headless Service.
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app-web-nginx
        image: nginx:1.16
        ports:
        - containerPort: 80
        volumeMounts:
        - name: my-app-web-vol
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: my-app-web-vol
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "nfs-provisioner-storage"
      resources:
        requests:
          storage: 1Gi
[root@kube-master statefulSet]# cat 2-web-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app        #必须匹配 .spec.template.metadata.labels
  serviceName: web-svc   #声明它属于哪个Headless Service.
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app-web-nginx
        image: nginx:1.16
        ports:
        - containerPort: 80
        volumeMounts:
        - name: my-app-web-vol
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: my-app-web-vol
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "nfs-provisioner-storage"
      resources:
        requests:
          storage: 1Gi

❌ 注意

定义 StatefulSet 资源时,spec 中必须要嵌套的字段为 "serviceName" 和 "template",用于指定关联的 Headless Service 和要使用的 Pod 模板

  • 查看
bash
[root@kube-master ~]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS        AGE
web-0                                     1/1     Running   1 (13m ago)     19m
web-1                                     1/1     Running   1 (12m ago)     19m
web-2                                     1/1     Running   1 (12m ago)     19m

[root@kube-master statefulSet]# kubectl get pvc,pv
NAME                                                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS              AGE
persistentvolumeclaim/my-app-web-vol-myapp-web-sts-0   Bound    pvc-0aad5ba3-1921-4772-a410-44ab7239523e   1Gi        RWO            nfs-provisioner-storage   82m
persistentvolumeclaim/my-app-web-vol-myapp-web-sts-1   Bound    pvc-1348f5f1-1505-49fd-aa33-f251ef8ffc00   1Gi        RWO            nfs-provisioner-storage   17m
persistentvolumeclaim/my-app-web-vol-myapp-web-sts-2   Bound    pvc-7d8f415d-3ebe-49f9-9910-8f3ef36bba57   1Gi        RWO            nfs-provisioner-storage   15m

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                   STORAGECLASS              REASON   AGE
persistentvolume/pvc-0aad5ba3-1921-4772-a410-44ab7239523e   1Gi        RWO            Delete           Bound    default/my-app-web-vol-myapp-web-sts-0   nfs-provisioner-storage            82m
persistentvolume/pvc-1348f5f1-1505-49fd-aa33-f251ef8ffc00   1Gi        RWO            Delete           Bound    default/my-app-web-vol-myapp-web-sts-1   nfs-provisioner-storage            17m
persistentvolume/pvc-7d8f415d-3ebe-49f9-9910-8f3ef36bba57   1Gi        RWO            Delete           Bound    default/my-app-web-vol-myapp-web-sts-2   nfs-provisioner-storage            15m
[root@kube-master ~]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS        AGE
web-0                                     1/1     Running   1 (13m ago)     19m
web-1                                     1/1     Running   1 (12m ago)     19m
web-2                                     1/1     Running   1 (12m ago)     19m

[root@kube-master statefulSet]# kubectl get pvc,pv
NAME                                                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS              AGE
persistentvolumeclaim/my-app-web-vol-myapp-web-sts-0   Bound    pvc-0aad5ba3-1921-4772-a410-44ab7239523e   1Gi        RWO            nfs-provisioner-storage   82m
persistentvolumeclaim/my-app-web-vol-myapp-web-sts-1   Bound    pvc-1348f5f1-1505-49fd-aa33-f251ef8ffc00   1Gi        RWO            nfs-provisioner-storage   17m
persistentvolumeclaim/my-app-web-vol-myapp-web-sts-2   Bound    pvc-7d8f415d-3ebe-49f9-9910-8f3ef36bba57   1Gi        RWO            nfs-provisioner-storage   15m

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                   STORAGECLASS              REASON   AGE
persistentvolume/pvc-0aad5ba3-1921-4772-a410-44ab7239523e   1Gi        RWO            Delete           Bound    default/my-app-web-vol-myapp-web-sts-0   nfs-provisioner-storage            82m
persistentvolume/pvc-1348f5f1-1505-49fd-aa33-f251ef8ffc00   1Gi        RWO            Delete           Bound    default/my-app-web-vol-myapp-web-sts-1   nfs-provisioner-storage            17m
persistentvolume/pvc-7d8f415d-3ebe-49f9-9910-8f3ef36bba57   1Gi        RWO            Delete           Bound    default/my-app-web-vol-myapp-web-sts-2   nfs-provisioner-storage            15m

2.4 测试dns解析

bash
测试格式,$(pod_name).$(headless_service_name).$(namespace_name).svc.cluster.local
也可以简写为,$(pod_name).$(headless_service_name)

kubectl run -it --image=busybox:1.28.3 dns-test --restart=Never --rm /bin/sh


/ # nslookup web-1.web-svc
Server:    192.168.0.10
Address 1: 192.168.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.web-svc
Address 1: 172.17.74.114 web-1.web-svc.default.svc.cluster.local
测试格式,$(pod_name).$(headless_service_name).$(namespace_name).svc.cluster.local
也可以简写为,$(pod_name).$(headless_service_name)

kubectl run -it --image=busybox:1.28.3 dns-test --restart=Never --rm /bin/sh


/ # nslookup web-1.web-svc
Server:    192.168.0.10
Address 1: 192.168.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.web-svc
Address 1: 172.17.74.114 web-1.web-svc.default.svc.cluster.local
  • 删除pod
bash
[root@kube-master statefulSet]# kubectl get pod -owide -l app=my-app
NAME    READY   STATUS    RESTARTS   AGE     IP               NODE          NOMINATED NODE   READINESS GATES
web-0   1/1     Running   0          2m58s   172.30.0.148     kube-node01   <none>           <none>
web-1   1/1     Running   0          2m56s   172.17.74.117    kube-node03   <none>           <none>
web-2   1/1     Running   0          2m54s   172.23.127.102   kube-node02   <none>           <none>

[root@kube-master statefulSet]# kubectl delete pod -l app=my-app
pod "web-0" deleted
pod "web-1" deleted
pod "web-2" deleted

[root@kube-master statefulSet]# kubectl get pod -owide -l app=my-app
NAME    READY   STATUS    RESTARTS   AGE   IP              NODE          NOMINATED NODE   READINESS GATES
web-0   1/1     Running   0          6s    172.30.0.176    kube-node01   <none>           <none>
web-1   1/1     Running   0          4s    172.17.74.115   kube-node03   <none>           <none>
web-2   1/1     Running   0          3s    172.23.127.87   kube-node02   <none>           <none>
[root@kube-master statefulSet]# kubectl get pod -owide -l app=my-app
NAME    READY   STATUS    RESTARTS   AGE     IP               NODE          NOMINATED NODE   READINESS GATES
web-0   1/1     Running   0          2m58s   172.30.0.148     kube-node01   <none>           <none>
web-1   1/1     Running   0          2m56s   172.17.74.117    kube-node03   <none>           <none>
web-2   1/1     Running   0          2m54s   172.23.127.102   kube-node02   <none>           <none>

[root@kube-master statefulSet]# kubectl delete pod -l app=my-app
pod "web-0" deleted
pod "web-1" deleted
pod "web-2" deleted

[root@kube-master statefulSet]# kubectl get pod -owide -l app=my-app
NAME    READY   STATUS    RESTARTS   AGE   IP              NODE          NOMINATED NODE   READINESS GATES
web-0   1/1     Running   0          6s    172.30.0.176    kube-node01   <none>           <none>
web-1   1/1     Running   0          4s    172.17.74.115   kube-node03   <none>           <none>
web-2   1/1     Running   0          3s    172.23.127.87   kube-node02   <none>           <none>

Pod 的序号、主机名、SRV 条目和记录名称没有改变,但和 Pod 相关联的 IP 地址可能发生了改变 如果你需要查找并连接一个 StatefulSet 的活动成员,你应该查询 Headless Service 的 CNAME和 CNAME 相关联的 SRV 记录只会包含 StatefulSet 中处于 Running 和 Ready 状态的 Pod。

2.5 扩缩StatefulSet

对 StatefulSet 资源来说,kubectl scalekubectl path 命令均可实现此功能,也可以使用 kubectl edit 命令直接修改其副本,或者在修改配置文件之后,由 Kubectl apply 命令重新声明

扩展StatefulSet副本数,它则会按照编号⼀个⼀个的顺序创建出来

kubectl scale statefulset web --replicas=5
kubectl scale statefulset web --replicas=5

查看Pod的创建过程,对于包含 N 个 副本的 StatefulSet,当部署 Pod 时,它们是依次创建的,顺序为0..N-1

bash
kubectl get pod -l app=my-app -w
kubectl get pod -l app=my-app -w

或者

kubectl patch sts web -p '{"spec":{"replicas":4}}' -n nginx-ss  #扩容
kubectl patch sts web -p '{"spec":{"replicas":2}}' -n nginx-ss  #缩容
kubectl patch sts web -p '{"spec":{"replicas":4}}' -n nginx-ss  #扩容
kubectl patch sts web -p '{"spec":{"replicas":2}}' -n nginx-ss  #缩容

2.6 更新策略和版本升级

修改更新策略,以partition方式进行更新,更新值为2,只有myapp编号大于等于2的才会进行更新。类似于金丝雀部署方式

bash
updateStrategy 
  rollingUpdate:
    partition: 2  #副本大于设置的值时进行更新,也就是分段更新,这里是大于2的副本更新
  type: RollingUpdate
updateStrategy 
  rollingUpdate:
    partition: 2  #副本大于设置的值时进行更新,也就是分段更新,这里是大于2的副本更新
  type: RollingUpdate

partiton是保留几个pod不更新,其他的pod进行更新

bash
[root@k8s-master mainfests]# kubectl edit sts web

或者
[root@k8s-master mainfests]# kubectl patch sts web -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":2}}}}'
statefulset.apps/myapp patched
[root@k8s-master ~]# kubectl get sts web
NAME      DESIRED   CURRENT   AGE
myapp     4         4         1h
[root@k8s-master ~]# kubectl describe sts web
Name:               myapp
Namespace:          default
CreationTimestamp:  Wed, 10 Oct 2018 21:58:24 -0400
Selector:           app=myapp-pod
Labels:             <none>
Annotations:        kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"apps/v1","kind":"StatefulSet","metadata":{"annotations":{},"name":"myapp","namespace":"default"},"spec":{"replicas":3,"selector":{"match...
Replicas:           4 desired | 4 total
Update Strategy:    RollingUpdate
  Partition:        2
......
[root@k8s-master mainfests]# kubectl edit sts web

或者
[root@k8s-master mainfests]# kubectl patch sts web -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":2}}}}'
statefulset.apps/myapp patched
[root@k8s-master ~]# kubectl get sts web
NAME      DESIRED   CURRENT   AGE
myapp     4         4         1h
[root@k8s-master ~]# kubectl describe sts web
Name:               myapp
Namespace:          default
CreationTimestamp:  Wed, 10 Oct 2018 21:58:24 -0400
Selector:           app=myapp-pod
Labels:             <none>
Annotations:        kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"apps/v1","kind":"StatefulSet","metadata":{"annotations":{},"name":"myapp","namespace":"default"},"spec":{"replicas":3,"selector":{"match...
Replicas:           4 desired | 4 total
Update Strategy:    RollingUpdate
  Partition:        2
......

版本升级,将image的版本升级为v2,升级后对比web-2和web-1的image版本是不同的。这样就实现了金丝雀发布的效果。

bash
[root@k8s-master mainfests]# kubectl set image sts/web myapp=ikubernetes/myapp:v3
statefulset.apps/myapp image updated
[root@k8s-master ~]# kubectl get sts -o wide
NAME      DESIRED   CURRENT   AGE       CONTAINERS   IMAGES
web     4         4         1h        myapp        ikubernetes/myapp:v3
[root@k8s-master ~]# kubectl get pods web-2 -o yaml |grep image
  - image: ikubernetes/myapp:v3
    imagePullPolicy: IfNotPresent
    image: ikubernetes/myapp:v3
    imageID: docker-pullable://ikubernetes/myapp@sha256:b8d74db2515d3c1391c78c5768272b9344428035ef6d72158fd9f6c4239b2c69

[root@k8s-master ~]# kubectl get pods web-1 -o yaml |grep image
  - image: ikubernetes/myapp:v2
    imagePullPolicy: IfNotPresent
    image: ikubernetes/myapp:v2
    imageID: docker-pullable://ikubernetes/myapp@sha256:85a2b81a62f09a414ea33b74fb8aa686ed9b168294b26b4c819df0be0712d358
[root@k8s-master mainfests]# kubectl set image sts/web myapp=ikubernetes/myapp:v3
statefulset.apps/myapp image updated
[root@k8s-master ~]# kubectl get sts -o wide
NAME      DESIRED   CURRENT   AGE       CONTAINERS   IMAGES
web     4         4         1h        myapp        ikubernetes/myapp:v3
[root@k8s-master ~]# kubectl get pods web-2 -o yaml |grep image
  - image: ikubernetes/myapp:v3
    imagePullPolicy: IfNotPresent
    image: ikubernetes/myapp:v3
    imageID: docker-pullable://ikubernetes/myapp@sha256:b8d74db2515d3c1391c78c5768272b9344428035ef6d72158fd9f6c4239b2c69

[root@k8s-master ~]# kubectl get pods web-1 -o yaml |grep image
  - image: ikubernetes/myapp:v2
    imagePullPolicy: IfNotPresent
    image: ikubernetes/myapp:v2
    imageID: docker-pullable://ikubernetes/myapp@sha256:85a2b81a62f09a414ea33b74fb8aa686ed9b168294b26b4c819df0be0712d358

将剩余的Pod也更新版本,只需要将更新策略的partition值改为0即可,如下:

bash
[root@k8s-master mainfests]#  kubectl patch sts web -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":0}}}}'
statefulset.apps/web patched

[root@k8s-master ~]# kubectl get pods -w
NAME                     READY     STATUS    RESTARTS   AGE
web-0                  1/1       Running   0          58m
web-1                  1/1       Running   0          58m
web-2                  1/1       Running   0          13m
web-3                  1/1       Running   0          13m
web-1   1/1       Terminating   0         58m
web-1   0/1       Terminating   0         58m
web-1   0/1       Terminating   0         58m
web-1   0/1       Terminating   0         58m
web-1   0/1       Pending   0         0s
web-1   0/1       Pending   0         0s
web-1   0/1       ContainerCreating   0         0s
web-1   1/1       Running   0         2s
web-0   1/1       Terminating   0         58m
web-0   0/1       Terminating   0         58m
web-0   0/1       Terminating   0         58m
web-0   0/1       Terminating   0         58m
web-0   0/1       Pending   0         0s
web-0   0/1       Pending   0         0s
web-0   0/1       ContainerCreating   0         0s
web-0   1/1       Running   0         2s
[root@k8s-master mainfests]#  kubectl patch sts web -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":0}}}}'
statefulset.apps/web patched

[root@k8s-master ~]# kubectl get pods -w
NAME                     READY     STATUS    RESTARTS   AGE
web-0                  1/1       Running   0          58m
web-1                  1/1       Running   0          58m
web-2                  1/1       Running   0          13m
web-3                  1/1       Running   0          13m
web-1   1/1       Terminating   0         58m
web-1   0/1       Terminating   0         58m
web-1   0/1       Terminating   0         58m
web-1   0/1       Terminating   0         58m
web-1   0/1       Pending   0         0s
web-1   0/1       Pending   0         0s
web-1   0/1       ContainerCreating   0         0s
web-1   1/1       Running   0         2s
web-0   1/1       Terminating   0         58m
web-0   0/1       Terminating   0         58m
web-0   0/1       Terminating   0         58m
web-0   0/1       Terminating   0         58m
web-0   0/1       Pending   0         0s
web-0   0/1       Pending   0         0s
web-0   0/1       ContainerCreating   0         0s
web-0   1/1       Running   0         2s

2.7 删除StatefulSet

删除StatefulSet,它会从后往前删除,先删除web-1,然后删除web-0

bash
kubectl delete statefulsets name
kubectl delete statefulsets name

删除 Pod 时,它们是逆序终⽌的,顺序为 N-1..0

statefulset删除分为两种

级联删除[默认] 删除statefulset时删除pod

非级联删除 删除statefulset时不删除pod

级联删除

bash
kubectl delete statefulsets name
kubectl delete statefulsets name

非级联删除

[root@kube-master statefulSet]# kubectl delete sts web --cascade=orphan
statefulset.apps "web" deleted
[root@kube-master statefulSet]# kubectl delete sts web --cascade=orphan
statefulset.apps "web" deleted
bash
[root@kube-master statefulSet]# kubectl get pod -l app=my-app
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          58m
web-1   1/1     Running   0          58m

[root@kube-master statefulSet]# kubectl get sts
No resources found in default namespace.
[root@kube-master statefulSet]# kubectl get pod -l app=my-app
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          58m
web-1   1/1     Running   0          58m

[root@kube-master statefulSet]# kubectl get sts
No resources found in default namespace.

💡 说明

非级联删除时,删除了sts 但是pod并未被删除

kubernetes 1.24以前版本参数 --cascade=false

kubernetes 1.24以后版本参数 --cascade=orphan