Volcano 源码阅读:多个作业怎样共享与竞争资源

上一篇的 Gang 检查回答了一组 Pod 能否满足最低要求。现在有两个作业都在等资源:demo-job 和独立提交的 demo-job-b。它们应该按提交时间排队,按优先级选择,还是按已经占用的资源量轮流获得机会?

Volcano 把这些选择放在不同层次:Queue 表达资源策略,作业和成员各有排序,节点还有自己的放置约束。这篇沿 Volcano d8984501e4ad 的 proportion、drf 和传统 preempt、reclaim 路径阅读。默认配置仍承接第七篇;分析驱逐时会显式增加对应 Action。数字例子只演示算法,不表示主案例已经修改配置或完成压测。

1. Queue 里配置的值,和本轮算出的份额

先看两个作业都提交到 ray-batch 的情况。它们共享该 Queue 的资源策略;作业之间如何排序,稍后再看。讨论跨队列分配时,另设一个对比场景:第二个作业在提交时就指定新建的 ray-batch-b。这是两个提交条件不同的例子,没有把运行中的作业迁移到另一队列。

两个作业分别进入两个 Queue。Queue 是集群级对象,可以汇集多个命名空间的工作负载;图中仍沿用 default。资源份额不等于把节点永久切成两组。
两个作业分别进入两个 Queue。Queue 是集群级对象,可以汇集多个命名空间的工作负载;图中仍沿用 default。资源份额不等于把节点永久切成两组。 查看原图

以 proportion 插件为例,它为每个参与计算的 Queue 构造一份 queueAttr。下面几个量看起来都在描述“队列有多少资源”,来源和用途却不同。

名称 来源与含义
weight Queue 配置的权重,用于计算有需求队列的相对份额
capability Queue 配置的资源上限;插件还会结合总资源和其他队列保障量计算 realCapability
guarantee.resource 配置的保障资源,参与份额下限及其他队列可用上限的计算
request 插件从本轮可见的已分配和 Pending 成员汇总的需求
deserved proportion 在本轮依据权重、需求和上限算出的份额
allocated 调度模型中已经分配的资源量,按成员资源请求记账,不是实时 CPU 使用率

因此,“业务当前 CPU 使用率很低”和“调度器认为队列还占着很多资源”可以同时成立。只要相应 Pod 仍占用资源请求,节点或队列的调度账目就不会因为进程暂时空闲而自动归零。

guarantee 也需要结合资源条件理解。它参与资源分配策略,并不让没有 GPU 的集群凭空满足 GPU 请求,也不会消除节点亲和性造成的限制。

2. 用一组数字推演 proportion

为看清权重的作用,先只算 CPU:本轮可分配总量为 12 核,没有其他占用;两个 Queue 优先级相同,权重分别为 1 和 2,保障量为零,上限不构成限制。假设 Pod 请求可以组成所需份额,内存和放置约束都已满足。

如果两个队列的需求都足够大,第一轮份额计算得到 4 核和 8 核。这里是在计算后续分配要遵循的额度,还没有把 Pod 放到节点上。源码中对应的动作,是把剩余资源乘以权重比例,加入各自 deservedproportion.go 的连续节选如下:

1
2
3
4
5
6
7
8
9
oldDeserved := attr.deserved.Clone()
attr.deserved.Add(remaining.Clone().Multi(float64(attr.weight) / float64(totalWeight)))

if attr.realCapability != nil {
attr.deserved.MinDimensionResource(attr.realCapability, api.Infinity)
}
attr.deserved.MinDimensionResource(attr.request, api.Zero)

attr.deserved = helpers.Max(attr.deserved, attr.guarantee)

这里紧接着做了上限和需求裁剪。若 ray-batch 实际只需要 2 核,分给它的 4 核就会裁到 2 核;另一队列先得到 8 核。剩余 2 核继续分配给尚未满足需求、也未达到上限的队列,最终可以得到 2 核和 10 核。

本轮条件 ray-batch 的 deserved ray-batch-b 的 deserved 未计入两队列份额的量
双方需求均充足,上限不限制 4 核 8 核 0
第一队列只需 2 核,第二队列至少需 10 核 2 核 10 核 0
第一队列只需 2 核,第二队列上限为 7 核 2 核 7 核 3 核
同一组权重在不同需求与上限下得到不同份额。灰色部分表示未计入两队列 deserved 的余量,不代表实测节点空闲量。图中忽略其他资源维度。
同一组权重在不同需求与上限下得到不同份额。灰色部分表示未计入两队列 deserved 的余量,不代表实测节点空闲量。图中忽略其他资源维度。 查看原图

第三种情况下,余下的 3 核不会继续计入这两个队列的份额:一个已经满足需求,另一个受到上限限制。只有后续 Pod 成功分配,份额才可能转化为实际占用;节点条件或 Gang 门槛还可能让其中一部分暂时用不上。权重 1:2 不是任何时刻都必须维持的占用比例,也不是永久固定的配额。

此外,deserved 的变化不会立即改变运行中的 Pod。假如第二个队列此前已经使用 10 核,现在第一队列新增需求,重新计算可能得到 4 核和 8 核,但第二个队列当前仍占着 10 核。如何让占用向新的份额靠拢,还需要等待资源自然释放或执行符合策略的回收。

3. 排得靠前,和还能拿资源,是两件事

proportion 注册的 Queue 排序函数先比较 Queue.spec.priority,相同时再比较资源份额使用程度。其 share 按各资源维度的 allocated / deserved 计算,取最大值。份额使用程度较低的队列,在这个比较中排在前面。

但从优先队列取出一个 Queue 后,还要执行资源可分配性检查。插件会计算分配候选 Pod 后的占用量,检查它是否超过该队列本轮的 deserved:

1
2
futureUsed := attr.allocated.Clone().Add(totalReq)
allocatable, _ := futureUsed.LessEqualWithDimensionAndResourcesName(attr.deserved, totalReq)

这两行来自 queueAllocatable 的连续节选。排序决定先检查谁,可分配性决定这个候选还能不能继续拿资源。随后节点过滤仍可能失败,因此“队列有额度”和“Pod 能放下”还隔着一步。

Queue 内部再按 JobOrder 回调选择作业。第七篇的配置把 priority 放在 drf 前面;Session.JobOrderCompareFn 按启用回调的顺序,遇到非零比较结果就返回。两个作业优先级不同时,前面的 priority 已经作出选择,不会再把 DRF 算出的份额与它加权平均。比较仍相同时,框架才使用创建时间和 UID 等后备规则。

这里有两个不同的优先级:Queue.spec.priority 参与队列排序,PodGroup 引用的 PriorityClass 参与作业优先级。成员 Pod 还有自己的优先级字段。在调试“为什么它先运行”时,应先确认当前比较的是哪一层对象。

4. CPU 和 GPU 混用时,怎样衡量份额

只数 Pod 个数无法比较资源占用:一个 Pod 请求 1 核 CPU,另一个可能请求 8 核和一张 GPU。DRF,即 Dominant Resource Fairness,用一个作业占用各类资源的最大比例表示它的主导资源份额。

单独构造一个数学例子:总资源为 16 核 CPU、4 张 GPU,两个作业同优先级,前面的排序回调没有分出先后。这里只看 DRF JobOrder 回调的比较,不声称这是主案例的实际配置。

作业 已分配资源 CPU 份额 GPU 份额 主导份额
demo-job 8 核、1 张 GPU 8/16 = 50% 1/4 = 25% 50%
demo-job-b 2 核、2 张 GPU 2/16 = 12.5% 2/4 = 50% 50%

两个作业占用的形状不同,主导份额却相同。若第二个作业只有 2 核、1 张 GPU,它的主导份额就是 25%,DRF 的这个比较会让它排在前面。实际能否再分配,仍然取决于它所在队列的份额、剩余资源和 Pod 约束。

DRF 取各资源占用比例的最大值。两个作业的主导资源可以不同;此图只解释份额比较,没有推导下一次必然分配给谁。
DRF 取各资源占用比例的最大值。两个作业的主导资源可以不同;此图只解释份额比较,没有推导下一次必然分配给谁。 查看原图

源码 drf.calculateShare 逐一计算资源维度的 share,保留最大值。每次分配或撤销分配,插件通过事件回调调整已分配量,后续比较就能反映本轮发生的变化。

proportion 和这里的 DRF 作用层次也不同:前者计算 Queue 的份额并约束队列分配,后者在当前配置中参与 Job 排序等判断。Volcano 还提供 capacity 插件,直接读取 Queue.spec.deserved 作为配置份额。注意这个同名字段:proportion 的 deserved 是本轮算出的内存值,capacity 这里读取的是 Queue 声明中的配置值。更换插件后,不能继续套用前面的权重计算过程。另有按队列层级组织公平分配的层级 DRF,当前源码明确拒绝它与 proportion 的冲突组合;本篇不采用这种配置。

5. preempt 与 reclaim 怎样释放资源

默认的 enqueue, allocate, backfill 不包含主动驱逐。这里的驱逐指让选中的已有 Pod 退出,把资源释放给等待者;被选中的 Pod 在源码中常称为 victim,下文称为受害成员。以下分析假设配置已启用对应 Action,等待中的作业也已经通过前置准入。单纯提高优先级,不足以越过所有准入和资源约束。

传统 preempt 处理队列内部的竞争。源码先寻找同一 Queue 中其他作业的候选成员,另外还有同一作业内成员竞争的分支。跨作业候选的核心条件是:

1
return job.Queue == preemptorJob.Queue && preemptor.Job != task.Job

候选还要满足可抢占状态、成员允许被抢占等条件。priority 插件在不同作业间比较作业优先级,在同一作业内比较成员优先级;Gang 等插件继续筛选受害者,节点约束也要验证。作业只是排得靠前,并不代表一定能驱逐任意一个正在运行的 Pod。

传统 reclaim 处理跨 Queue 的资源回收。它从其他队列里寻找允许被回收的运行中成员,检查来源队列的 reclaimable 设置,再交给策略回调判断。在 proportion 中,超过 deserved 的占用是筛选回收对象的重要依据。

路径 主要竞争范围 还要检查什么
preempt 同 Queue 的作业之间,以及同作业内成员之间 优先级、可抢占性、Gang、节点条件等
reclaim 不同 Queue 之间 来源队列允许回收、资源份额、成员可抢占性、Gang、节点条件等

传统路径下,Gang 回调有一项很具体的限制:它按候选依次减少受害作业的可用成员计数,只有计数高于 MinAvailable 时才允许继续选成员。例如某组正好只有最低要求的三个成员,不能把其中一个当作不影响成组要求的冗余成员回收。

当前源码另有 gangpreemptgangreclaim,支持以组为单位组织驱逐决策,采用不同的候选组合模型。上面的限制针对本篇跟踪的传统 Action,不能推广成“Volcano 永远不会驱逐一个最低运行组”。这些新路径不在默认配置中,本篇不把它们与传统回调混写。

传统驱逐路径先推演、再提交。候选不满足条件时撤销本轮尝试;提交后仍需等待受害 Pod 退出,未来可用资源才能成为当前可用资源。
传统驱逐路径先推演、再提交。候选不满足条件时撤销本轮尝试;提交后仍需等待受害 Pod 退出,未来可用资源才能成为当前可用资源。 查看原图

preempt 和 reclaim 同样使用 Statement。它们可以先推演受害成员退出后是否放得下等待者,并将等待者记为 Pipelined。没有满足要求时撤销尝试;提交驱逐以后,资源也不会在同一瞬间可用。Pod 终止、缓存观察到变化、等待成员重新分配和绑定,都有自己的时间窗口。

对 Ray 应用来说,被驱逐的 worker 上可能有用户任务、actor 或内存中的对象。后续 Pod 被补回,不代表这些计算必然无损恢复。Ray 的重试设置、检查点以及应用处理重复执行的方式,仍需按第四、五篇的故障边界分析。

6. 有空闲资源,为什么作业还在排队

把本篇的判断按执行顺序放回现场,可以缩小排查范围:

  1. 查看实际加载的 Action、插件和参数。没有启用驱逐,就先分析自然释放条件;作业未准入,则先查 enqueue。
  2. 查看 Queue 是否 Open、PodGroup 的队列引用是否正确,再比较需求、上限、保障量、deserved 和 allocated。不要用实时 CPU 利用率替代资源请求账目。
  3. 查看单个 Pod 的放置失败原因。资源碎片、GPU 型号、节点亲和性和存储条件,都可能让总量足够的资源无法使用。还要看节点污点:这是节点上用来排斥特定 Pod 的标记,相应 Pod 需要配置容忍条件才能通过这类检查。
  4. 查看 Gang 的最低要求与可用成员。一个候选放不下,可能使本轮已试分配的部分被撤销。
  5. 若已触发驱逐,检查受害 Pod 是否真正退出,以及等待成员的绑定和启动是否完成。

回到本系列的问题,KubeRay 创建了多少 Pod,与 Volcano 当前允许哪一组占用资源,由不同的循环决定。下一篇会逐字段核对 KubeRay 如何生成 PodGroup,并解释 submitter 还不存在时,为什么已经出现在组的最低资源量里。

参考资料


Volcano 源码阅读:多个作业怎样共享与竞争资源
https://tanxinyu.work/kuberay-volcano-08-volcano-queues/
作者
谭新宇
发布于
2026年9月15日
许可协议