Katalyst 是一个以 QoS 保障为核心的开源资源管理系统,是字节跳动对大规模在离线混部实践的总结。大规模的混部场景对配置管理的自动化和灵活度有很高的要求,本文通过讲解 Katalyst 中的 Katalyst Custom Config 方案,介绍了 Katalyst 实现复杂配置管理的思路以及实际的使用场景。
背景
在大规模集群中,往往存在各种不同的机型和业务,这就需要管理员对不同节点进行差异化配置——
对于 CPU 密集型的业务的节点,我们可能需要调高 CPU 的驱逐阈值,以保证业务的稳定运行;对于 IO 密集型的业务的节点,我们可能需要调低 IO 的驱逐阈值,以防止 IO 饥饿;此外,还需要根据业务的安全需求,对不同节点的 Agent 接口权限进行精细化配置。
在上述过程中,AdminQoSConfiguration 和 AuthConfiguration 是比较常见的配置:
AdminQoSConfiguration 是用于管理 QoS 相关管控手段的配置。例如,它可以配置 cpu/memory/io/network 等多个资源维度的压制驱逐策略,包括各种驱逐开关、驱逐阈值等。它也可以配置混部算法相关的管控策略,如混部开关、混部算法参数等;
AuthConfiguration 是用于管理 Agent 各类接口的权限策略的配置。例如,它可以配置 out-of-tree plugin 的准入权限,端口访问权限等。这对于保证系统的安全性和稳定性非常重要。
然而这些配置在管理层面仍然存在复杂度过高的问题——对于通过 DaemonSet 部署的单机 Agent 而言,传统的基于启动参数的静态配置管理方式只能通过滚动重启实例进行配置变更,存在生效时间长、实例重启存在风险等问题。另外,面对集群中存在的的差异化配置需求,这种方式也只能通过部署多个 DaemonSet 实例的方式实现,存在运维负担较重的问题。因此对于单机管控系统而言,动态配置管理已经成为不可或缺的功能。
针对上述需求,原生 Kubernetes 提出了 Dynamic Kubelet Configuration 的动态配置管理方案(v1.11 开始 Alpha 支持,v1.22 之后被废弃),该方案为集群管理员提供了能够通过 Kubernetes API 动态改变 Kubelet 运行时配置的动态配置管理方案。
Dynamic Kubelet Configuration 的工作流程大致如下:
创建一个 ConfigMap,其中包含了想要在 Kubelet 上应用的配置。
将这个 ConfigMap 关联到一个或多个节点。
Kubelet 在后台检查这个 ConfigMap,并且在检测到任何改变时,它会重启并使用新的配置。
然而,Dynamic Kubelet Configuration 也存在一些局限性:
动态配置的生效需要 Kubelet 重启,这可能会导致正在运行的 Pod 中断,影响应用的稳定性。
动态配置只能应用于 Kubelet,对于 out-of-tree 的 agent 如各种 device plugin 等,无法进行动态配置。
对于集群内存在机型或业务差异的场景,并没有提供自动化配置的扩展和支持。
什么是 KCC
Katalyst 作为字节跳动开源的提高资源利用率的通用资源管控系统,能通过精细化的单机管控手段,实现细粒度的资源隔离与业务 SLA 保障。
针对上述社区方案存在的问题,Katalyst 推出一种全新的解决方案 —— Katalyst Custom Config(KCC),这是字节跳动在大规模集群单机管控实践中,总结并设计出了一套高度自动化、扩展性强的单机动态配置管理方案。
设计目标
KCC 旨在解决现有方案的局限性,提供一种更加灵活、高效和可扩展的单机动态配置管理方案,以满足日益增长的单机管控需求。以下是 KCC 的主要设计目标:
动态配置:KCC 应能够实时响应配置更改,无需重启,从而避免影响正在运行的 Pod 和应用的稳定性。
差异化配置:KCC 应能够支持集群内存在机型或业务差异的场景,提供差异化配置的能力,以满足不同节点可能需要的不同配置。
自动化管理:KCC 应能够根据节点差异化配置自动下发节点配置,减轻大规模集群管理的工作负担,避免手动操作导致的错误。
易于运维:KCC 应提供简单易用的接口和工具,使运维人员能够方便地管理和监控配置的状态和变更。
易于扩展:KCC 不仅应用于 Katalyst 自身,还能以 SDK 的形式支持 out-of-tree 的 agent,如各种 device plugin 等,以满足更广泛的配置需求。
基本架构
KCC 方案中 Agent 的动态配置都是基于 CRD,而不是 ConfigMap,这能提高动态配置的可靠性和易用性。其各组件或模块的职责如下:
KatalystCustomConfig (KCC):由管理员创建,描述需要托管的动态配置 CRD 信息(如前文提到的 AdminQosConfiguration 和 AuthConfiguration 的 GVR) 和托管行为。
KatalystCustomConfig Target (KCCT):托管的动态配置(如前文提到的 AdminQosConfiguration 和 AuthConfiguration 的 CR),包含实际配置字段以及支持差异化配置的通用字段。
CustomNodeConfig (CNC):每个节点创建的同名 CR,实时同步节点 Labels,保存当前节点匹配的动态配置信息。
KCC Controller:管理托管的动态配置 CRD 注册,更新匹配节点的动态配置信息到 CNC。
KCC SDK:定时轮询 CNC,自动加载最新动态配置 CR。
该方案通过动态配置注册机制,允许管理员动态定义和管理配置,从而实现灵活地按需扩展动态配置。同时,通过使用标签选择器和临时选择器,KCC 也可以灵活实现集群节点差异化自动配置。
除此之外,Agent 与 APIServer 无需建立 list/watch 的长链接,避免了在大规模集群场景下过多长链接对 APIServer 的负担。基于 CNC 的配置 hash 缓存策略,也可以有效减少直接查询动态配置的 APIServer 请求。
KCC 方案详解
动态注册机制
为了方便管理员动态的扩展动态配置的需求,KCC 支持动态配置 CRD 的注册,支持管理员将 Agent 的动态配置划分到不同 CRD 中,同时方便进行权限管控。
管理员在 KatalystCustomConfig 中通过 TargetType 描述需要被托管的动态配置 CRD 的 GVR(Group Version Resource)。
当 KCC Controller 监听到 KatalystCustomConfig CR 的创建,就会根据其配置的 GVR 信息动态创建一个 dynamic Informer,这样 KCC Controller 就可以通过 list/watch 的方式动态发现 KCCT 创建和更新。
KCC Controller 会校验 KCCT 配置是否有效,同时更新 KCCT 的 hash 值,并根据 KCCT 中差异化配置将其同步到所匹配的节点的 CNC 的 KatalystCustomConfigList 中。
KatalystCustomConfigList 表示当前节点所匹配动态配置信息列表,即多个 TargetConfig,每个 TargetConfig 描述对应 KCCT 的 GVR 信息以及节点当前所匹配动态配置 CR 的信息和其配置的 hash 值。
差异化配置
为了满足集群中差异化配置的需求,KCC 方案支持 LabelSelector 或节点列表的配置,充分利用 K8s 原生的 label 和 labelSeletor 特性,将动态配置的差异化划分成三个粒度:全局粒度、LabelSelector 粒度、节点粒度。同时一个节点匹配的配置顺序是节点 > LabelSelector > 全局。
全局粒度配置:即动态配置无需指定 NodeLabelSelector 和 EphemeralSelector,同一个集群只能有一个全局配置
LabelSelector 粒度配置:即动态配置指定了 NodeLabelSelector,其采用 K8s 原生的 labelSeletor 语法,并支持 =, ==, !=, in, notin 等选择算子,且支持多个 key 的组合,例如 "key1=value1,key2!=value2"
节点粒度配置:即动态配置指定了 EphemeralSelector,其指定配置所匹配的节点列表,为了避免这种临时配置长期存在导致配置不可维护,要求一定需要配置持续时间,当该配置过期之后会被自动清理。
配置冲突检测
一个集群里有一个全局粒度配置、多个 LabelSelector 粒度配置和节点粒度配置,但单一节点所匹配的动态配置只能有一个,因此需要对所有配置进行冲突检测。
节点粒度配置冲突检测比较简单,即两个不同配置的节点列表集合不能有交集,但 LabelSelector 粒度配置的冲突检测较为复杂。
NodeLabelSelector 支持相等运算符(=/==)、非相等运算符(!=)以及集合运算符(in/notin)来匹配 Label,且支持多个匹配算子组合的复合选择器。然而对于一个 key 而言,所对应的 value 可能是无穷的,selector 中包含可能的 key 越多,出现冲突的可能性越大,配置的维护就越复杂。因此管理员可以通过 KCC 的 NodeLabelSelectorAllowedKeyList 对 NodeLabelSelector 支持的 key 进行约束。
为了判断两个 LabelSelector 粒度配置是否冲突,我们设计了基于等值集合和不等值集合的冲突检测算法,该算算法基本思路如下:
1. 对两个配置的选择器 (selectorA 和 selectorB) 遍历所有支持 key :
获取
selectorA
中对于 key 的相等和不等的值集合(equalValueSetA
和inEqualValueSetA
)。获取
selectorB
中对于 key 的相等和不等的值集合(equalValueSetB
和inEqualValueSetB
)。
2. 检查这两个选择器的值集合满足以下条件则可能存在冲突,需要继续遍历下一个 key:
如果
equalValueSetA
和equalValueSetB
的交集非空。如果
equalValueSetA
和equalValueSetB
都为空。如果
inEqualValueSetA
与equalValueSetB
的交集不等于equalValueSetB
。如果
inEqualValueSetB
与equalValueSetA
的交集不等于equalValueSetA
。如果
equalValueSetA
非空,但equalValueSetB
和inEqualValueSetB
都为空,即selectorB
可以匹配该 key 的任意值。如果
equalValueSetB
非空,但equalValueSetA
和inEqualValueSetA
都为空,即selectorA
可以匹配该 key 的任意值。
3. 如果存在一个 key 不满足以上任何条件,则表示两个选择器没有冲突,因为算子之间的关系是 AND。
4. 如果所有键都满足以上条件,表示两个选择器可能存在冲突。
基于该算法,我们可以提前判断两个 LabelSelector 可能存在冲突告知用户,避免在新加节点的时候出现匹配到多个配置的情况。
配置优先级
根据上述冲突检测算法,可以看出如果允许配置多个 key 的情况下,要求每个动态配置尽可能包含所有的 key,这时候对于用户而言在某些场景下可能很难充分考虑到所有可能冲突的情况,比如紧急降级等。因此我们对 LabelSeletor 配置引入了配置优先级的概念,即冲突仅存在相同优先级,一个节点优先匹配高优先级的配置。
除此之外,KCC 方案也支持在 NodeLabelSelectorAllowedKeyList 中对不同优先级配置允许的 key,这样可以更好地规范用户的使用。基于生产实践,对于同一个配置,我们推荐最多配置两个优先级,每个优先级的最多配置两个 key,这样才不会导致配置爆炸。
Agent SDK
对于 Agent 而言,我们希望其不需要感知节点层差异化配置,只需要根据其需要的动态配置的 GVR 就可以获得当前该节点所匹配的最新的动态配置。
因此我们设计了 Agent SDK,负责定时轮询 CustomNodeConfig,只有当 KatalystCustomConfigList 中 TargetConfig 与 cache 中相同 GVR 的 TargetConfig 的配置 hash 值不一致时,才会访问 APIServer 将 TargetConfig 所对应的动态配置最新的 CR 加载到 cache 中。
Agent 通过 KCC SDK 只需要动态配置 GVR 信息就可以获取该节点所匹配的动态配置的 CR,而不需要关心当前节点差异化的信息,这有效降低了单机侧识别配置的复杂度。
Agent 动态配置框架
基于 KCC 的动态配置管理方案,Katalyst 实现了灵活的 Agent 动态配置框架。该框架涉及以下组件:
DynamicConfigCRD:包含所有动态配置 CRD api 的结构体,若需要扩展动态配置,需要将动态配置 CRD 的 api 定义加到该结构体中。
DynamicConfigManager:位于 MetaServer 中,负责管理 Agent 模块需要监听的动态配置的注册,并通过 KCC SDK 自动获取所需的动态配置,并对获取的动态配置以 DynamicConfigCRD 的结构保存 checkpoint,避免 Agent 重启时无法访问 APIServer 导致无法获取到当前节点的配置。除此之外,其还会将当前节点的 DynamicConfigCRD 与启动时初始的 DynamicAgentConfiguration 通过 ApplyConfiguration 接口进行覆盖,并通过 SetDynamicConfiguration 方法写到全局 Configuration 中,这样 Agent 各个模块就可以直接通过全局 Configuration 的 GetDynamicConfiguration 方法获取到最新的动态配置。
DynamicAgentConfiguration:位于全局 Configuration 中,通过 GetDynamicConfiguration 和 SetDynamicConfiguration 方法进行读写。其包含 Agent 需要的所有动态配置的实体,其的所有成员对象都需要提供 ApplyConfiguration 的方法,即将 DynamicConfigCRD 应用到该成员对象的策略。
基于该框架,Katalyst Agent 中的各个模块可以像使用静态配置一样使用动态配置。
KCC 应用案例
混部降级
在春节活动、线上事故等场景,研发团队需要对集群中的混部资源进行紧急回收,这时就可以通过 KCC 实现快速混部降级,即创建 AdminQoSConfiguration 的全局配置,将 EnableReclaim 设为 false,如下所示:
策略灰度
在一些新的管控策略上线的场景,研发团队可以通过 KCC 在部分节点上对该策略进行灰度。例如新驱逐策略上线,管理员可以对部分节点开启 72 小时 Numa 粒度的内存驱逐策略,即创建 AdminQoSConfiguration 的节点级别配置将 enableNumaLevelEviction 设为 true,如下所示:
机型定制化策略
在集群中节点的机型存在性能差异的场景,如集群中同时存在 HDD 盘和 SSD 盘的机器,对于 HDD 盘的机器,Kswapd 内存回收对业务指标影响较大,管理员可以通过 KCC 调整 HDD 盘机器的内存驱逐阈值,提前驱逐离线的 pod,即创建 AdminQoSConfiguration 的 LabelSelector 配置,增大 systemFreeMemoryThresholdMinimum 阈值到 20Gi,如下所示:
后续规划
Katalyst Custom Config(KCC)方案的动态注册机制允许管理员动态定义和管理配置,差异化配置通过 LabelSelector 和节点列表实现,配置冲突检测算法帮助管理员避免配置匹配冲突,配置优先级允许节点匹配高优先级配置。Agent SDK 简化了 Agent 获取动态配置的过程,隐藏了节点层的差异化配置细节。
当前,KCC 已在 Katalyst 中应用,例如管理员可以灵活控制混部配置,实现快速降级或灰度测试。在 Katalyst 后续的版本中,我们将持续迭代 Katalyst Custom Config 方案,使其能够更好地方便集群管理员进行 Agent 动态配置管理。
可观测性:当前 KCC 的动态配置的下发暂时没有提供进度显示,对于管理员来说不够友好,因此 KCC 的可观测性增强将作为后续迭代的主要方向之一
历史版本:对于动态配置方案而言,为了避免误操作,必然存在回滚的需求,因此历史版本支持也将是 KCC 未来需要补充的能力
如需企业交流和合作:
添加字节跳动云原生小助手,加入云原生社群:
评论