Katalyst 支持reclaimed 资源的 NUMA 粒度上报|社区征文

2023总结KubeWharf

引言

本文回顾了我个人参与 Katalyst 开源项目的机缘巧合、过程中的挑战,以及所获得的感悟。一方面,这是对我的经历的记录;另一方面,我希望这些分享能对开源新人,对 Katalyst 项目感兴趣的新入门者有所帮助。

自我介绍

我本科毕业于南昌大学计算机科学与技术专业,目前在浙江大学攻读硕士学位,是 SEL 实验室的一名研究生。我的主要研究方向是混部集群的调度策略。

GitHub:https://github.com/Lan-ce-lot

在开源方面,我曾对阿里的 Sealer 社区和 OpenYurt 社区做过一些贡献。在实验室方面,我的工作主要集中在“在保证应用 QoS 前提下,提高系统资源利用率”这一多目标优化问题上。

此外,在五月份我在一家金融科技公司做一个面向金融软件分布式架构的eBPF可观测技术研究项目,这个项目里主要负责其中聚合组件的实现和RDMA协议的可观测工作,这方面的工作经历让我对云原生可观测技术有了更深入的理解。

参与开源的过程

2023 年 3 月左右,我首次从江南学长的朋友圈和其他同学那里了解到 Katalyst 这个 Kubernetes 混部开源项目。我对开源混部项目(如阿里的 Koordinator、腾讯的 Crane 和 Caelus 等)都有所调研,因此对字节最新开源的 Katalyst 感到非常感兴趣。虽然我已经半知半解地学习了一些源码,但还没有开始实际贡献。

直到有一天,Ricky 告诉我公众号上有一个“字节跳动云原生成本优化实践开源项目 Katalyst 的社区编程挑战”活动,他当时选择了 OOM 的题目。这次机缘巧合激发了我加入 Katalyst 开源之旅的决心。我随后撰写了简历和提案,并发送到了指定邮箱。很幸运地,我被选中了,这让我感到非常激动和荣幸。在这里,我要特别感谢我的编程挑战赛导师萌哥,他在代码上给了我很多帮助,并为我的项目方案提供了宝贵的意见,萌哥人非常好。

值得一提的是,当时正值上海 KubeCon 云原生峰会。左神带我和 Ricky 以及袁神一起前往,我也去到了katalyst的会场,面基了贺哥等大佬,并观看了 Katalyst 的分享。

开始 Katalyst 项目的第一个挑战是运行社区的colocation 教程

  1. 开启 agent 的 memory-resource-plugin-advisor 参数。
  2. 同时在 kubelet 启动参数中添加 --qos-resource-manager-resource-names-map=resource.katalyst.kubewharf.io/reclaimed_millicpu=cpu,resource.katalyst.kubewharf.io/reclaimed_memory=memory

最终,一个 reclaimed_core 的 pod 成功在集群中被调度并运行。

在此之后,我为 Katalyst 项目做出了一些贡献,包括提交了几个与文档相关的 issue 和 pull requests。

在Katalyst的社区编程挑战里,我做的是github.com/kubewharf/katalyst-core/issues/217, 支持reclaimed 资源的 NUMA 粒度上报。

我的主要任务是修改单机 Agent 组件。由于当前 Katalyst 的混部策略只上报整机维度的可出让资源,这导致对于跨 NUMA 的离线任务内存申请无法进行精确控制,从而引发内存压力。为解决这个问题,我和萌哥经过多次讨论,提出了三个方案:

  1. reclaimed resources 上报 CNR。
  2. 基于 Fake NUMA 的内存管控。
  3. 精细化 NUMA 粒度内存管控框架。

在多次和社区的同学会议交流讨论后,我们选择了最后一个方案作为最终的方案并实施。

在确定了方案之后,我开始了开发工作。由于之前已经对 Katalyst 项目有了深入的了解和准备,同时在萌哥和社区其他同学的帮助下,整个开发流程相对顺利。我主要集中在单机 Agent 组件的修改上,确保它能够根据我们的新策略精确地上报和管理 NUMA 粒度的内存资源。

Refined NUMA-granularity Memory Management and Control Framework

背景

当前 Katalyst 的混部策略只上报整机维度的可出让资源。对于跨 NUMA 的离线任务内存申请,可能导致离线任务的内存容量和带宽在多个 NUMA 之间分布不均,无法精确控制内存使用量,从而引发内存压力。

为解决 NUMA 维度的 reclaimed memory 管控问题,我提出了一种精细化的 NUMA 粒度内存管控框架。该框架需要在 sysadvisor 计算 memory provisions 以及与 qrm memory plugin 交互的部分。qrm memory plugin 后续可以根据 memory provisions 进行 NUMA 细粒度的内存管控。

方案设计

主要在节点侧的 katalyst-agent 内实现。图中绿色为新增模块,蓝色为需要更新的模块。

picture.image 新增模块:

  1. memoryProvisioner: 在 sysadvisor 的 memory plugin 中,增加一个叫做 memoryProvisioner 的 plugin。这个 plugin 负责计算每个 NUMA 可以出让多少内存给 reclaimed cores,并发送给 qrm memory plugin。
  2. ProvisionPolicy: 考虑 memoryProvisioner 计算策略的可拓展性,增加一个 ProvisionPolicy 的接口,新策略只需要实现该接口即可使用。
  3. PolicyCanonical: 一个默认的计算策略,实现了 ProvisionPolicy 接口。

更新模块:

  1. memory server: Sysadvisor 的 memory server 负责向 qrm memory plugin 提供建议,其中有一个ListAndWatch 接口,可以复用这个接口,将 memoryProvisioner 插件计算出来的每个 NUMA 可以出让内存发送给 qrm memory plugin 端。

  2. Qrm memory plugin :

    1. Advisor handler: 需要添加并注册一个 handleAdvisorMemoryProvision 的 handler,用于接收 memoryProvisioners插件发来的给 reclaimed cores 的内存下发建议。

Sysadvisor 侧

在 Sysadvisor 的内存插件(memory plugin)中,我负责集成一个名为 memoryProvisioner 的新插件。这个插件的主要职责是实现 Per NUMA memory provision 的计算逻辑,确保在 NUMA 节点级别有效管理内存资源。

为了提高 memoryProvisioner 的灵活性和可扩展性,我设计了一个名为 ProvisionPolicy 的接口。这个接口定义了两个关键方法:UpdateGetProvisionUpdate 方法用于定期更新内存供给(memory provision)的计算,而 GetProvision 方法则用于获取当前的内存供给状态。这两个方法直接对应于 MemoryAdvisorPlugin 接口的 ReconcileGetAdvices 方法。

type ProvisionPolicy interface {
    Update() error
    GetProvision() machine.MemoryDetails
}

MemoryProvisioner 的实现中,我确保了它遵循 MemoryAdvisorPlugin 接口的规范,以便它能够与 Sysadvisor 的其他组件无缝集成。

对于 Per NUMA memory provision 的具体计算逻辑,我的目标是平衡每个 NUMA 节点的内存使用,使其空闲内存尽可能接近预留值(Reserved)。为此,我借鉴了 headroom 策略中的 PolicyNUMAAware 思想,并设计了一个 PolicyCanonical 策略。该策略通过遍历每个物理 NUMA 节点和其上的 pod,计算出每个 NUMA 节点的内存 provision。具体计算公式如下:

picture.image

在这个公式中,mem.request.numa.container_{j} 是位于 NUMA i 上的 reclaimed cores。如果 reclaimed cores 分布在多个 NUMA 上,则取其均值。减去的系统 scale_factor 用于避免 kswapd 触发,同时也考虑了系统预留的内存(reserved)。

Update() 方法负责实现这个计算逻辑,而 GetProvision() 方法则用于返回计算出的 Provision 结果。

最后,我在 memoryProvisioner 插件中添加了一个 memory-provision-policy 参数,允许用户指定计算策略,默认设定为 memory-provisioner-canonical。这样,用户可以通过以下方式来配置和使用这个插件:

katalyst-agent --kubeconfig=/root/.kube/config \
--memory-resource-plugin-advisor=true   \
--memory-advisor-plugins=memory-provisioner \
--memory-provision-policy=memory-provisioner-canonical \
...

通过这些工作,我确保了 Sysadvisor 在处理 NUMA 级别的内存管理时具有更高的效率和精确度。

qrm plugin侧

需要先在Advisor handler注册,mrmory plugin 的 qrm的handleAdvisorMemoryProvisions 用于处理接收 MemoryProvisioner 的逻辑,接收之后如何处理的逻辑没有在本设计中体现

组件交互

  1. 由 memroy provisioner plugin 计算内存供应量
  2. 由 memory resource advisor 调用sendAdvices()方法将 advices 发到 sendChan
  3. 当 memoryServer 的 recvCh接收到内容,ListAndWatch()方法不再阻塞,同时将 advices 通过 gRPC 发送给 qrm memory plugin端
  4. qrm memory plugin 接收到 advices 后,开始进行 内存的管理

这里复用 AdvisorService 的 ListAndWatchResponse 接口和extra_entries。传输的内容如下,qrm 后续可以根据 memroy provisions 来做 numa细粒度的内存管控:

result: {
    "extra_entries": [
        {
            "calculation_result": {
                "values": {
                    "reclaimed_memory_size": "{"0":3363651584}"
                }
            }
        }
    ]
}

方案比较

方案一:reclaimed resources 上报 CNR

另外还有一种调度感知的方案,流程如下:

  • Numa 粒度的 reclaimed resources 上报到 CNR,为调度器感知节点维度 reclaimed resource 的可分配及已分配信息,从而对需要绑 numa 的 reclaimed_cores pod 分配最优的节点,单机 QRM plugin 根据当前 reclaimed resource numa 维度的可用量对需要绑 numa 的 reclaimed_cores pod 分配最优的 numa
  • Numa 粒度的 reclaimed_cores pod 的 allocation的上报

这个方案的问题是需要中心调度器参与,复杂程度较高

方案二:Fake NUMA

借助内核提供的 fake NUMA 机制,把 real NUMA 的空闲 memory 用于创建 fake NUMA。规定 reclaimed core pod 申请的内存都是来自 fake NUMA,由内核自动管理 fake NUMA 到 real NUMA 的映射,以此解决 NUMA 维度的 reclaimed memory 管控问题。

这里的 fake NUMA 的来源于内核原生的 fake NUMA 机制:Fake NUMA nodes in Linux* *。**但与原生fake NUMA不同,这里的 fake NUMA 可动态创建,大小可指定,可动态扩展和收缩。

例如:物理节点node0可出让80G,物理节点 node1可出让 100G,将 node0、node1 的可出让内存加入新的虚拟节点node2上,此时node2上就有180G。让离线任务绑到虚拟节点 node2,在线任务使用物理节点node 0 和 node1,实现在离线内存软件层面的隔离,同时控制离线最大使用量。

picture.image

这个方案的问题是,目前fake numa 暂时没办法屏蔽上层业务的影响,无法动态感知 numa 数量变化和 numa capacity 的变化。

收获与感悟

沟通与交流:在 katalyst 编程挑战过程中,我和导师萌哥多次进行了会议讨论,也积极参与了社区的双周会议。在这些讨论和交流中,我们对方案进行了修改和优化。我频繁地与社区成员沟通,这不仅提高了我的表达能力,也帮助我更好地理解他人的观点和建议。

深入实践中的技术理解:通过亲自参与 Katalyst 项目,我对该项目有了更深刻的理解。在这个过程中,我不仅阅读和理解了现有代码,还亲自动手实现新功能和解决问题。这种实践经验远超阅读文档或观看教程,使我深入了解代码的结构、逻辑及其背后的设计理念,同时提升了我的技术实现能力。我学会了如何将理论知识应用于实际项目中,从而提高了对技术的理解。

技术深度和广度的提升:在 Katalyst 项目中工作,我不仅加深了对特定技术(如 NUMA 内存管理)的理解,还拓宽了我对整个云原生技术生态的认识。这种深入浅出的学习方式帮助我在技术上更加成熟。

总结

参与 Katalyst 社区这几个月以来,我学到了很多,也帮助我去开源崇拜,在我刚接触开源不到一年的时间里,参与了一个相对完整的 Katalyst 开源项目后,我意识到开源世界非我最初的想象。起初,我对开源社区抱有一种近乎崇拜的态度,认为所有的开源项目都是完美无瑕、高不可攀的。然而,通过亲身参与和贡献,我发现开源并不是高不可攀的,开源社区更多是一个合作、学习和成长的平台。开源不仅是技术的交流,更是技术理念的传播。我上面提到的方案的实现 pr 在这里 # feat(sysadvisor): Refined NUMA-granularity Memory Management and Control Framework

总的来说,Katalyst 社区氛围很好,每两周会举办社区会议给各位同学分享议题,同时大家也可以自由提问讨论。社区的氛围很好,也感谢萌哥对我的帮助。我也愿意继续为 Katalyst 社区做贡献。也希望对此感兴趣的同学们加入进来,共同推动项目的发展!

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