katalyst 支持 OOM 优先级作为 QoS 增强|社区征文

2023总结KubeWharf

本文是我对今年秋季参加字节跳动 Kubewharf 社区编程挑战活动的回顾和总结,分享自己参与过程中的一些心得体会。在自我总结的同时,也希望能够给有意为 Kubewharf 社区贡献的同学一些参考。

Katalyst 介绍

Katalyst旨在提供一种通用解决方案,以提高云中资源利用率并降低总体成本。其主要特性包括:

  • 基于 QoS 的资源模型: Katalyst 提供了预定义的 QoS 抽象,并通过多项增强功能满足各种工作负载的 QoS 要求;
  • 弹性资源管理: Katalyst 提供了水平和垂直两种扩展实现,以及一种可扩展的机制用于外部算法;
  • 拓扑感知调度和分配: Katalyst 在原生 scheduler 和 kubelet 的基础上扩展功能,使其在调度 Pod 和为其分配资源时能够感知 NUMA 和设备拓扑,从而提高工作负载的性能;
  • 细粒度资源隔离: Katalyst 为每个 QoS 提供了实时和细粒度的资源超配、分配和隔离策略,通过自动调整的工作负载配置来实现。

开源历程

虽然我研究生不是研究混部相关的方向,但是作为云原生的爱好者对云原生混部领域的发展也一直有一定的关注。首次接触到 Katalyst 是 8 月在字节云原生公众号上看到了 Katalyst:字节跳动云原生成本优化实践 这篇文章,从中了解到字节内部在业务云原生化背景下混合部署基础设施的演进历程。在阅读完文章后,我对 Katalyst 的整体架构和设计思路有了大致的了解,同时也对其中的一些特性产生了浓厚的兴趣。一个月后公众号又放出了社区编程挑战活动的消息,我便决定参加这次活动,希望通过参与 Katalyst 的开源贡献,进一步从实现层面了解其设计细节,同时也希望能够为 Katalyst 社区贡献一些代码。

因为先前有参加类似的活动的经验,我迅速选择了感兴趣的课题,撰写了简历和提案并联系了社区,也很荣幸在九月底收到了社区的入选通知。我的课题为 Support for OOM priority as a QoS enhancement,主要任务是让 Katalyst 能够以自身 QoS 抽象为基础,支持更加灵活地为 pods 设置 OOM 优先级。参与社区贡献主要有提案撰写、社区提案评估和代码编写测试三个主要阶段,过程中很幸运得到了社区 maintainer 健俞哥的指导,健俞哥在设计可扩展性和代码实现规范方面给了我很多指导和建议,让我在这个过程中受益匪浅,在此特别感谢健俞哥的帮助。

在健俞哥和社区其他同学的帮助下,我比较顺利的完成了课题的开发和测试,并在社区的 review 下合入了代码。下面将分享一下关于本次课题的具体设计介绍。

Support for OOM priority as a QoS enhancement

背景

目前,kubernetes 中 pod 的 OOM 优先级主要受其 QoS 级别与其对内存的申请量、使用量影响。然而,当前混部场景下,kubelet 原生的 oom_score_adj 计算策略已经不能很好的满足需求,例如当需要给两个都映射到原生的 burstable 级别的 shared_cores pods 设定 OOM 优先级;或者当需要在两个原生都是 guaranteed 级别的 dedicated_cores pod 和 shared_cores pod 之间设定 shared_cores pod 要早于 dedicated_cores pod OOM。此外,当前 kubelet 中提供的静态 oom_score_adj 计算机制,不支持 OOM 优先级的动态调整。因此我们希望提供一个关于 OOM 优先级的 Katalyst QoS Enhancement,支持更加灵活地为 pods 设置 OOM 优先级。

调研

原生的 kubernetes 中,kubelet 会在容器启动时根据其所属 pod 的 QoS 级别与其对内存的申请量,为其计算oom_score_adj 并配置到/proc//oom_score_adj中,从而影响其被 OOM Kill 的顺序,主要设置方式如下:

  • 对于 Kubelet 和 KubeProxy 组件,将其 oom_score_adj 设置为 -999
  • 对于 Critical Pod 或 Guaranteed Pod 中的容器,将其 oom_score_adj 设置为 -997
  • 对于 BestEffort Pod 中的容器,将其 oom_score_adj 设置为 1000
  • 对于 Burstable Pod 中的容器,按照如下公式计算对应的 oom_score_adj:
min{max[1000 - (1000 * memoryRequest) / memoryCapacity, 1000 + guaranteedOOMScoreAdj], 999}

内核最终对于非 root 进程的 OOM 得分计算为如下所示,并按此得分作为最终的 OOM 依据

// points 代表打分的结果
// process_pages 代表进程已经使用的物理内存页面数
// oom_score_adj 代表 OOM 校准值
// totalpages 代表系统总的可用页面数
points = process_pages() + oom_score_adj * totalpages / 1000

总体来看,原生方式较为直接并存在一定局限性,考虑下面的场景: Pod A 是一个 QoS level 为 dedicated_cores 的核心组件,其映射到原生的 guaranteed 级别; Pod B 是一个 Qos level 为 shared_cores 的普通组件,其也映射到原生的 guaranteed 级别; 两者的 oom_score_adj 均为 -997 ,如若发生 Global OOM 时 Pod A 的实际内存用量明显小于 Pod B 的实际内存用量,会由于 process_pages 的因素导致 Pod A 优先于 Pod B 被 OOM Kill,这是不符合预期的。

整体方案设计

在 Katalyst 中存在一个 QoS Resource Manager (QRM) 组件,旨在扩展资源分配的能力,通过在准入阶段实现资源动态分配调整,为 Pod 提供更好的灵活性。其设计上类似于 K8S kubelet 中的 Device Manager, 将资源分配逻辑通过插件化的方式实现,从而支持不同的资源类型(例如 CPU 和 Memory 等)。本次对 OOM 优先级的支持是作为 Memory 能力的增强,因此选择将功能添加在 QRM Memory Plugin 中。

picture.image 如上图所示,OOM Priority 能力支持主要在 qrm-plugins 中的 memory plugin 完成,其主要内容包括:

  • 对上层用户指定的 oom_priority 进行校准(此外 webhook 中也会增加类似的校准)
  • 将 oom_priority 同步以及定期异步下发至内核 ebpf map

API 设计

如整体方案设计所述,OOM Priority 能力是作为 Memory 能力的增强,因此在上层 annotations 指定时也添加在 memory_enhancement 的子字段中。

annotations:
    "katalyst.kubewharf.io/memory_enhancement":'{
    "numa_binding": "true", 
    "numa_exclusive": "true",
    "oom_priority": priorityValueInt,
    }'

这里的 priorityValueInt 的取值由用户指定,其中值越大表示优先级越高。priorityValueInt的 取值范围受 pod 所指定的 QoS level 影响,如下表所示。

Qos levelValue Range
*[300, 300]
system_cores[200, 300)
dedicated_cores[100, 200)
shared_cores[0, 100)
reclaimed_cores[-100, 0)

其中 value 为 300 表示此 OOM Priority 层面不会选中该 Pod 进行 OOM Kill,可用于一些特殊的组件。若 OOM Priority 层面的 value 相同时,会 fallback 到原生的 oom_score_adj 加权的方式。

详细设计

由于 OOM Priority 需要依赖 ebpf,因此需要保证上下链路的全部打通。在详细设计中包括 BPF 初始化,上层参数校准和同步参数至 ebpf map 三个方面。

BPF 初始化

在 memory plugin 初始化时需要对 OOM Priority 依赖的 BPF 能力进行初始化,这包括了几个方面:

  • 将 ebpf OOM priority map pin 到指定目录或复用已经 pin 到指定目录的 ebpf map
  • 将自定义的 OOM Priority ebpf 程序 attach 到内核中对应的 hook 点 kprobe/bpf_oom_evaluate_task
  • 初始化成功后 load 对应的 pinned map 到 policy 中的 oomPriorityMap 对象

这里考虑到 bpf prog 初始化流程与策略的可扩展性,此处采用支持注册自定义的初始化函数的设计。此外,该初始化流程不应该阻塞住后续主流程执行,因此需要设计为异步周期性判断的方式直至初始化成功。

参数校准

由于 OOM Priority 的值是由用户通过 annotation 指定,因此可能出现 value 格式错误或 value 值超出对应 Qos Level 指定的范围的情况,因此需要在下发前对 OOM Priority 的值进行参数校准。

考虑到校准要覆盖 pod 初次创建以及后续手动更新,可以为 pod 添加相关 webhook:对于显式指定的 OOM Priority 是否出现格式错误或格式正确但取值超出 Qos level 对应取值范围的情况,需要提示对应信息并返回失败。

参数下发

在 memory plugin 中将上述 OOM Priority 下发至 ebpf map,需要支持异步的配置周期性更新以及 pod 删除时的同步配置清理。

异步更新方面,由于 pod admit 时还不能得知对应 container 的容器级 cgroup id,因此无法在 pod admit 时同步下发 OOM Priority。为了在容器级 cgroup 就绪后下发 OOM Priority 配置,并且支持运行时动态调整 OOM Priority 的情况,可以在 memory plugin 中运行一个 goroutine,定期同步容器对应的 ebpf OOM priority map entry。此外,为防止 OOMPriorityMap 同步清理由于各种原因出现遗漏,另可以起一个长周期的 goroutine 清理 map 中残留 entry。

同步清理方面,需要在 RemovePod 阶段同步删除 Pod 内所有 conatiners 在 ebpf OOM priority map 中对应的 entries。在设计上OOM Priority 作为一种 enhancement 能力,会在 memory plugin 中被启用;考虑到后续新增的 enhancement 能力可能也会在各个 lifecycle RPC 流程中触发相应的同步逻辑,因此可以设计一个 enhancement_handler 如下所示:

type EnhancementHandler func(ctx context.Context, 
    emitter metrics.MetricEmitter,
    metaServer *metaserver.MetaServer,
    req interface{},
    podResourceEntries interface{}) error

// example:
// map[apiconsts.QRMPhaseRemovePod][apiconsts.PodAnnotationMemoryEnhancementOOMPriority]
// 作为 QRM plugin 中 RemovePod 阶段中 oom_priority 这个 memory_enchancement 对应的 handler    
type ResourceEnhancemetHandlerMap map[apiconsts.QRMPhase]map[string]EnhancementHandler

func (r ResourceEnhancemetHandlerMap) Register(
    phase apiconsts.QRMPluginPhase, enhancementKey string, handler EnhancementHandler) {
    if _, ok := r[phase]; !ok {
        r[phase] = make(map[string]EnhancementHandler)
    }
    r[phase][enhancementKey] = handler
}

总结

具体的设计与实现可以参考相关 issue 和 已合入的 PR

参与 Katalyst 社区课题过程中我收获颇丰,首先在技术方面,我对 Linux OOM 的核心流程有了更深入的了解,同时也对 Kubelet 的准入细节流程和 Katalysy QRM 的设计有了更全面的认识;其次,通过在过程中和健俞哥的多次交流讨论以及在社区双周会的提案分享,我学习到了系统设计上的一些思考方式和技巧的同时也提高了自身的表达能力,这对我今后的学习和工作都有很大的帮助。

总体来说,Katalyst 社区的氛围非常好,社区的双周会也会分享技术方案并同步社区的进展和规划。社区的同学也非常友好,交流也很愉快,特别感谢健俞哥,技术水平高且人非常nice,希望以后还能有机会和健俞哥一起交流学习。虽然相比其他成熟社区 Katalyst 只是刚起步,但是相信在社区的共同努力下,Katalyst 会越来越好。我也愿意继续参与 Katalyst 社区的开源贡献,也希望更多的开发者参与进来,共同推动 Katalyst 的发展。

150
0
0
0
关于作者
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论