乐趣区

关于云原生:OpenKruise-v090-版本发布新增-Pod-重启删除防护等重磅功能

作者 | 王思宇(酒祝)
Photo Creidt@ 王思宇(酒祝)

背景


OpenKruise 是阿里云开源的云原生利用自动化治理套件,也是以后托管在 Cloud Native Computing Foundation (CNCF) 下的 Sandbox 我的项目。它来自阿里巴巴多年来容器化、云原生的技术积淀,是阿里外部生产环境大规模利用的基于 Kubernetes 之上的规范扩大组件,也是紧贴上游社区规范、适应互联网规模化场景的技术理念与最佳实际。

OpenKruise 在 2021 年 5 月 20 日公布了最新的 v0.9.0 版本(ChangeLog​),新增了 Pod 容器重启、资源级联删除防护等重磅性能,本文以下对新版本做整体的概览介绍。

Pod 容器重启 / 重建


“重启”是一个很奢侈的需要,即便日常运维的诉求,也是技术畛域较为常见的“复原伎俩”。而在原生的 Kubernetes 中,并没有提供任何对容器粒度的操作能力,Pod 作为最小操作单元也只有创立、删除两种操作形式。

有的同学可能会问,在云原生时代,为什么用户还要关注容器重启这种运维操作呢?在现实的 Serverless 模式下,业务只须要关怀服务本身就好吧?

这来自于云原生架构和过来传统根底基础设施的差异性。在传统的物理机、虚拟机时代,一台机器上往往会部署和运行多个利用的实例,并且机器和利用的生命周期是不同的;在这种状况下,利用实例的重启可能仅仅是一条 systemctl 或 supervisor 之类的指令,而无需将整个机器重启。然而,在容器与云原生模式下,利用的生命周期是和 Pod 容器绑定的;即惯例状况下,一个容器只运行一个利用过程,一个 Pod 也只提供一个利用实例的服务。

基于上述的限度,目前原生 Kubernetes 之下是没有 API 来为下层业务提供容器(利用)重启能力的。而 Kruise v0.9.0 版本提供了一种单 Pod 维度的容器重启能力,兼容 1.16 及以上版本的规范 Kubernetes 集群。在装置或降级 Kruise​ 之后,只须要创立 ContainerRecreateRequest(简称 CRR)对象来指定重启,最简略的 YAML 如下:

apiVersion: apps.kruise.io/v1alpha1
kind: ContainerRecreateRequest
metadata:
  namespace: pod-namespace
  name: xxx
spec:
  podName: pod-name
  containers:
  - name: app
  - name: sidecar


其中,namespace 须要与要操作的 Pod 在同一个命名空间,name 可自选。spec 中 podName 是 Pod 名字,containers 列表则能够指定 Pod 中一个或多个容器名来执行重启。

除了上述必选字段外,CRR 还提供了多种可选的重启策略:

spec:
  # ...
  strategy:
    failurePolicy: Fail
    orderedRecreate: false
    terminationGracePeriodSeconds: 30
    unreadyGracePeriodSeconds: 3
    minStartedSeconds: 10
  activeDeadlineSeconds: 300
  ttlSecondsAfterFinished: 1800
  • failurePolicy:Fail 或 Ignore,默认 Fail;示意一旦有某个容器进行或重建失败,CRR 立刻完结。
  • orderedRecreate:默认 false;true 示意列表有多个容器时,等前一个容器重建实现了,再开始重建下一个。
  • terminationGracePeriodSeconds:期待容器优雅退出的工夫,不填默认用 Pod 中定义的工夫。
  • unreadyGracePeriodSeconds:在重建之前先把 Pod 设为 not ready,并期待这段时间后再开始执行重建。

    • 注:该性能依赖于 KruisePodReadinessGate 这个 feature-gate 要关上,后者会在每个 Pod 创立的时候注入一个 readinessGate。否则,默认只会给 Kruise workload 创立的 Pod 注入 readinessGate,也就是说只有这些 Pod 能力在 CRR 重建时应用 unreadyGracePeriodSeconds。
  • minStartedSeconds:重建后新容器至多放弃运行这段时间,才认为该容器重建胜利。
  • activeDeadlineSeconds:如果 CRR 执行超过这个工夫,则间接标记为完结(未实现的容器标记为失败)。
  • ttlSecondsAfterFinished:CRR 完结后,过了这段时间主动被删除掉。

实现原理:当用户创立了 CRR 后,通过了 kruise-manager 核心端的初步解决,会被 Pod 所在节点上的 kruise-daemon 收到并开始执行。执行的过程如下:

  1. 如果 Pod 容器定义了 preStop,kruise-daemon 会先走 CRI 运行时 exec 到容器中执行 preStop。
  2. 如果没有 preStop 或执行实现,kruise-daemon 调用 CRI 接口将容器进行。
  3. kubelet 感知到容器退出,则会新建一个“序号”递增的新容器,并开始启动(以及执行 postStart)。
  4. kruise-daemon 感知到新容器启动胜利,上报 CRR 重启实现。

上述的容器“序号”其实就对应了 Pod status 中 kubelet 上报的 restartCount。因而,在容器重启后会看到 Pod 的 restartCount 减少。另外,因为容器产生了重建,之前长期写到旧容器 rootfs 中的文件会失落,然而 volume mount 挂载卷中的数据依然存在。

级联删除防护


Kubernetes 的面向终态自动化是一把“双刃剑”,它既为利用带来了申明式的部署能力,同时也潜在地会将一些误操作行为被终态化放大。例如它的“级联删除”机制,即失常状况(非 orphan 删除)下一旦父类资源被删除,则所有子类资源都会被关联删除:

  1. 删除一个 CRD,其所有对应的 CR 都被清理掉。
  2. 删除一个 namespace,这个命名空间下包含 Pod 在内所有资源都被一起删除。
  3. 删除一个 workload(Deployment/StatefulSet/…),则上司所有 Pod 被删除。

相似这种“级联删除”带来的故障,咱们曾经听到不少社区 K8s 用户和开发者带来的埋怨。对于任何一家企业来说,其生产环境产生这种规模误删除都是不可接受之痛,阿里巴巴也不例外。

因而,在 Kruise v0.9.0 版本中,咱们将阿里外部所做的防级联删除能力输入到社区,冀望能为更多的用户带来稳定性保障。在以后版本中如果须要应用该性能,则在装置或降级 Kruise 的时候须要显式关上 ResourcesDeletionProtection 这个 feature-gate。

对于须要防护删除的资源对象,用户能够给其打上 policy.kruise.io/delete-protection 标签,value 能够有两种:

  • Always: 示意这个对象禁止被删除,除非上述 label 被去掉。
  • Cascading:这个对象如果还有可用的上司资源,则禁止被删除。

目前反对的资源类型、以及 cascading 级联关系如下:

CloneSet 新增性能

1. 删除优先级


controller.kubernetes.io/pod-deletion-cost​ 是从 Kubernetes 1.21 版本后退出的 annotation,ReplicaSet 在缩容时会参考这个 cost 数值来排序。CloneSet 从 Kruise v0.9.0 版本后也同样反对了这个性能。

用户能够把这个 annotation 配置到 pod 上,它的 value 数值是 int 类型,示意这个 pod 相较于同个 CloneSet 下其余 pod 的 “ 删除代价 ”,代价越小的 pod 删除优先级绝对越高。没有设置这个 annotation 的 pod 默认 deletion cost 是 0。

留神这个删除程序并不是强制保障的,因为实在的 pod 的删除相似于下述程序:

  1. 未调度 < 已调度
  2. PodPending < PodUnknown < PodRunning
  3. Not ready < ready
  4. 较小 pod-deletion cost < 较大 pod-deletion cost
  5. 处于 Ready 工夫较短 < 较长
  6. 容器重启次数较多 < 较少
  7. 创立工夫较短 < 较长

2. 配合原地降级的镜像预热


当应用 CloneSet 做利用原地降级时,只会降级容器镜像、而 Pod 不会产生重建。这就保障了 Pod 降级前后所在 node 不会发生变化,从而在原地降级的过程中,如果 CloneSet 提前在所有 Pod 节点上先把新版本镜像拉取好,则在后续的公布批次中 Pod 原地降级速度会失去大幅度提高。

在以后版本中如果须要应用该性能,则在装置或降级 Kruise 的时候须要显式关上 PreDownloadImageForInPlaceUpdate 这个 feature-gate。关上后,当用户更新了 CloneSet template 中的镜像、且公布策略反对原地降级,则 CloneSet 会主动为这个新镜像创立 ImagePullJob 对象(OpenKruise 提供的批量镜像预热性能),来提前在 Pod 所在节点上预热新镜像。

默认状况下 CloneSet 给 ImagePullJob 配置的并发度是 1,也就是一个个节点拉镜像。如果须要调整,你能够在 CloneSet annotation 上设置其镜像预热时的并发度:

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:
  annotations:
    apps.kruise.io/image-predownload-parallelism: "5"

3. 先扩再缩的 Pod 置换形式


在过来版本中,CloneSet 的 maxUnavailable、maxSurge 策略只对利用公布过程失效。而从 Kruise v0.9.0 版本开始,这两个策略同样会对 Pod 指定删除失效。

也就是说,当用户通过 podsToDeleteapps.kruise.io/specified-delete: true 形式(具体见官网文档)来指定一个或多个 Pod 冀望删除时,CloneSet 只会在以后不可用 Pod 数量(绝对于 replicas 总数)小于 maxUnavailable 的时候才执行删除。同时,如果用户配置了 maxSurge 策略,则 CloneSet 有可能 会先创立一个新 Pod、期待新 Pod ready、再删除指定的旧 Pod。

具体采纳什么样的置换形式,取决于过后的 maxUnavailable 和理论不可用 Pod 数量。比方:

  • 对于一个 CloneSet maxUnavailable=2, maxSurge=1 且有一个 pod-a 处于不可用状态,如果你对另一个 pod-b 指定删除,那么 CloneSet 会立刻删除它,而后创立一个新 Pod。
  • 对于一个 CloneSet maxUnavailable=1, maxSurge=1 且有一个 pod-a 处于不可用状态,如果你对另一个 pod-b 指定删除,那么 CloneSet 会先新建一个 Pod、期待它 ready,最初再删除 pod-b
  • 对于一个 CloneSet maxUnavailable=1, maxSurge=1 且有一个 pod-a 处于不可用状态,如果你对这个 pod-a 指定删除,那么 CloneSet 会立刻删除它,而后创立一个新 Pod。

4. 基于 partition 终态的高效回滚


在原生的 workload 中,Deployment 本身公布不反对灰度公布,StatefulSet 有 partition 语义来容许用户管制灰度降级的数量;而 Kruise workload 如 CloneSet、Advanced StatefulSet,也都提供了 partition 来反对灰度分批。

对于 CloneSet,Partition 的语义是 保留旧版本 Pod 的数量或百分比 。比如说一个 100 个正本的 CloneSet,在降级镜像时将 partition 数值阶段性改为 80 -> 60 -> 40 -> 20 -> 0,则实现了分 5 批次公布。

但过来,不论是 Deployment、StatefulSet 还是 CloneSet,在公布的过程中如果想要回滚,都必须将 template 信息(镜像)从新改回老版本。后两者在灰度的过程中,将 partition 调小会触发旧版本升级为新版本,但再次 partition 调大则不会解决。

从 v0.9.0 版本开始,CloneSet 的 partition 反对了“终态回滚”性能。如果在装置或降级 Kruise 的时候关上了 CloneSetPartitionRollback 这个 feature-gate,则当用户将 partition 调大时,CloneSet 会将对应数量的新版本 Pod 从新回滚到老版本。

这样带来的益处是不言而喻的:在灰度公布的过程中,只须要前后调节 partition 数值,就能灵便得管制新旧版本的比例数量。但须要留神的是,CloneSet 所根据的“新旧版本”对应的是其 status 中的 updateRevision 和 currentRevision:

  • updateRevision:对应以后 CloneSet 所定义的 template 版本。
  • currentRevision:该 CloneSet 前一次全量公布胜利 的 template 版本。

5. 短 hash


默认状况下,CloneSet 在 Pod label 中设置的 controller-revision-hash 值为 ControllerRevision 的残缺名字,比方:

apiVersion: v1
kind: Pod
metadata:
  labels:
    controller-revision-hash: demo-cloneset-956df7994

它是通过 CloneSet 名字和 ControllerRevision hash 值拼接而成。通常 hash 值长度为 8~10 个字符,而 Kubernetes 中的 label 值不能超过 63 个字符。因而 CloneSet 的名字个别是不能超过 52 个字符的,如果超过了,则无奈胜利创立出 Pod。

在 v0.9.0 版本引入了 CloneSetShortHash 新的 feature-gate。如果它被关上,CloneSet 只会将 Pod 中的 controller-revision-hash 的值只设置为 hash 值,比方 956df7994,因而 CloneSet 名字的长度不会有任何限度了。(即便启用该性能,CloneSet 依然会辨认和治理过来存量的 revision label 为残缺格局的 Pod。)

SidecarSet

sidecar 热降级性能


SidecarSet 是 Kruise 提供的独立治理 sidecar 容器的 workload。用户能够通过 SidecarSet,来在肯定范畴的 Pod 中注入和降级指定的 sidecar 容器。

默认状况下,sidecar 的独立原地降级是先进行旧版本的容器,而后创立新版本的容器。这种形式更加适宜不影响 Pod 服务可用性的 sidecar 容器,比如说日志收集 agent,然而对于很多代理或运行时的 sidecar 容器,例如 Istio Envoy,这种降级办法就有问题了。Envoy 作为 Pod 中的一个代理容器,代理了所有的流量,如果间接重启降级,Pod 服务的可用性会受到影响。如果须要独自降级 envoy sidecar,就须要简单的 grace 终止和协调机制。所以咱们为这种 sidecar 容器的降级提供了一种新的解决方案,即热降级(hot upgrade)。

apiVersion: apps.kruise.io/v1alpha1
kind: SidecarSet
spec:
  # ...
  containers:
  - name: nginx-sidecar
    image: nginx:1.18
    lifecycle:
      postStart:
        exec:
          command:
          - /bin/bash
          - -c
          - /usr/local/bin/nginx-agent migrate
    upgradeStrategy:
      upgradeType: HotUpgrade
      hotUpgradeEmptyImage: empty:1.0.0
  • upgradeType: HotUpgrade 代表该 sidecar 容器的类型是 hot upgrade,将执行热降级计划 hotUpgradeEmptyImage: 当热降级 sidecar 容器时,业务必须要提供一个 empty 容器用于热降级过程中的容器切换。empty 容器同 sidecar 容器具备雷同的配置(除了镜像地址),例如:command, lifecycle, probe 等,然而它不做任何工作。
  • lifecycle.postStart: 状态迁徙,该过程实现热降级过程中的状态迁徙,该脚本须要由业务依据本身的特点自行实现,例如:nginx 热降级须要实现 Listen FD 共享以及流量排水(reload)。

具体 sidecar 注入和热降级流程,请参考官网文档。

最初


理解上述能力的更多信息,能够拜访官网文档。对 OpenKruise 感兴趣的同学欢送参加咱们的社区建设,曾经应用了 OpenKruise 我的项目的用户请在 issue​ 中注销。

钉钉搜寻群号 23330762 退出钉钉交换群!

退出移动版