OpenKruise v1.1:功能增强与上游对齐,大规模场景性能优化

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
函数计算FC,每月15万CU 3个月
应用实时监控服务-用户体验监控,每月100OCU免费额度
简介: 在 v1.1 版本中,OpenKruise 对不少已有功能做了扩展与增强,并且优化了在大规模集群中的运行性能。以下对 v1.1 的部分功能做简要介绍。

作者:酒祝(王思宇)


云原生应用自动化管理套件、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 项目官方主页与文档!


相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
Web App开发 弹性计算 编解码
最佳实践:如何扩展你的SRS并发能力?
当我们用SRS快速搭建了视频服务,业务也开始上线运行了,很快就会遇到一个问题:如何支持更多的人观看?如何支持更多的人推流?这本质上就是系统的水平扩展能力,SRS当然是支持的,而且有多种扩展的方法,这篇文章就就详细分析各种扩展的方案,以及各种方案的应用场景和优缺点。
2545 0
最佳实践:如何扩展你的SRS并发能力?
|
20天前
|
存储 数据采集 物联网
TDengine 集群能力:超越 InfluxDB 的水平扩展与开源优势
随着物联网、车联网等领域的快速发展,企业所面临的数据采集量呈爆炸式增长,这对 IT 基础设施和数据库提出了严峻挑战。传统单机版数据库逐渐无法应对高并发的数据写入和复杂的查询需求。因此,底层数据库必须具备水平扩展能力,以确保其能够在数据量持续增长的情况下高效运行。
26 0
|
4月前
|
运维 Prometheus Kubernetes
kubevela可观测体系问题之KubeVela 的可观测性能力通过插件机制进行扩展的问题如何解决
kubevela可观测体系问题之KubeVela 的可观测性能力通过插件机制进行扩展的问题如何解决
|
4月前
|
存储 缓存 监控
通用研发提效问题之动态调整干预能力,如何解决
通用研发提效问题之动态调整干预能力,如何解决
|
3月前
|
Serverless Cloud Native 关系型数据库
Serverless集群资源随业务负载动态弹降特性的重点评测
Serverless集群资源随业务负载动态弹降特性的重点评测
|
4月前
|
固态存储 测试技术 数据库
最新测试揭秘:TDengine 线性扩展能力能否满足你的性能需求?
**摘要:** TDengine 的线性扩展能力通过CPU和磁盘测试得以验证。在CPU测试中,使用TDengine V3.3.0.0和taosBenchmark,随着CPU核数从4增至12,写入性能线性提升,每个CPU增加对应约50W条/秒的提升,保持CPU在瓶颈状态。磁盘IO测试中,使用低速机械盘,增加磁盘数量导致写入性能成比例增长,充分利用新增磁盘IO。测试结果表明,无论CPU还是磁盘扩展,TDengine都能有效利用资源,展示出出色的线性扩展能力。
63 0
|
6月前
|
消息中间件 缓存 负载均衡
构建高性能的后端服务:优化策略与实践
在当今互联网时代,构建高性能的后端服务至关重要。本文将深入探讨如何通过优化策略与实践来提升后端服务的性能。我们将从数据库优化、缓存策略、异步处理和负载均衡等方面展开讨论,帮助开发者构建出稳定、高效的后端架构。
153 2
|
Kubernetes API 调度
【k8s系列5】KubernetesClientException: too old resource version 原因分析
【k8s系列5】KubernetesClientException: too old resource version 原因分析
1530 0
|
6月前
|
监控 NoSQL 测试技术
构建高性能后端服务的关键因素与最佳实践
本文将介绍构建高性能后端服务的关键因素和最佳实践,包括服务器选型、数据库设计、代码优化等方面,帮助开发人员在后端开发中提升性能并满足高并发需求。
200 15
|
6月前
|
SQL 架构师 大数据
提升企业级数据处理效率!3.0 系列版本的四个集群优化点详解
为了帮助企业更好地进行大数据处理,我们在此前 TDengine 3.x 系列版本中进行了几项与集群相关的优化和新功能开发,本文将对这几项重要优化进行详细阐述。
92 0