作者:酒祝(王思宇)

云原生运用主动化办理套件、CNCF Sandbox 项目 — OpenKruise,近期发布了 v1.1 版别。

OpenKruise [1] 是针对 Kubernetes 的增强能力套件,聚焦于云原生运用的部署、晋级、运维、稳定性防护等领域。一切的功用都经过 CRD 等规范方法扩展,能够适用于 1.16 以上版别的恣意 Kubernetes 集群。单条 helm 命令即可完结 Kruise 的一键部署,无需更多装备。******

版别解析

在 v1.1 版别中,OpenKruise 对不少已有功用做了扩展与增强,并且优化了在大规划集群中的运转性能。以下对 v1.1 的部分功用做简要介绍。

值得注意的是,OpenKruise v1.1 现已将 Kubernetes 代码依赖版别晋级到 v1.22,这意味着用户能够在 CloneSet 等工作负载的 pod template 模板中运用 up to v1.22 的新字段等, 但用户装置运用 OpenKruise 所兼容的 Kubernetes 集群版别依然保持在 >= v1.16。

原地晋级支撑容器次序优先级

上一年底发布的 v1.0 版别,OpenKruise 引入了容器发动次序操控 [2] 功用, 它支撑为一个 Pod 中的多个容器界说不同的权重关系,并在 Pod 创立时依照权重来操控不同容器的发动次序。

在 v1.0 中,这个功用仅仅能够作用于每个 Pod 的创立阶段。当创立完结后,假如对 Pod 中多个容器做原地晋级,则这些容器都会被一起履行晋级操作。

最近一段时间,社区与 LinkedIn 等公司做过一些交流,获得了更多用户运用场景的输入。在一些场景下,Pod 中多个容器存在相关关系,例如事务容器晋级的一起,Pod 中其他一些容器也需求晋级装备然后相关到这个新版别;或是多个容器防止并行晋级,然后确保如日志采集类的 sidecar 容器不会丢掉事务容器中的日志等。

因而,在 v1.1 版别中 OpenKruise 支撑了按容器优先级次序的原地晋级。在实际运用过程中,用户无需装备任何额定参数,只需 Pod 在创立时现已带有了容器发动优先级,则不仅在 Pod 创立阶段,会确保高优先级容器先于低优先级容器发动;并且在单次原地晋级中,假如一起晋级了多个容器,会先晋级高优先级容器,等候它晋级发动完结后,再晋级低优先级容器。

这儿的原地晋级,包含修正 image 镜像晋级与修正 env from metadata 的环境变量晋级,详见原地晋级介绍[3] 总结来说:

  • 关于不存在容器发动次序的 Pod,在多容器原地晋级时没有次序确保。

  • 关于存在容器发动次序的 Pod:

  • 假如本次原地晋级的多个容器具有不同的发动次序,会按发动次序来操控原地晋级的先后次序。

  • 假如本地原地晋级的多个容器的发动次序相同,则原地晋级时没有次序确保。

例如,一个包含两个不同发动次序容器的 CloneSet 如下:

apiVersion: apps.kruise.io/v1alpha1
kind: CloneSet
metadata:
  ...
spec:
  replicas: 1
  template:
    metadata:
      annotations:
        app-config: "... config v1 ..."
    spec:
      containers:
      - name: sidecar
        env:
        - name: KRUISE_CONTAINER_PRIORITY
          value: "10"
        - name: APP_CONFIG
          valueFrom:
            fieldRef:
              fieldPath: metadata.annotations['app-config']
      - name: main
        image: main-image:v1
  updateStrategy:
    type: InPlaceIfPossible

当咱们更新 CloneSet,将其间 app-config annotation 和 main 容器的镜像修正后, 意味着 sidecar 与 main 容器都需求被更新,Kruise 会先原地晋级 Pod 来将其间 sidecar 容器重建来生效新的 env from annotation。

接下来,咱们能够在已晋级的 Pod 中看到apps.kruise.io/inplace-update-stateannotation 和它的值:

{
  "revision": "{CLONESET_NAME}-{HASH}",         // 本次原地晋级的目标 revision 姓名
  "updateTimestamp": "2022-03-22T09:06:55Z",    // 整个原地晋级的初次开端时间
  "nextContainerImages": {"main": "main-image:v2"},                // 后续批次中还需求晋级的容器镜像
  // "nextContainerRefMetadata": {...},                            // 后续批次中还需求晋级的容器 env from labels/annotations
  "preCheckBeforeNext": {"containersRequiredReady": ["sidecar"]},  // pre-check 检查项,符合要求后才能原地晋级后续批次的容器
  "containerBatchesRecord":[
    {"timestamp":"2022-03-22T09:06:55Z","containers":["sidecar"]}  // 已更新的首个批次容器(它仅仅表明容器的 spec 现已被更新,例如 pod.spec.containers 中的 image 或是 labels/annotations,但并不代表 node 上真实的容器现已晋级完结了)
  ]
}

当 sidecar 容器晋级成功之后,Kruise 会接着再晋级 main 容器。最终你会在 Pod 中看到如下的apps.kruise.io/inplace-update-stateannotation:

{
  "revision": "{CLONESET_NAME}-{HASH}",
  "updateTimestamp": "2022-03-22T09:06:55Z",
  "lastContainerStatuses":{"main":{"imageID":"THE IMAGE ID OF OLD MAIN CONTAINER"}},
  "containerBatchesRecord":[
    {"timestamp":"2022-03-22T09:06:55Z","containers":["sidecar"]},
    {"timestamp":"2022-03-22T09:07:20Z","containers":["main"]}
  ]
}

一般来说,用户只需求重视其间containerBatchesRecord来确保容器是被分为多批晋级的。假如这个 Pod 在原地晋级的过程中卡住了,你能够检查nextContainerImages/nextContainerRefMetadata字段,以及preCheckBeforeNext中前一次晋级的容器是否现已晋级成功并 ready 了。

StatefulSetAutoDeletePVC 功用

从Kubernetesv1.23开端,原生的StatefulSet参加了StatefulSetAutoDeletePVC功用,即根据给定战略来挑选保存或主动删去StatefulSet创立的PVC目标,参阅文档 [4]

因而,v1.1版别的AdvancedStatefulSet从上游同步了这个功用,答运用户经过.spec.persistentVolumeClaimRetentionPolicy字段来指定这个主动整理战略。这需求你在装置或晋级 Kruise 的时分,启用StatefulSetAutoDeletePVCfeature-gate 功用。

apiVersion: apps.kruise.io/v1beta1
kind: StatefulSet
spec:
  ...
  persistentVolumeClaimRetentionPolicy:  # optional
    whenDeleted: Retain | Delete
    whenScaled: Retain | Delete

其间,两个战略字段包含:

  • whenDeleted:当 Advanced StatefulSet 被删去时,对 PVC 的保存/删去战略。
  • whenScaled:当 Advanced StatefulSet 发生缩容时,对缩容 Pod 相关 PVC 的保存/删去战略。

每个战略都能够装备以下两种值:

  • Retain(默许值):它的行为与曩昔 StatefulSet 相同,在 Pod 删去时对它相关的 PVC 做保存。
  • Delete:当 Pod 删去时,主动删去它所相关的 PVC 目标。

除此之外,还有几个注意点:

  1. StatefulSetAutoDeletePVC 功用只会整理由volumeClaimTemplate中界说和创立的 PVC,而不会整理用户自己创立或相关到 StatefulSet Pod 中的 PVC。
  2. 上述整理只发生在 Advanced StatefulSet 被删去或主动缩容的状况下。例如 node 故障导致的 Pod 驱赶重建等,依然会复用已有的 PVC。

Advanced DaemonSet 重构并支撑生命周期钩子

早先版别的 Advanced DaemonSet 实现与上游操控器差异较大,例如关于 not-ready 和 unschedulable 的节点需求额定装备字段来挑选是否处理,这关于咱们的用户来说都增加了运用成本和负担。

在 v1.1 版别中,咱们对 Advanced DaemonSet 做了一次小重构,将它与上游操控器从头做了对齐。因而,Advanced DaemonSet 的一切默许行为会与原生 DaemonSet 基本共同,用户能够像运用 Advanced StatefulSet 相同,经过修正apiVersion就能很方便地将一个原生 DaemonSet 修正为 Advanced DaemonSet 来运用。

另外,咱们还为 Advanced DaemonSet 增加了生命周期钩子,首先支撑 preDelete hook,来答运用户在 daemon Pod 被删去前履行一些自界说的逻辑。

apiVersion: apps.kruise.io/v1alpha1
kind: DaemonSet
spec:
  ...
  # define with label
  lifecycle:
    preDelete:
      labelsHandler:
        example.io/block-deleting: "true"

当 DaemonSet 删去一个 Pod 时(包含缩容和重建晋级):

  • 假如没有界说 lifecycle hook 或许 Pod 不符合 preDelete 条件,则直接删去。
  • 否则,会先将 Pod 更新为PreparingDelete状态,并等候用户自界说的 controller 将 Pod 中相关的 label/finalizer 去除,再履行 Pod 删去。

Disable DeepCopy 性能优化

默许状况下,咱们在运用 controller-runtime 来编写 Operator/Controller 时, 运用其间sigs.k8s.io/controller-runtime/pkg/clientClient客户端来 get/list 查询目标(typed),都是从内存 Informer 中获取并回来,这是大部分人都知道的。

但很多人不知道的是,在这些 get/list 操作背后,controller-runtime 会将从 Informer 中查到的一切目标做一次 deep copy 深复制后再回来。

这个设计的初衷,是防止开发者过错地将 Informer 中的目标直接篡改。在深复制之后,不管开发者对 get/list 回来的目标做了任何修正,都不会影响到 Informer 中的目标,后者只会从 kube-apiserver 的 ListWatch 恳求中同步。

但是在一些很大规划的集群中,OpenKruise 中各个操控器一起在运转,一起每个操控器还存在多个 worker 履行 Reconcile,可能会带来很多的 deep copy 操作。例如集群中有很多运用的 CloneSet,而其间一些 CloneSet 下办理的 Pod 数量十分多,则每个 worker 在 Reconcile 的时分都会 list 查询一个 CloneSet 下的一切 Pod 目标,再加上多个 worker 并行操作, 可能造成 kruise-manager 瞬时的 CPU 和 Memory 压力陡增,甚至在内存配额缺乏的状况下有发生 OOM 的风险。

在上游的 controller-runtime 中,我在上一年现已提交合并了DisableDeepCopy 功用 [5] ,包含在 controller-runtime v0.10 及以上的版别。它答应开发者指定某些特定的资源类型,在做 get/list 查询时不履行深复制,而是直接回来 Informer 中的目标指针

例如下述代码,在 main.go 中初始化 Manager 时,为 cache 参加参数即可装备 Pod 等资源类型不做深复制。

mgr, err := ctrl.NewManager(cfg, ctrl.Options{
        ...
        NewCache: cache.BuilderWithOptions(cache.Options{
            UnsafeDisableDeepCopyByObject: map[client.Object]bool{
                &v1.Pod{}: true,
            },
        }),
    })

但在Kruisev1.1版别中,咱们没有挑选直接运用这个功用,而是将DelegatingClient [6]从头做了封装,然后使得开发者能够在恣意做list查询的当地经过DisableDeepCopyListOption来指定单次的 list 操作不做深复制。

if err := r.List(context.TODO(), &podList, client.InNamespace("default"), utilclient.DisableDeepCopy); err != nil {
        return nil, nil, err
    }

这样做的优点是运用上更加灵敏,防止为整个资源类型关闭深复制后,很多社区贡献者在参加开发的过程中假如没有注意到则可能会过错修正 Informer 中的目标。

其他改动

你能够经过Github release [7]页面,来检查更多的改动以及它们的作者与提交记录。

社区参加

十分欢迎你经过 Github/Slack/钉钉/微信 等方法参加咱们来参加 OpenKruise 开源社区。你是否现已有一些期望与咱们社区交流的内容呢?能够在咱们的社区双周会 [8] 上分享你的声响,或经过以下途径参加讨论:

  • 参加社区Slack channel [9] (English)
  • 参加社区钉钉群:查找群号23330762(Chinese)
  • 参加社区微信群(新):添加用户openkruise并让机器人拉你入群 (Chinese)

相关链接​* *​

[1]OpenKruise

​​https://openkruise.io/​​

[2]容器发动次序操控

​​https://openkruise.io/zh/docs/user-manuals/containerlaunchpriority/​​

[3]原地晋级介绍

​​https://openkruise.io/zh/docs/core-concepts/inplace-update​​

[4]参阅文档

​​https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#persistentvolumeclaim-retention​​

[5]DisableDeepCopy 功用

​​https://github.com/kubernetes-sigs/controller-runtime/pull/1274​​

[6]Delegating Client

​​https://github.com/openkruise/kruise/blob/master/pkg/util/client/delegating_client.go​​

[7]Github release

​​https://github.com/openkruise/kruise/releases​​

[8]社区双周会

​​https://shimo.im/docs/gXqmeQOYBehZ4vqo​​

[9]Slack channel

​​https://kubernetes.slack.com/?redir=%2Farchives%2Fopenkruise​​

​点击​此处​,检查 OpenKruise 项目官方主页与文档!​