18. 有状态应用编排:StatefulSet

本文由 CNCF + Alibaba 云原生技术公开课 整理而来

“有状态”需求

  • Deployment 介绍:

Deployment 作为 Pod 管理部署发布的控制器,每个 Deployment 其实是管理的一组相同的应用 PodDeployment 可以具体完成这些事情:

1. Deployment定义了 Pod 期望数量,Controller 始终会持续维持 Pod 数量为期望的数量。当 Pod 出现问题时,Controller也能够恢复

2. 配置 Pod 发布方式,Controller 会按照 Deployment 配置的策略来更新 Pod,而且在更新过程中可以设置不可用的 Pod 数量范围

3. 如果更新过程中发生问题,也可以针对 Deployment “一键回滚”,通过一条命令就可以将 Deployment 对应的所有 Pod 更新为之前的旧版本

Deployment 管理的所有相同版本的 Pod 都是一模一样的副本。也就是说,在 Deployment Controller 看来,所有相同版本的 Pod,不管是里面部署的应用还是行为,都是完全相同的。

这样一种能力对于无状态的应用是支持满足的,如果遇到一些有状态应用,Deployment 是否还能支持满足呢?

  • 需求分析:

对于有状态的应用,一般有以下需求:

1. Pod 之间并非相同的副本,每个 Pod 有一个独立标识

2. Pod 独立标识要能够对应到一个固定的网络标识,并在发布升级后继续保持

3. 每个 Pod 有一块独立的存储盘,并在发布升级后还能继续挂载原有的盘(保留数据)

4. 应用发布时,按照固定顺序升级 Pod

以上这些需求,Deployment 是无法满足的。因此,Kubernetes 引入了全新的资源对象——StatefulSet,专门用来管理有状态应用。

  • StatefulSet 介绍:

StatefulSet,是主要面向有状态应用管理的控制器,它能较好地满足一些有状态应用特有的需求:

1. 每个 Pod 有 Order 序号,会按序号创建、删除、更新 Pod

2. 通过配置 headless Service,使每个 Pod 有唯一的网络标识(hostname)

3. 通过配置 PVC template,每个 Pod 有一块独享的 PV 存储盘

4. 支持一定数量的灰度发布

StatefulSet 用法

下面简单介绍 StatefulSet 的用法。

  • StatefulSet 语法:

nginx-web StatefulSet yaml 文件示例:

apiVersion: v1
klind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  selector:
    app: nginx
  ports:
  - port: 80
    name: web
  clusterIp: None
  
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-web
spec:
  serviceName: nginx
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
        volumnMounts:
        - name: www-storage
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www-storage
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 20Gi

文件解析:

apiVersion: apps/v1     表示 StatefulSet 当前所属的组是 apps,版本是 v1

kind        表示 Kubernetes 资源类型是 StatefulSet

metadata    表示 StatefulSet 的元数据,元数据通常包含 name、namespace、labels、annotations 等

spec    表示 StatefulSet 的期望状态,replicas 表示期望的 Pod 数量;selector 表示 Pod 选择器,选择带相同 label 的 Pod;
        template 表示 Pod 的模板,模板中也有 Pod 的 metadata 和 spec;volumeClaimTemplates 表示 StatefulSet 的 PVC 模板,通过该模板动态创建 PV,与 Pod 一一对应
  • 查看 StatefulSet 状态:

当创建出一个 StatefulSet,可以通过 kubectl get statefulset 命令查看 StatefulSet 的状态。具体可以看到这些字段:

NAME        StatefulSet 名称

READY       分母是 StatefulSet 中期望的 Pod 数量,而分子表示已经达到 READY 状态的 Pod 数量

AGE         StatefulSet 创建的时长
  • 查看 Pod 状态:

当创建出一个 StatefulSet,可以通过 kubectl get pod 命令查看 Pod 的状态。通常可以看出 Pod 的名称分为两部分:

第一部分,是 Pod 所属的 StatefulSet 的名称,如果是上面示例中创建的 StatefulSet,则该名称是 nginx-web

第二部分,是序号,同一个 StatefulSet 的 Pod 序号从 0 开始,依次加 1,直到 Pod 数量达到 StatefulSet 的期望数量
  • 查看 PVC 状态:

当创建出一个 StatefulSet,可以通过 kubectl get pvc 命令查看 PVC 的状态。通常可以看出 PVC 的名称分为三部分:

第一部分,是 StatefulSet 中定义的 PVC 模板的名称,如果是上面示例中创建的 StatefulSet,则该名称是 www-storage

第二部分,是 PVC 所属的 StatefulSet 的名称,如果是上面示例中创建的 StatefulSet,则该名称是 nginx-web

第三部分,是序号,同一个 StatefulSet 的 PVC 序号从 0 开始,依次加 1,直到 PVC 数量达到 StatefulSet 期望的 Pod 数量
  • StatefulSet 更新镜像版本:

对一个给定的 StatefulSet 更新镜像版本,有三种方式。以上面示例为例:

1. kubectl set image statefulset nginx-web nginx=nginx:1.9.1

2. kubectl edit statefulset nginx-web               #修改镜像版本

3. 直接修改 yaml 文件(image: nginx:1.9.1),kubectl apply -f <yaml file>
  • StatefulSet 快速回滚:

如果在发布过程中遇到了问题,也支持快速回滚。以上面示例为例:

#回滚到上一版本

kubectl rollout undo statefulset nginx-web

#先查看版本列表,然后回滚到指定版本

kubectl rollout history statefulset nginx-web

kubectl rollout undo statefulset nginx-web --to-revision=2
  • Pod 的版本:

不同于 Deployment 使用 ReplicaSet 来管理版本和维持副本数,StatefulSet Controller 直接管理下属的 Pod,而 Pod 中用一个 Label 来表示版本:controller-revision-hash

这个 Label 标识和 Deployment 以及 StatefulSetPod 中注入的 Pod template hash 是类似的。当版本更新时,controller-revision-hash 会发生改变,而且 StatefulSetPod 是倒序更新的,并复用之前 Pod 使用的 PVC

kubectl get pod -L controller-revision-hash

架构设计

  • 管理模式:

StatefulSet 可能会创建三种类型的资源。

第一种资源:ControllerRevision,通过这个资源,StatefulSet 可以很方便地管理不同版本的 template 模板。

第二个资源:PVC,如果在 StatefulSet 中定义了 volumeClaimTemplatesStatefulSet 会在创建 Pod 之前,先根据这个模板创建 PVC,并把 PVC 加到 Pod volume 中。当然在 spec 中也可以不定义 PVC template,那么所创建出来的 Pod 就不会挂载单独的一个 PV

第三个资源:PodStatefulSet 按照顺序创建 Pod,按照倒序删除、更新 Pod,每个 Pod 有唯一的序号。

当前版本的 StatefulSet 只会在 ControllerRevisionPod 中添加 OwnerReference,而不会在 PVC 中添加 OwnerReference。之前提到过,拥有 OwnerReference 的资源,在管理的这个资源进行删除的默认情况下,会关联级联删除下属资源。因此默认情况下删除 StatefulSet 之后,StatefulSet 创建的 ControllerRevisionPod 都会被删除,但是 PVC 因为没有写入 OwnerReferencePVC 并不会被级联删除。

  • StatefulSet Controller

在这里插入图片描述

StatefulSet Controller 的工作流程如下:

首先通过注册 InformerEvent Handler(事件处理),来处理 StatefulSetPod 的变化。在 Controller 逻辑中,每一次收到 StatefulSetPod 的变化,都会找到对应的 StatefulSet 放到队列。紧接着从队列取出来处理后,先做的操作是 Update Revision,也就是先查看当前拿到的 StatefulSet 中的 template,有没有对应的 ControllerRevision。如果没有,说明 template 已经更新过,Controller 就会创建一个新版本的 Revision,也就有了一个新的 ControllerRevision hash 版本号。

然后 Controller 会把所有版本号拿出来,并且按照序号整理一遍。这个整理的过程中,如果发现有缺少的 Pod,它就会按照序号去创建,如果发现有多余的 Pod,就会按照序号去删除。当保证了 Pod 数量和 Pod 序号满足 Replica 数量之后,Controller 会去查看是否需要更新 Pod。这两步的区别在于,Manage pods in order 去查看所有的 Pod 是否满足序号;而后者 Update in order 查看 Pod 期望的版本是否符合要求,并且通过序号来更新。

Update in order 更新过程如上图所示,其实这个过程比较简单,就是删除 Pod。删除 Pod 之后,其实是在下一次触发事件,Controller 拿到这个 success 之后会发现缺少 Pod,然后再从前一个步骤 Manage pods in order 中把新的 Pod 创建出来。在这之后 Controller 会做一次 Update status,也就是通过命令行可以看到的 StatefulSet 的 status 信息。

通过这样的一个完整流程,StatefulSet 达到了管理有状态应用的能力。

  • 扩缩容管理策略:

如果不想按照序号创建和删除 PodStatefulSet 也支持其它的创建和删除 Pod 的策略,这也就是为什么社区有些人把无状态应用也通过 StatefulSet 来管理。它的好处是 Pod 能拥有唯一的网络标识以及网络存储,同时也能通过并发的方式进行扩缩容。

StatefulSet.spec 中有个字段叫 podMangementPolicy 字段,该字段的可选策略为 OrderedReadyParallel,默认情况下为 OrderedReady

OrderedReady 情况下,扩缩容就严格按照 Order 顺序来执行,必须要等前面的 Pod 状态为 Ready 之后,才能扩容下一个 Pod。在缩容的时候,倒序删除,序号从大到小进行删除。

例如,从 Pod0 扩容到 Pod0、Pod1、Pod2 的时候,必须先创建 Pod1,等 Pod1 Ready 之后再创建 Pod2。其实还存在一种可能性:比如在创建 Pod1 的时候,Pod0 可能因为宿主机的原因或者应用本身的原因,Pod0 变成 NotReady 状态。这时 Controller 也不会继续创建 Pod2,所以不只是创建 Pod 的前一个 PodReady,而是前面所有的 Pod 都要 Ready 之后,才会创建下一个 Pod。如果要创建 Pod2,那么 Pod0、Pod1 都要 ready

另一种策略叫做 Parallel,即并行扩缩容,不需要等前面的 PodReady 或者删除后再处理下一个。

  • 发布模拟:

在这里插入图片描述

假设这里的 StatefulSet template1 对应逻辑上的 Revision1,这时 StatefulSet 下面的三个 Pod 都属于 Revision1 版本。在修改了 template,比如修改了镜像之后,Controller 是通过倒序的方式逐一升级 Pod。上图中可以看到 Controller 先创建了一个 Revision2,对应的就是创建了 ControllerRevision2 这么一个资源,并且将 ControllerRevision2 这个资源的 name 作为一个新的 Revision hash。在把 Pod2 升级为新版本后,逐一删除 Pod0、Pod1,再去创建 Pod0、Pod1。

它的逻辑其实很简单,在升级过程中 Controller 会把序号最大并且符合条件的 Pod 删除掉,那么删除之后在下一次 Controller 在做 reconcile 的时候,它会发现缺少这个序号的 Pod,然后再按照新版本把 Pod 创建出来。

  • Spec 字段解析:

StatefulSet 中有这些重要的 Spec 字段:

Replicas:期望的副本数

Selector:标签选择器,必须匹配 spec.template.metadata.labels 中定义的条件

Template:Pod 模板,定义了所要创建的 Pod 的基础信息模板

VolumeClaimTemplates:PVC 模板列表,如果在 spec 中定义了这个,PVC 会先于 Pod 模板 Template 进行创建。
                      在 PVC 创建完成后,把创建出来的 PVC name 作为一个 volume 注入到根据 Template 创建出来的 Pod 中

ServiceName:对应 headless Service 的名字。Controller 虽然不会做校验,但推荐每个 Service 都要配置一个 headless Service,
             不管 StatefulSet 下的 Pod 是否需要网络标识

PodManagementPolicy:Pod 管理策略。这个字段的可选策略为 OrderedReady 和 Parallel,默认情况下为 OrderedReady

UpdateStrategy:Pod 升级策略

RevisionHistoryLimit:保留历史 ControllerRevision 的数量限制(默认为 10)。需要注意的是,这里清除的版本,必须没有相关的 Pod 对应这些版本,
                      如果有 Pod 还在这个版本中,这个 ControllerRevision 是不能被删除的
  • 升级策略字段解析:

StatefulSet .spec.updateStrategy 有个 type 字段,这个 type 定义了两个类型:RollingUpdateOnDelete

RollingUpdate:与 Deployment 中的升级是有点类似的,就是根据滚动升级的方式来升级

OnDelete:在删除的时候升级,叫做禁止主动升级,Controller 并不会把存活的 Pod 做主动升级,而是通过 OnDelete 的方式

比如说当前有 3 个旧版本的 Pod,但是升级策略是 OnDelete,所以当更新 spec 中镜像的时候,Controller 并不会把 3 个 Pod 逐一升级为新版本,而是当缩小 Replicas 的时候,Controller 会先把 Pod 删除掉,当下一次再进行扩容的时候,Controller 才会扩容出来新版本的 Pod

此外,Partition 表示滚动升级时,保留旧版本 Pod 的数量。并不是灰度发布中更新为新版本的 Pod 数量,这是错误的。

例如,假设当前有个 replicas 为 10 的 StatefulSet,当更新版本时,如果 Partition 是 8,并不是表示要把 8 个 Pod 更新为新版本,而是表示需要保留 8 个 Pod 为旧版本,只更新 2 个新版本作为灰度。当 replicas 为 10 的时候,下面的 Pod 序号为 [0,9),因此当配置 Partition 为 8 的时候,其实还是保留 [0,7) 这 8个 Pod 为旧版本,只有 [8,9) 进入新版本。

总结一下,假设 replicas=N,Partition=M (M<N) ,则最终旧版本 Pod 为 [0,M) ,新版本 Pod 为 [M,N)。通过配置 Partition 的方式来达到灰度升级的目的,这是目前 Deployment 所不支持的。

  • 总结:

StatefulSet 是 Kubernetes 中常见的一种资源对象,其初始目标是面向有状态应用部署,但也支持部署无状态应用;

Deployment 不同,StatefulSet 是直接操作 Pod 来做扩缩容和发布,并没有通过类似 ReplicaSet 的中间资源对象来管控。

StatefulSet 的特点是:支持每个 Pod 独享 PVC、有一个唯一网络标识,且在升级发布后还能复用 PVC 和网络标识。


猜你喜欢

转载自blog.csdn.net/miss1181248983/article/details/112969614
18.