






上一篇的 Gang 检查回答了一组 Pod 能否满足最低要求。现在有两个作业都在等资源:demo-job 和独立提交的 demo-job-b。它们应该按提交时间排队,按优先级选择,还是按已经占用的资源量轮流获得机会?
Volcano 把这些选择放在不同层次:Queue 表达资源策略,作业和成员各有排序,节点还有自己的放置约束。这篇沿 Volcano d8984501e4ad 的 proportion、drf 和传统 preempt、reclaim 路径阅读。默认配置仍承接第七篇;分析驱逐时会显式增加对应 Action。数字例子只演示算法,不表示主案例已经修改配置或完成压测。
先看两个作业都提交到 ray-batch 的情况。它们共享该 Queue 的资源策略;作业之间如何排序,稍后再看。讨论跨队列分配时,另设一个对比场景:第二个作业在提交时就指定新建的 ray-batch-b。这是两个提交条件不同的例子,没有把运行中的作业迁移到另一队列。
以 proportion 插件为例,它为每个参与计算的 Queue 构造一份 queueAttr。下面几个量看起来都在描述“队列有多少资源”,来源和用途却不同。
因此,“业务当前 CPU 使用率很低”和“调度器认为队列还占着很多资源”可以同时成立。只要相应 Pod 仍占用资源请求,节点或队列的调度账目就不会因为进程暂时空闲而自动归零。
guarantee 也需要结合资源条件理解。它参与资源分配策略,并不让没有 GPU 的集群凭空满足 GPU 请求,也不会消除节点亲和性造成的限制。
为看清权重的作用,先只算 CPU:本轮可分配总量为 12 核,没有其他占用;两个 Queue 优先级相同,权重分别为 1 和 2,保障量为零,上限不构成限制。假设 Pod 请求可以组成所需份额,内存和放置约束都已满足。
如果两个队列的需求都足够大,第一轮份额计算得到 4 核和 8 核。这里是在计算后续分配要遵循的额度,还没有把 Pod 放到节点上。源码中对应的动作,是把剩余资源乘以权重比例,加入各自 deserved。proportion.go 的连续节选如下:
1 | |
这里紧接着做了上限和需求裁剪。若 ray-batch 实际只需要 2 核,分给它的 4 核就会裁到 2 核;另一队列先得到 8 核。剩余 2 核继续分配给尚未满足需求、也未达到上限的队列,最终可以得到 2 核和 10 核。
第三种情况下,余下的 3 核不会继续计入这两个队列的份额:一个已经满足需求,另一个受到上限限制。只有后续 Pod 成功分配,份额才可能转化为实际占用;节点条件或 Gang 门槛还可能让其中一部分暂时用不上。权重 1:2 不是任何时刻都必须维持的占用比例,也不是永久固定的配额。
此外,deserved 的变化不会立即改变运行中的 Pod。假如第二个队列此前已经使用 10 核,现在第一队列新增需求,重新计算可能得到 4 核和 8 核,但第二个队列当前仍占着 10 核。如何让占用向新的份额靠拢,还需要等待资源自然释放或执行符合策略的回收。
proportion 注册的 Queue 排序函数先比较 Queue.spec.priority,相同时再比较资源份额使用程度。其 share 按各资源维度的 allocated / deserved 计算,取最大值。份额使用程度较低的队列,在这个比较中排在前面。
但从优先队列取出一个 Queue 后,还要执行资源可分配性检查。插件会计算分配候选 Pod 后的占用量,检查它是否超过该队列本轮的 deserved:
1 | |
这两行来自 queueAllocatable 的连续节选。排序决定先检查谁,可分配性决定这个候选还能不能继续拿资源。随后节点过滤仍可能失败,因此“队列有额度”和“Pod 能放下”还隔着一步。
Queue 内部再按 JobOrder 回调选择作业。第七篇的配置把 priority 放在 drf 前面;Session.JobOrderCompareFn 按启用回调的顺序,遇到非零比较结果就返回。两个作业优先级不同时,前面的 priority 已经作出选择,不会再把 DRF 算出的份额与它加权平均。比较仍相同时,框架才使用创建时间和 UID 等后备规则。
这里有两个不同的优先级:Queue.spec.priority 参与队列排序,PodGroup 引用的 PriorityClass 参与作业优先级。成员 Pod 还有自己的优先级字段。在调试“为什么它先运行”时,应先确认当前比较的是哪一层对象。
只数 Pod 个数无法比较资源占用:一个 Pod 请求 1 核 CPU,另一个可能请求 8 核和一张 GPU。DRF,即 Dominant Resource Fairness,用一个作业占用各类资源的最大比例表示它的主导资源份额。
单独构造一个数学例子:总资源为 16 核 CPU、4 张 GPU,两个作业同优先级,前面的排序回调没有分出先后。这里只看 DRF JobOrder 回调的比较,不声称这是主案例的实际配置。
两个作业占用的形状不同,主导份额却相同。若第二个作业只有 2 核、1 张 GPU,它的主导份额就是 25%,DRF 的这个比较会让它排在前面。实际能否再分配,仍然取决于它所在队列的份额、剩余资源和 Pod 约束。
源码 drf.calculateShare 逐一计算资源维度的 share,保留最大值。每次分配或撤销分配,插件通过事件回调调整已分配量,后续比较就能反映本轮发生的变化。
proportion 和这里的 DRF 作用层次也不同:前者计算 Queue 的份额并约束队列分配,后者在当前配置中参与 Job 排序等判断。Volcano 还提供 capacity 插件,直接读取 Queue.spec.deserved 作为配置份额。注意这个同名字段:proportion 的 deserved 是本轮算出的内存值,capacity 这里读取的是 Queue 声明中的配置值。更换插件后,不能继续套用前面的权重计算过程。另有按队列层级组织公平分配的层级 DRF,当前源码明确拒绝它与 proportion 的冲突组合;本篇不采用这种配置。
默认的 enqueue, allocate, backfill 不包含主动驱逐。这里的驱逐指让选中的已有 Pod 退出,把资源释放给等待者;被选中的 Pod 在源码中常称为 victim,下文称为受害成员。以下分析假设配置已启用对应 Action,等待中的作业也已经通过前置准入。单纯提高优先级,不足以越过所有准入和资源约束。
传统 preempt 处理队列内部的竞争。源码先寻找同一 Queue 中其他作业的候选成员,另外还有同一作业内成员竞争的分支。跨作业候选的核心条件是:
1 | |
候选还要满足可抢占状态、成员允许被抢占等条件。priority 插件在不同作业间比较作业优先级,在同一作业内比较成员优先级;Gang 等插件继续筛选受害者,节点约束也要验证。作业只是排得靠前,并不代表一定能驱逐任意一个正在运行的 Pod。
传统 reclaim 处理跨 Queue 的资源回收。它从其他队列里寻找允许被回收的运行中成员,检查来源队列的 reclaimable 设置,再交给策略回调判断。在 proportion 中,超过 deserved 的占用是筛选回收对象的重要依据。
传统路径下,Gang 回调有一项很具体的限制:它按候选依次减少受害作业的可用成员计数,只有计数高于 MinAvailable 时才允许继续选成员。例如某组正好只有最低要求的三个成员,不能把其中一个当作不影响成组要求的冗余成员回收。
当前源码另有 gangpreempt、gangreclaim,支持以组为单位组织驱逐决策,采用不同的候选组合模型。上面的限制针对本篇跟踪的传统 Action,不能推广成“Volcano 永远不会驱逐一个最低运行组”。这些新路径不在默认配置中,本篇不把它们与传统回调混写。
preempt 和 reclaim 同样使用 Statement。它们可以先推演受害成员退出后是否放得下等待者,并将等待者记为 Pipelined。没有满足要求时撤销尝试;提交驱逐以后,资源也不会在同一瞬间可用。Pod 终止、缓存观察到变化、等待成员重新分配和绑定,都有自己的时间窗口。
对 Ray 应用来说,被驱逐的 worker 上可能有用户任务、actor 或内存中的对象。后续 Pod 被补回,不代表这些计算必然无损恢复。Ray 的重试设置、检查点以及应用处理重复执行的方式,仍需按第四、五篇的故障边界分析。
把本篇的判断按执行顺序放回现场,可以缩小排查范围:
回到本系列的问题,KubeRay 创建了多少 Pod,与 Volcano 当前允许哪一组占用资源,由不同的循环决定。下一篇会逐字段核对 KubeRay 如何生成 PodGroup,并解释 submitter 还不存在时,为什么已经出现在组的最低资源量里。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。