乐趣区

关于阿里云:OpenKruise-v11功能增强与上游对齐大规模场景性能优化

作者:酒祝(王思宇)

云原生利用自动化治理套件、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-state annotation 和它的值:

{"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-state annotation:

{"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 性能

从 Kubernetes v1.23 开始,原生的 StatefulSet 退出了 StatefulSetAutoDeletePVC 性能,即 依据给定策略来抉择保留或主动删除 StatefulSet 创立的 PVC 对象,参考文档 [4]

因而,v1.1 版本的 Advanced StatefulSet 从上游同步了这个性能,容许用户通过 .spec.persistentVolumeClaimRetentionPolicy 字段来指定这个主动清理策略。这须要你在装置或降级 Kruise 的时候,启用 StatefulSetAutoDeletePVC feature-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/client Client 客户端来 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,
            },
        }),
    })

但在 Kruise v1.1 版本中,咱们没有抉择间接应用这个性能,而是将 Delegating Client [6] 从新做了封装,从而使得开发者能够在任意做 list 查问的中央通过 DisableDeepCopy ListOption 来指定单次的 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 我的项目官方主页与文档!​

退出移动版