ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Volcano HDRF 深度解析:用层级主导资源公平算法实现树状队列资源调度

Volcano HDRF 深度解析:用层级主导资源公平算法实现树状队列资源调度 Volcano HDRF 深度解析用层级主导资源公平算法实现树状队列资源调度【免费下载链接】volcanoA Cloud Native Batch System (Project under CNCF)项目地址: https://gitcode.com/GitHub_Trending/vol/volcano本篇技术指南围绕 Volcano 调度器中的 HDRFHierarchical Dominant Resource Fairness层级主导资源公平设计与实现展开讲解为何扁平化的 DRF 权重模型无法表达树状共享结构、HDRF 如何通过缩放子节点、忽略饱和节点解决互补资源饿死与饱和节点阻塞两大经典问题以及如何通过 Queue 注解与 drf 插件选项在 pkg/scheduler/plugins/drf/drf.go 中落地。读完本文你将掌握 HDRF 的注解配置方式、enableHierarchy插件选项的用法、allocate/preempt/reclaim 三个调度动作在层级队列下的行为差异以及该特性与 proportion 插件的冲突边界。背景从 DRF 到 HDRF为什么需要树状层级公平DRFDominant Resource Fairness主导资源公平是 Kubernetes 生态中多资源调度最常用的公平算法之一。在 Volcano 调度器中资源共享的公平性通常靠为队列Queue和命名空间Namespace设置权重来保证——drf插件通过比较各作业的主导份额dominant share决定调度顺序proportion插件则按队列权重比例分配资源。然而这种一个大的扁平化加权共享模型无法表达树状的层级共享结构tree-like hierarchy。当需要表达比命名空间/队列的扁平加权共享更复杂的层级分组共享时例如部门 → 小组 → 项目、或者业务线 → 环境 → 应用这样的多级结构就需要引入 HDRF 算法。HDRF 源自 Berkeley 团队关于多资源公平分配的经典论文H-DRF论文指出了朴素 DRF 无法适应的两个典型场景也是 HDRF 需要解决的两个核心问题。问题一互补主导资源子节点引发的饿死Starvation考虑下面这个层级结构n / \ n1 w50% n2 w50% (0 CPU,1 GPU) / \ n2,1 w50% n2,2 w50% (1 CPU,0 GPU) (0 CPU,1 GPU)节点 n1 的请求是 GPU 型0 CPU, 1 GPUn2 下有 n2,1CPU 型与 n2,2GPU 型两个子节点。假设 n2,1 组拥有更高的主导份额例如 CPU 100%这会给它的父节点 n2 带来 100% 的份额。此时若有作业被移除、新作业加入n2 的兄弟节点 n2,2 会因为主导资源不同例如 GPU 50%而持续受到惩罚——因为 GPU 资源会一直被分配给主导份额更小的 n1 组n2,2 始终无法获得 GPU。HDRF 的解法是把子节点缩放到最小节点rescale children to the minimum node。在本例中n2,1 组被缩放为(10,0) * (0.5/1) (5,0)再累加到父节点上于是父节点 n2 的份额变为 50%这正是我们期望的公平状态——n2 与 n1 各占一半而不是让 GPU 全部流向 n1。问题二饱和节点引起的资源阻塞Blocking再看第二个场景n / | | \ n1 n2 n3 n4 (1 CPU,0 GPU) (1 CPU,0 GPU) / \ (0 CPU,1 GPU) n3,1 n3,2 (1 CPU,0 GPU) (0 CPU,1 GPU)假设某个时刻每个叶子节点都分配到了其主导资源的 1/3。此时只要有新任务被分配到 n4 组n4 的主导份额就会高于 n3导致所有剩余的 GPU 资源被反复分配给 n3,2。最终结果n3,2 拿到了 2/3 的 GPU而 n4 只拿到 1/3 的 GPU——n4 被饱和的 n3 阻塞了。HDRF 的解法分三步在所有未被阻塞non-blocked的节点中选取最小的主导份额 M把每个非阻塞节点的资源消耗向量resource consumption vector重新缩放使其主导份额等于 M将所有节点阻塞与非阻塞的向量累加得到父节点的资源消耗向量。此外HDRF 在计算任意内部节点的主导份额时会忽略已饱和saturated的资源从而避免饱和节点对整体公平性的干扰。Volcano 中的 HDRF 实现层次结构与权重的声明Queue 注解HDRF 所关心的层级结构与权重通过 Queue 的注解annotations声明使用两个固定的注解键volcano.sh/hierarchy声明队列所处的层级路径例如root/eng/prodvolcano.sh/hierarchy-weights声明与该路径各级对应的权重例如100/50/50。这两个注解键在 staging/src/volcano.sh/apis/pkg/apis/scheduling/v1beta1/labels.go 中定义为常量KubeHierarchyAnnotationKey与KubeHierarchyWeightAnnotationKey。调度器在构建QueueInfo时从 Queue 注解中解析出这两个字段见 pkg/scheduler/api/queue_info.go其中Weights是一串用斜杠分隔的浮点数每个数值对应层级路径上的一级权重Hierarchy是从根到该节点路径上的节点名列表。一个带层级声明的 Queue 示例apiVersion: scheduling.volcano.sh/v1beta1 kind: Queue metadata: name: prod annotations: volcano.sh/hierarchy: root/eng/prod # 路径root → eng → prod volcano.sh/hierarchy-weights: 100/50/50 # 每级权重root100, eng50, prod50 spec: weight: 1 reclaimable: true注意spec.weight是队列自身的基础权重与注解中的层级权重volcano.sh/hierarchy-weights是两套独立的概念后者专门服务于 HDRF 的树状公平计算。启用开关drf 插件新增的 hierarchyEnable 选项HDRF 作为 drf 插件的一个可选能力通过插件配置选项enableHierarchy对应代码中的EnabledHierarchy *boolyaml 键名为enableHierarchy开启。该字段定义在 pkg/scheduler/conf/scheduler_conf.go 的PluginOption结构中。drfPlugin.HierarchyEnabled()见 drf.go会在调度会话Session的 tiers 配置中检索 drf 插件返回enableHierarchy是否被显式置为 true。在调度器配置例如 benchmark/testcases/gang/config/scheduler-config.yaml 所使用的 ConfigMap 格式中启用方式如下actions: enqueue, allocate, backfill, preempt, reclaim tiers: - plugins: - name: drf enableHierarchy: true # 开启 HDRF 层级公平 enableQueueOrder: true # 按层级份额参与队列排序 enableReclaimable: true # 按层级份额参与抢占回收核心数据结构drf 插件内部为 HDRF 维护了一棵层级树根节点名为root、权重固定为 1。树中每个节点由hierarchicalNode表示drf.go包含以下关键字段parent/children父子关系叶子节点没有子节点attr *drfAttr该节点的 DRF 属性含share主导份额、dominantResource主导资源名、allocated已分配资源量request *api.Resource仅叶子节点有意义表示该作业的总请求量weight float64该节点在层级中的权重saturated bool该节点是否已饱和无法再获得资源hierarchy string节点在路径中的名字。树通过Clone()方法支持整树深拷贝这在 reclaim 动作的模拟计算中至关重要详见后文。更新流程三步递归算法启用 HDRF 后在任务分配allocate和释放deallocate事件发生时drf 属性按照以下三个步骤更新对应OnSessionOpen中注册的AllocateFunc/DeallocateFunc事件处理器见 drf.go构建层级路径根据作业所属 Queue 的hierarchy注解沿路径构建层级节点。例如层级root/eng/prod加权重100/50/50会构造出三级层级结构root → eng → prod。此逻辑由buildHierarchy()实现drf.go逐级解析以/分隔的路径与权重已存在的节点直接复用不存在的节点按权重创建权重解析失败或小于 1 时被强制为 1最后把作业作为叶子节点挂到路径末端。计算作业的 DRF 属性并标记饱和作业叶子节点按照普通 DRF 算法计算主导份额与主导资源如果其任一资源已满足已分配量达到请求量或者它请求的资源已全部被分配完毕不再是 demanding resource则标记为饱和。饱和判定由resourceSaturated()实现drf.go。自底向上递归更新内部节点从根开始递归先计算所有非阻塞子节点的主导资源找出其中最小的主导份额将非阻塞子节点的资源向量按最小份额缩放饱和子节点不缩放、直接累加原始向量累加所有子节点阻塞与非阻塞的资源得到父节点的资源消耗向量在有需求demanding的资源集合中计算父节点的主导份额若所有子节点都已饱和则父节点也标记为饱和。该逻辑由updateHierarchicalShare()实现drf.go。入口函数UpdateHierarchicalShare()drf.go还会先做一步需求资源过滤只有当前总分配量totalAllocated小于集群总资源totalResource的资源才被视为 demanding resource饱和资源不参与内部节点主导份额的计算——这正是文档中HDRF 忽略饱和资源原则的代码落地。三个调度动作中的 HDRF 语义allocate沿层级路径比较队列启用层级后队列的排序顺序是沿层级路径逐级比较的。compareQueues()drf.go实现如下语义两个同层节点的share / weight若相等落入同一档则继续下钻比较路径上的子节点若不相等则份额小的优先。饱和节点拥有最低优先级——它已无法再获得资源自然排在队尾。队列排序函数通过ssn.AddQueueOrderFn()注册drf.go。同时层级模式下存在一条约束一个队列不能同时包含子队列和作业。例如root/sci队列与root/sci/dev队列互相冲突因为它们试图把同一层级路径节点既当作作业容器又当作子队列容器。preempt纳入层级队列优先级抢占时除了原有的命名空间优先级namespace priority与作业优先级job priority层级队列的优先级也必须被纳入考量。具体规则为份额share更低的队列拥有更高优先级饱和队列拥有最低优先级。这保证了低份额队列中的作业在被抢占时处于更有利的地位。reclaim以 HDRF 份额决定应得资源回收reclaim动作中队列的应得份额deserved share由队列的 HDRF 份额决定。实现上reclaim 函数会先克隆整棵 HDRF 树和 totalAllocated 向量见 drf.go模拟回收者拿到资源后的树状态对每个候选被回收对象再模拟释放其资源后的状态调用compareQueues()比较回收者队列与被回收者队列的层级份额若回收者份额仍更低则将其加入受害者列表。模拟结束后恢复现场把释放的资源加回 totalAllocated 并重新 updateShare保证不影响真实状态——这正是Clone()存在的意义。测试验证rescaling 与 blocking 两个经典场景HDRF 的单元测试位于 pkg/scheduler/plugins/drf/hdrf_test.goTestHDRF通过 uthelper 构造真实调度会话同时启用 drf 与 proportion 插件、仅运行 allocate 动作用两个用例分别验证文档中描述的两大问题均被修复用例一 rescaling test缩放测试构造root/sci权重100/50、root/eng/dev与root/eng/prod权重100/50/50三个层级队列分别提交 CPU 型作业10 个 pod各 1 CPU与内存型作业10 个 pod各 1G 内存集群总资源 10 CPU / 10G。最终断言sci 队列作业获得 5 CPU 5G占总量 50%eng 下的 dev 作业获得 5 CPU、prod 作业获得 5G 内存——互补资源子节点经缩放后各得其所没有出现 GPU/内存被单边占满的饿死现象。用例二 blocking nodes test饱和节点阻塞测试构造root/pg1、root/pg2、root/pg3/pg31、root/pg3/pg32、root/pg4五条层级路径权重100/25与100/25/50混合 CPU 型与内存型作业各 30 个 pod总资源 30 CPU / 30G。最终断言CPU 型的 pg1、pg2、pg31 各得 10 CPU内存型的 pg32、pg4 各得 15G——即便 pg3 子树已趋于饱和pg4 也不再被阻塞仍然拿到 50% 的内存份额。两个用例共同验证了 HDRF 的核心行为子节点按最小主导份额缩放、饱和节点不缩放不参与最小份额选取、内部节点只在 demanding 资源集合上计算主导份额。局限性与注意事项与 proportion 插件冲突HDRF 与 proportion 插件互斥。启用 HDRF 后应禁用 proportion同时需要补充一个按层级份额与权重比较队列的 reclaim 函数drf 插件自身的 reclaim 实现已覆盖此职责。配置时务必注意不要把proportion的队列排序/回收能力与 drf 的enableHierarchy混在同一 tier 中以免两个公平模型相互干扰。递归更新开销每当有作业加入层级树HDRF 需要从根开始递归遍历整棵树并标记所有饱和节点当某些资源被完全分配时。在队列层级深、作业数量大的场景下这一过程可能效率不高。从实现看每次 allocate/deallocate 事件都会触发一次UpdateHierarchicalShare()的全树递归调度规模较大时需要评估其性能影响。延伸阅读DRF 插件设计HDRF 所依赖的基础 DRF 算法与作业排序逻辑队列管理文档Queue 对象模型与权重语义proportional 设计与 HDRF 互斥的比例公平插件理解两者的边界有助于正确选型公平份额插件使用指南fairshare 插件与 DRF/HDRF 的定位差异。【免费下载链接】volcanoA Cloud Native Batch System (Project under CNCF)项目地址: https://gitcode.com/GitHub_Trending/vol/volcano创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进