16. Pod 自动管理——StatefulSet

本章讲解知识点

  • 前言
  • StatefulSet
  • StatefulSet 更新策略
  • StatefulSet 的灰度升级(金丝雀发布)
  • StatefulSet 的级联删除和非级联删除
  • StatefulSet 对于有状态存储的管理

<br>

1. 前言

上一节我们介绍了 Deployment 无状态应用,Deployment 实际上并不足以覆盖所有的应用编排问题。造成这个问题的根本原因,在于 Deployment 对应用做了一个简单化假设。它认为,一个应用的所有 Pod,是完全一样的。所以,它们互相之间没有顺序,也无所谓运行在哪台宿主机上。需要的时候,Deployment 就可以通过 Pod 模板创建新的 Pod;不需要的时候,Deployment 就可以“杀掉”任意一个 Pod。但是,在实际的场景中,并不是所有的应用都可以满足这样的要求。尤其是分布式应用,它的多个实例之间,往往有依赖关系,比如:主从关系、主备关系

还有就是数据存储类应用,它的多个实例,往往都会在本地磁盘上保存一份数据。而这些实例一旦被杀掉,即便重建出来,实例与数据之间的对应关系也已经丢失,从而导致应用失败。所以,这种实例之间有不对等关系,以及实例对外部数据有依赖关系的应用,就被称为“有状态应用”(Stateful Application)。例如,数据库、缓存系统等应用程序需要在运行过程中持久化存储数据。

在无状态 Deployment 中,所有 Pod 都可以被视为互相替换的副本。因此,当需要扩展应用程序时,可以通过在 Deployment 中增加副本数来增加容量。另外,因为无状态应用程序通常不需要持久化存储或其他状态信息,所以可以随意删除或重新启动 Pod。

但是有的应用,比如数据库 redis,是主备从的部署模式,一般是主服务先起来,然后依次是备服务、从服务,存在一个向前依赖的关系,因此是一个按顺序的部署模式。同时主备从都需要一个唯一的网络标识符。不仅如此,我重建了服务后,它还要恢复到原来的状态(因为它是数据库,不恢复到原来的状态,数据就会有问题),这些要求使用 Deployment 是做不到的

那么这就是 StatefulSet 的应用场景了。接下来我们详细介绍。

2. StatefulSet

2.1. 概念

StatefulSet 是 Kubernetes 中的一个控制器,用于管理有状态应用程序,可以确保它们在节点重启或故障时仍能保持稳定。与 Deployment 不同,StatefulSet 为每个 Pod 分配一个唯一的稳定的网络标识符(通常是一个数字),这些标识符在 Pod 的整个生命周期中都是固定的。这种固定标识符的使用允许有状态应用程序在部署和扩展时保持稳定

比如在生产环境中,可以部署 ElasticSearch 集群、MongoDB 集群或者需要持久化的 RabbitMQ 集群、Redis 集群、Kafka 集群和ZooKeeper 集群等。和 Deployment 类似,一个 StatefulSet 也同样管理着基于相同容器规范的 Pod。不同的是,StatefulSet 为每个 Pod 维护了一个网络标识符。这些 Pod 是根据相同的规范创建的,但是不可互换,每个 Pod 都有一个持久的标识符,在重新调度时也会保留,一般格式为 StatefulSetName-Number。比如定义一个名字是 A 的 StatefulSet,指定创建三个 Pod,那么创建出来的 Pod 名字就为 A-0、A-1、A-2。如图

假如公司某个项目需要在 Kubernetes 中部署一个主从模式的 Redis。此时使用 StatefulSet 部署就极为合适,因为 StatefulSet 启动时,只有当前一个容器完全启动时,后一个容器才会被调度,并且每个容器的标识符是固定的,那么就可以通过标识符来断定当前 Pod 的角色。比如用一个名为 redis-ms 的 StatefulSet 部署主从架构的 Redis,第一个容器启动时,它的标识符为 redis-ms-0,并且 Pod 内主机名也为 redis-ms-0,redis-ms-0 所在的 node 将作为 Redis 的主节点。

2.2. StatefulSet的特点

StatefulSet 的特点包括:

  • 每个 Pod 都有一个唯一的固定名称,如 <statefulset name>-<ordinal>,其中 ordinal 是一个整数,表示 Pod 在 StatefulSet 中的序号。
  • Pod 的启动和删除顺序可以被控制,保证了有状态应用程序的数据的稳定性。
  • 与 Pod 相关的存储(例如 PV 或 PVC)可以自动或手动地管理,以便与 Pod 的生命周期相对应。

StatefulSet 是适用于有状态应用程序的理想选择,例如数据库(典型 redis)、缓存等。

2.3. 小实验

我们来写第一个StatefulSet定义文件,命名为 my-first-sts.yaml:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: my-first-statefulset
spec:
  serviceName: redis
  replicas: 1
  selector:
    matchLabels:
      app: my-first-statefulset
  template:
    metadata:
      labels:
        app: my-first-statefulset
    spec:
      containers:
      - name: my-first-container
        image: redis:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80

这个 YAML 文件,和我们在前面文章中用到的 my-first-deploy.yaml 的唯一区别,就是多了一个 serviceName=redis 字段。用于指定 statefulset 的名称。

创建出我们第一个 statefulset,kubectl create -f my-first-sts.yaml

[root@master mtuser]# kubectl create -f my-first-sts.yaml
statefulset.apps/my-first-statefulset created
[root@master mtuser]#
[root@master mtuser]# kubectl get sts
NAME                   READY   AGE
my-first-statefulset   1/1     3m8s
[root@master mtuser]#
[root@master mtuser]# kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
my-first-statefulset-0   1/1     Running   0          5s

与 Deployment 类似,StatefulSet 同样支持一键式扩容和升级:

kubectl scale statefulset/my-first-statefulset --replicas=2

[root@master mtuser]# kubectl scale statefulset/my-first-statefulset --replicas=2
statefulset.apps/my-first-statefulset scaled
[root@master mtuser]# 
[root@master mtuser]# kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
my-first-statefulset-0   1/1     Running   0          4m41s
my-first-statefulset-1   1/1     Running   0          27s

我们可以很清楚地看见,Pod 名称就是 <statefulset name>-<ordinal> 的格式,其中 ordinal 是一个整数,表示 Pod 在 StatefulSet 中的序号。StatefulSet 创建 Pod,序号由小到大。而且这些编号都是从 0 开始累加,与 StatefulSet 的每个 Pod 实例一一对应,绝不重复。更重要的是,这些 Pod 的创建,也是严格按照编号顺序进行的。比如,在 my-first-statefulset-0 进入到 Running 状态、并且细分状态(Conditions)成为 Ready 之前,my-first-statefulset-1 会一直处于 Pending 状态。

我们使用 kubectl exec 命令进入到容器中查看它们的 hostname:

[root@master mtuser]# kubectl exec my-first-statefulset-0 -- sh -c 'hostname'
my-first-statefulset-0
[root@master mtuser]# kubectl exec my-first-statefulset-1 -- sh -c 'hostname'
my-first-statefulset-1

可以看到,这两个 Pod 的 hostname 与 Pod 名字是一致的,都被分配了对应的编号。

当我们把这两个“有状态应用”的 Pod 删掉:

[root@master mtuser]# kubectl delete pod my-first-statefulset-0 my-first-statefulset-1
pod "my-first-statefulset-0" deleted
pod "my-first-statefulset-1" deleted
[root@master mtuser]# 
[root@master mtuser]# kubectl get pod
NAME      READY     STATUS              RESTARTS   AGE
my-first-statefulset-0     0/1       ContainerCreating   0          0s
NAME      READY     STATUS    RESTARTS   AGE
my-first-statefulset-0     1/1       Running   0          2s
my-first-statefulset-1     0/1       Pending   0         0s
my-first-statefulset-1     0/1       ContainerCreating   0         0s
my-first-statefulset-1     1/1       Running   0         32s

可以看到,当我们把这两个 Pod 删除之后,Kubernetes 会按照原先编号的顺序,创建出了两个新的 Pod。并且,Kubernetes 依然为它们分配了与原来相同的“网络身份”:my-first-statefulset-0.redis 和 my-first-statefulset-1.redis(podName.serviceName 格式)。通过这种严格的对应规则,StatefulSet 就保证了 Pod 网络标识的稳定性。比如,如果 my-first-statefulset-0 是一个需要先启动的主节点,my-first-statefulset-1 是一个后启动的从节点,那么只要这个 StatefulSet 不被删除,你访问 my-first-statefulset-0.redis 时始终都会落在主节点上,访问 my-first-statefulset-1.redis 时,则始终都会落在从节点上,这个关系绝对不会发生任何变化。

同样,我们也可以一键式替换镜像:

[root@master mtuser]# kubectl set image statefulset/my-first-statefulset my-first-container=redis:1.2.1
statefulset.apps/my-first-statefulset image updated
[root@master mtuser]#
[root@master mtuser]# kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
my-first-statefulset-0   1/1     Running   0          20s
my-first-statefulset-1   1/1     Running   0          25s
[root@master mtuser]#
[root@master mtuser]# kubectl describe pod my-first-statefulset-0 | grep image
  Normal  Pulled     <invalid>  kubelet, node1     Container image "redis:1.2.1" already present on machine

<br>

3. StatefulSet 更新策略

StatefulSet 更新策略逐渐向 Deployment 看齐,也是要考虑更新时业务不能中断,那么自然默认更新策略为 RollingUpdate。同时相比于 Deployment,StatefulSet 还要考虑一个问题,就是更新过程中,主备不能变,依然要保持原有的依赖关系和状态,确保更新过程中 Pod 的主机名和网络标识符不会变化,以保证数据的可靠性。那么我们就继续看看 StatefulSet 是怎么做的。

3.1. StatefulSet 更新策略

  • RollingUpdate(默认):逐个替换 Pod,新 Pod 取代旧 Pod。它适用于有状态应用程序需要一段时间才能启动或停止的情况。RollingUpdate 策略默认同时更新一个副本,即 maxUnavailable 和 maxSurge 均设置为 1,更新时不会造成业务中断。
  • OnDelete:先删除旧 Pod,再创建新 Pod。OnDelete 策略需要手动执行命令才能触发更新,不会自动更新。一旦旧 Pod 被删除,相关服务可能会短暂的不可用,直到新 Pod 被创建并启动。

看了以上介绍,StatefulSet 默认选择 RollingUpdate。OnDelete也有用武之地,好处就在于可以控制整个过程,每当一个实例更新完成,必须手动删除旧 Pod 再更新下一个实例,一旦发生错误,更新停止,未更新的老版本实例照常运行,服务不会中断,可以做到无损升级

3.2. StatefulSet RollingUpdate 工作原理

当更新策略设置为 RollingUpdate 时,StatefulSet Controller 会删除并创建 StatefulSet 相关的每个 Pod 对象,其处理顺序与 StatefulSet 终止 Pod 的顺序一致,即从序号最大的 Pod 开始重建,每次更新一个 Pod。如果更新过程中,小序号的 Pod 出现故障,那么更新就会被打断,同时对出现故障的小序号 Pod 进行重建(使用当前版本的 StatefulSet 版本而不是新版本)

总结就是 StatefulSet 创建 Pod,序号由小到大;删除、更新 Pod,序号由大到小

3.3. 实验

我们更新 StatefulSet 的镜像版本:

kubectl set image statefulset/my-first-statefulset my-first-container=redis:latest

然后查看更新历史记录:

[root@master mtuser]# kubectl rollout status statefulset/my-first-statefulset
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
Waiting for partitioned roll out to finish: 1 out of 3 new pods have been updated...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
Waiting for partitioned roll out to finish: 2 out of 3 new pods have been updated...
Waiting for 1 pods to be ready...
Waiting for 1 pods to be ready...
partitioned roll out complete: 3 new pods have been updated...
[root@master mtuser]# kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
my-first-statefulset-0   1/1     Running   0          9s
my-first-statefulset-1   1/1     Running   0          13s
my-first-statefulset-2   1/1     Running   0          17s

可以看到 Pod 重建的时间根据 Pod 序号而定,序号越大,重建时间越早。

接下来我们查看 StatefulSet 更新信息:

kubectl describe sts my-first-statefulset | tail -20

[root@master mtuser]# kubectl describe sts my-first-statefulset | tail -20
Pod Template:
  Labels:  app=my-first-statefulset
  Containers:
   my-first-container:
    Image:        redis:latest
    Port:   

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

云计算面试题全解析 文章被收录于专栏

本专刊适合于立志转行云计算的小白,有一定的编程、操作系统、计算机网络、数据结构、算法基础。 本专刊同时也适合于面向云计算(Docker + Kubernetes)求职的从业者。 本专刊囊括了云计算、VMWare、Docker、Kubernetes、Containerd等一系列知识点的讲解,并且最后总

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务