![Katalyst 如何基于 NRI 实现精细化且可插拔的容器资源管理](https://static001.infoq.cn/resource/image/7f/9d/7fbe4f73b698a0e33de40ffae43f7b9d.png)
来源 | KubeWharf 社区
项目 | github.com/kubewharf/katalyst-core
Katalyst 是字节跳动开源的精细化资源管理项目,通过在离线业务混合部署、拓扑感知调度、资源超分、潮汐混部等方式提升 Kubernetes 集群的资源利用率和工作负载的性能。
自 2023 年开源以来,社区不断对其进行优化和迭代,在近期发布的 Katalyst v0.5.0 中,社区对 Katalyst 进行了重构,将 QoS Resource Manager (QRM) 框架从 kubelet 中解耦出来,实现了一套新的方案——Out-of-Band Resource Manager (ORM)。
本文将介绍 Katalyst 如何通过 NRI 无侵入地对容器的 CPU、内存、IO 和网络等资源实现精细化且可插拔的管理,从而在提高资源利用率的同时,保证业务的 SLO 不受影响。这种全新的架构使得 Katalyst 可以基于原生 Kubernetes 部署,方便使用和维护。此外,NRI 可以在容器创建的过程中同步地为其注入资源分配策略,也解决了旁路异步更新方案实时性较差的问题。
Katalyst 简介
Katalyst 来自字节跳动在管理大规模 Kubernetes 集群的过程中遇到的资源管理方面的挑战:在线业务的资源使用量情况通常呈现潮汐现象,晚高峰时用户量激增,对资源消耗较大;而凌晨用户量相对较少,资源消耗也会比较低。如下图所示,业务资源使用在凌晨时分有比较严重的浪费现象:
![](https://static001.geekbang.org/infoq/0d/0d4fe95f14e2ad95a0a38bf9c37abffd.png)
另外,为了保证服务的稳定性,业务方通常会有过度申请资源的倾向,这部分过度申请的资源也会带来巨大的浪费。
字节跳动内部有非常多类型的业务,通过分析技术团队发现在线业务相对更看重 CPU 和 RPC 时延,离线作业更看重内存和吞吐,它们对资源的使用模式天然互补。因此一个解决方案诞生了:当在线业务处于低峰期时,通过离线作业去填补这部分资源使用的空白,从而实现削峰填谷,提高天级的资源利用率。
在这个背景下,随着在离线混部在字节跳动内部逐渐落地,技术团队也积攒了大量精细化资源管理的经验,并抽象和总结出了开源项目 Katalyst。Katalyst 引申自 “Catalyst”,意为化学反应中的催化剂,旨在为运行在 Kubernetes 之上的工作负载提供更加强劲的资源管理能力。
插件化的资源管理机制
在企业落地在离线混部的过程中,当在线业务和离线作业跑在同一个节点上时,它们之间可能会有一些相互的干扰。因此工程师首先要做的就是对不同的业务类型进行分级,也就是 QoS 等级。QoS 等级在多个维度反映了服务对资源质量的要求,比如在资源隔离上的一些配置,以及在资源竞争比较激烈时,驱逐时的顺序等。
![](https://static001.geekbang.org/infoq/ef/efeb43ce2f59712a9af71ab21ef18b30.png)
Katalyst 扩展了 4 个与 Kubernetes 原生的 QoS 正交的 QoS 等级,分别为:
dedicated_cores,指独占 CPU 核心,不与其他负载进行共享。在此基础之上,我们进一步支持了绑定 NUMA 这样一种增强的语义,可以得到更极致的性能。dedicated_cores 比较适合对延迟极度敏感的业务,比如搜广推的在线服务。映射到 K8s 的 QoS 等级,就是 Guaranteed。
shared_cores,这种 QoS 的负载运行在一个共享的 CPU 资源池。在此基础之上,我们支持根据业务类型拆分更细粒度的资源池。shared_cores 比较适合能够容忍一定程度 CPU 限流或者干扰的服务,典型的就是微服务。
reclaimed_cores,指超卖出来的资源。这类资源的稳定性相对没有保障,这些 Pod 甚至可能会被驱逐。这种 QoS 等级适合对延迟不敏感而比较在乎吞吐的业务,如模型训练和大数据业务的离线作业。
system_cores,指预留的资源核心,用于保障系统组件的稳定性。
QRM 框架:使 kubelet 的资源管理策略可扩展
通过上述 4 种扩展的 QoS 等级,Katalyst 可以为业务提供更加精细化的资源管理策略。但是我们也知道,kubelet 的 CPU Manager 和 Memory Manager 对 CPU 和内存的管理策略比较朴素,且没有提供扩展机制。因此,我们实现了一套扩展的方案:
![](https://static001.geekbang.org/infoq/ae/ae39c67ba705a674c5e5186e08494fc4.png)
首先,我们借鉴了 Device Manager 的设计,在 kubelet 中引入了一个与 Device Manager 同级的 QoS Resource Manager (QRM) 框架,用于取代 CPU Manager 和 Memory Manager,以插件化的方式来扩展 CPU 和内存的分配策略。
其次,我们将 QRM 注册为 Topology Manager 的一个 Hint Provider,从而借助 Topology Manager 去做 NUMA 的撮合,实现 NUMA 亲和的分配策略。
最后,在混部场景下,除了需要支持在容器首次创建时定制化资源分配策略,还需要支持在容器运行的过程中动态调整在离线的容器资源分配,我们通过让 QRM 周期性地调用运行时来实现资源调整。
第二部分就是 QRM 框架的 Plugin,我们通过插件化的方式定制了对 CPU、内存、网络这些维度的资源分配策略。这种方式非常方便工程师去做运维,当需要去修改策略时,只需要更新 Katalyst Agent 这个 DaemonSet 即可,不需要更新 kubelet。
但是在用户落地 Katalyst 的过程中,我们也发现了 QRM 方案的一些局限:QRM 框架是在 kubelet 中实现的,也就是说,QRM 框架对 Kubernetes 有侵入,用户必须搭配 KubeWharf Kubernetes 发行版一起使用。这样既不方便部署,也为 Kubernetes 代码的维护带来了一定的成本。
ORM 框架:将 QRM 框架从 kubelet 中解耦
基于以上反馈,近期我们对 Katalyst 进行了重构,将 QRM 框架从 kubelet 中解耦出来,实现了一套新的方案 —— Out-of-Band Resource Manager (ORM),即带外的资源管理框架。
![](https://static001.geekbang.org/infoq/cc/cc13b08b82867d9f06e5032edd57c6db.png)
从上图中可以看到,我们将 QRM 从 kubelet 中移除,在 Katalyst Agent 中新增了一个 ORM 模块。在注入资源管理策略的 Hook 点的选择上,不同于 QRM,ORM 支持两种模式:NRI 模式(v0.6.0 发布)和旁路异步更新模式。我们在 ORM 中实现了一个 NRI 的 Server,当容器的一些生命周期事件 (比如 RunPodSandbox、CreateContainer、RemovePodSandbox 等) 发生时,containerd 会同步调用 ORM 的 NRI Server,从而实现资源管理策略的注入。此外,对于 containerd 版本比较低的场景,ORM 也提供了一种 Bypass 模式,周期性从旁路异步更新容器的 Cgroup 等配置。
在 NUMA 亲和的实现方式上,由于 QRM 从 kubelet 中解耦出来后不能再复用 Topology Manager 的撮合能力,因此我们在 ORM 中实现了一个带外的 Topology Manager。另外解耦之后 kubelet 原生的 PodResources API 的数据也和实际的分配情况不一致了,所以我们在 ORM 中也实现了一个带外的 PodResources Server,从而像 Reporter 这样的模块可以从中获取到准确的 CPU 和内存的分配情况,上报到 KCNR CRD 中供调度器感知。
NRI 机制简介
Kubernetes QoS 资源管理实现方式
在介绍 NRI 插件运行机制之前,我们先回顾目前社区常用的对 Kubernetes 资源管理进行增强的一些实现方式。
![](https://static001.geekbang.org/infoq/6b/6b18774c887e0ba1b1b0b96d4185a6e3.png)
上图中左边部分是 Kubernetes 原生的工作流程。当工程师创建一个 Pod 时,kubelet 就会通过 CRI Request 调用 CRI 运行时,然后再调用底层的 OCI 运行时创建出 Pod。
右边是我们总结的目前常用的三种 Kubernetes 增强策略:
第一类方式称为 Proxy 模式。代表项目有 Intel 早期提出的 CRI Resource Manager。这种模式是在 kubelet 和 CRI 运行时之间增加一个 Proxy 组件。在这个 Proxy 组件中拦截必要的 Pod/容器的生命周期事件,对容器的资源进行细粒度分配。然后把修改后的 Container Spec 发送给 CRI 运行时,进行容器的创建。
这种模式对资源的修改是同步的,但是它打破了 Kubernetes 原有的流程。
第二种方式称为 Bypass 模式,也就是旁路模式。这种模式不需要对 Kubernetes 组件做任何修改。通过在每个节点上运行一个 Agent 旁路监听 Pod 事件。事件的来源可以是通过轮询当前节点的 kubelet 的 API,或者是通过 Cgroup 文件系统的 inotify 获取 Pod 事件,然后再通过直接修改 Cgroup 文件系统的方式,或者直接调用 CRI 运行时的接口对容器进行更新。
这种模式对资源的修改是异步的,是在容器创建之后再对容器的资源进行重新分配,会有一定的滞后。
第三种方式是对 Kubelet 进行增强,此前 Katalyst 的 QRM 机制就是采用的这种模式。在 kubelet 中实现插件化机制,调用可扩展的资源细粒度管理插件进行资源分配。这种方式在第一时间更新好了容器的资源配置,实现了对 Pod 资源的同步修改。但是这种模式需要使用特定的 K8s 发行版。如之前介绍,使用 QRM 机制需要使用 KubeWharf K8s 发行版。
目前这三类策略都存在一定的问题,要么是对 Kubernetes 原生的组件进行修改,导致不能直接使用上游版本,要么就是实时性不好。
什么是 NRI
NRI 全称 Node Resource Interface (节点资源接口),是 Intel 参与发起并积极推动的一个节点细粒度资源管理解决方案,目前在 containerd v1.7.0 之后的版本以及 CRI-O 的新版本中都可以使用 NRI。
![](https://static001.geekbang.org/infoq/62/62b1ce76579c5332f04de945f2be7f78.png)
NRI 本质上是一个 CRI 运行时插件扩展管理的通用框架。上图为基于 NRI 增强的 Kubernetes 的工作流程。图中红色部分为 NRI 组件,主要由两个部分组成,一个是集成在 CRI 运行时中的 Adaptation,另一个是用户自定义的 NRI 插件。Adaptation 和 NRI 插件之间通过 Unix Socket 进行通信。
NRI 为扩展插件提供了跟踪容器状态,并对其配置进行有限修改的基本机制。它几乎覆盖了 Pod/Container 所有的生命周期事件,对容器常用的修改主要有三部分:Annotation、环境变量、系统资源。
另外,NRI 插件是运行时无关的,基于 NRI 开发的插件既可以适用于 containerd,也可以适用于 CRI-O。
NRI 插件的运行机制
● 运行方式
NRI 插件有两种运行方式。一种是 NRI 插件作为一个独立的进程,通过 NRI Socket 与 CRI 运行时进行通信。这种情况下可以把 NRI 插件部署成为一个 DaemonSet,更方便在 Kubernetes 上管理。
另一种是可以把编译好的 NRI 插件二进制文件放在 containerd 的指定路径下,由 containerd 发起对 NRI 插件的调用,这种方式有点类似于 CNI 的机制。不过这种方式会存在一个问题,只有 containerd 在启动的时候才会尝试加载对应路径下的 NRI 插件。
● Hook 事件
NRI 可以 Hook 的 Pod 的生命周期事件有 3 个,Container 的事件有 8 个。
NRI 插件除了可以通过 Hook 的方式获取容器事件,NRI 提供了一个 stub.UpdateContainer 的方法也可以主动对容器进行更新。
NRI 在 Katalyst 中的应用
NRI 在 Katalyst 中主要是对 ORM 架构进行增强。
在这种新的架构模式下,Katalyst Agent 作为一个 NRI 插件,从 containerd 获取 Pod/Container 事件。在 ORM 中我们主要使用了三个 Hook 函数:
RunPodSandbox, 在这个事件中我们尝试对当前 Pod 进行细粒度的资源分配,分配好的资源会先存储在一个 Cache 中;
CreateContainer,在这个事件中我们会从 Cache 中获取为当前 Container 分配好的资源,返回需要调整的资源信息,由 containerd 根据调整后的 Spec 创建出容器;
RemovePodSandbox,在这个阶段我们会对 Pod 的资源进行回收清理的操作。
![](https://static001.geekbang.org/infoq/c5/c576e8833cec490a7515c094eb0349fa.png)
从架构图中可以看到,在 ORM 架构下,我们除了使用了 NRI,还实现了旁路模式,这种模式主要是为一些 containerd 版本比较低的用户提供支持。
在 ORM 架构中,我们也复用了 QRM 的插件,样可以降低 QRM 往 ORM 架构的迁移成本。
另外,Katalyst 除了在 Pod 创建时会对资源进行更新,在运行时也会根据节点资源的情况对容器的资源进行动态更新。之前 Katalyst 使用自己实现的执行器更新容器资源,需要对不同版本的 Cgroup 进行兼容;使用了 NRI 之后,可以通过 NRI 的 UpdateContainer 机制更新容器资源,这样可以降低一定的维护成本。
结语
本文所述特性为字节跳动和 Intel 联合共建,在此对参与开发和开源的工程师们表示感谢。未来,Katalyst 也会持续保持开源开放,欢迎广大云原生爱好者参与社区建设,期待听到您的声音!
NRI 社区
NRI 是由 Intel 参与发起并推动的一个项目,截至目前已经历数个版本的迭代,并且已经在企业生产环境中落地,希望帮助大家在节点资源管理上享受到 NRI 带来的便利。
NRI 社区除了推出了 NRI 的通用框架,还维护了一个 NRI 插件的 Repo。这个 Repo 主要是维护一些常用的资源管理插件,避免大家在同类资源上的重复开发工作。
Katalyst 社区
Katalyst 是字节跳动开源的成本优化实践系统,致力于解决云原生场景下的资源不合理利用问题,为资源管理和成本优化提供解决方案。
近期,Katalyst 参与了由中国科学院软件研究所“开源软件供应链点亮计划”发起并长期支持的开源之夏活动,欢迎广大高校学生参与,完成项目的同学将获得结项奖金税前人民币 8000 元,详见《开源之夏 2024|KubeWharf 邀你一起云端编程!》。
如需开源交流,添加字节跳动云原生小助手,加入云原生社群:
![](https://static001.geekbang.org/infoq/d4/d4a9e8cba206a158b0404d888f080602.png)
文章专题推荐:字节跳动云原生创新实践与开源之路
评论