Volcano Queue 全流程拆解:一个 Job 从提交到运行
Volcano Queue 全流程拆解:一个 Job 从提交到运行
本文是 一个集群,多个调度器:Kubernetes 多调度器共存实践指南 的下篇——上一篇讲了多调度器并存,本文深入 Volcano 内部,回答一个更具体的问题:Queue 在整个调度流程中到底扮演什么角色?我用一个"两个队列、三个 Job"的资源博弈场景,把从 YAML 提交到 Pod 运行的每一步拆开看——包括 YAML 怎么写、每个字段如何影响调度决策、队列之间如何"借用"与"回收"资源。
引言:为什么需要理解 Queue 的完整流程
Volcano 的 Queue 是多租户资源分配的最上层抽象——它不参与"Pod 放哪个节点"的决策,而是先决定"这个任务有没有资格、以什么优先级、能用多少资源进入调度"。
如果没有 Queue,调度器只有"先来后到"一种公平观:谁先提交谁先用,大任务可能饿死小任务,一个团队的突发负载可以吃光整个集群。Queue 补上了配额隔离、弹性借用、公平分配三块拼图。
但 Queue 的字段语义(deserved/guarantee/capability)和调度动作(enqueue/allocate/preempt/reclaim)交织在一起时,行为并不直观。最好的理解方式就是完整走一遍流程。
全景流程图
flowchart TD
A[用户提交 vcjob] --> B{job 指定了 queue?}
B -- 否 --> C[注入 default queue]
B -- 是 --> D[校验 queue 状态是否 Open]
D -- Closed/Closing --> E[拒绝创建 Job]
D -- Open --> F[vc-controller 创建 PodGroup]
F --> G[PodGroup: Pending]
G --> H["enqueue action
检查 minResources 能否被满足"]
H -- 不满足 --> G
H -- 满足 --> I[PodGroup: Inqueue]
I --> J[vc-controller 开始创建 Pods]
J --> K["allocate action
按队列 share/priority 掐序"]
K --> L{"Gang 约束满足?
可调度数 ≥ minAvailable"}
L -- 否 --> M[不 bind, 等待下一轮]
L -- 是 --> N["PodGroup: Running
Pods 绑定节点"]
N --> O{资源不够?}
O -- 跨队列借用方需求 --> Q["reclaim action
回收超 deserved 部分"]
O -- 同队列高优先级 --> P["preempt action
队列内驱逐"]
Q --> R[被驱逐的 PodGroup 重新 Inqueue]
P --> R
场景设定:两个队列、三个 Job 的资源博弈
集群总资源:8C CPU
| Queue | deserved | guarantee | capability | reclaimable |
|---|---|---|---|---|
| research | 2C | 1C | 8C | true |
| production | 2C | 3C | 8C | true |
三个 Job 按时间顺序提交:
job-a1(research 队列,需 6C)job-p1(production 队列,需 4C)job-p2(production 队列,需 3C,稍后提交)
YAML 配置
Queue 定义(使用 capacity 插件,Volcano v1.9+ 推荐):
1 | # research-queue.yaml |
1 | # production-queue.yaml |
配置铁律:
guarantee ≤ deserved ≤ capability。guarantee是"锁死的底仓别人不能动",deserved是"合理份额超了会被收",capability是"绝对上限"。
Job 定义(关键是 queue 字段和 minAvailable):
1 | # job-a1.yaml |
1 | # job-p1.yaml |
minAvailable与tasks.replicas可以不同——比如 5 个 replica 只要求 3 个同时运行。
调度器配置(volcano-scheduler-configmap):
1 | actions: "enqueue, allocate, preempt, reclaim, backfill" |
重要约束:
enqueue与preempt/reclaim存在冲突——如果enqueue判定 Job 不能入队,PodGroup 会留在 Pending、Pods 不会被创建,preempt/reclaim就没有目标可以驱逐。官方文档明确提醒了这一点。
逐步流程拆解
第一步:Job 提交与 PodGroup 创建
用户执行 kubectl apply -f job-a1.yaml。volcano-admission webhook 校验:
- Job 指定的
queue: research是否存在 - 该 Queue 的
status.state是否为Open(只有 Open 状态的 Queue 接受新任务,Closed/Closing 状态直接拒绝)
校验通过后,vc-controller 为 Job 创建一个 PodGroup(名为 job-a1,带 ownerReferences 指向 Job),PodGroup 的 spec 继承 Job 的 minAvailable、minResources、priorityClassName、queue 字段。
第二步:enqueue——准入检查
volcano-scheduler 每个调度周期(默认 1 秒)都会执行一轮 action 序列。enqueue action 是第一步,它判断这个 Job 的 minResources 总和当前集群能否满足:
job-a1的 minResources = 3 pods × 2C = 6C- 集群当前空闲 = 8C(初始状态)
- 6C ≤ 8C → 满足
PodGroup 状态从 Pending → Inqueue。只有进入 Inqueue 状态,vc-controller 才会真正为该 PodGroup 创建 Pods。这个设计避免了资源不足时大量僵尸 Pod 堆积,提高调度器在高负载场景下的性能。
如果 minResources 无法满足(比如集群只剩 2C 但 Job 需要 6C),PodGroup 会一直卡在 Pending,Pods 不会被创建。这时如果你配置了 preempt/reclaim 想让它去抢资源,会发现根本抢不到——因为 enqueue 的准入拒绝在前,后面的抢占动作没有目标可执行。
第三步:与 allocate 的 Gang 调度与队列排序
Pods 创建后进入 Pending 状态。allocate action 开始工作:
-
队列排序:capacity 插件在 session 构建时基于快照计算每个 Queue 的 share 值 =
Allocated / Deserved(allocate action 基于此排序执行,不是动态计算):- research:0 / 2 = 0
- production:0 / 6 = 0
share 值相同时按队列 priority 排序。如果使用 proportion 插件,则是直接按 weight 比例瓜分集群资源。
-
Job 排序:同队列内按 Job 的
priorityClassName排序,高优先级先调度。 -
节点选择:3 个 task 各占 2C,research 队列 Allocated = 6C。
注意:此时 research 的 Allocated(6C)已经超过其 deserved(2C)——这是资源借用:队列空闲时可以用其他队列的空闲资源,这正是 Volcano 弹性队列的设计目标。
-
Gang 约束检查:allocate 遍历
job-a1的 3 个 task,为每个 task 模拟寻找节点。只有 3 个 task 都找到可用节点(即达到minAvailable=3的门槛)才真正执行 bind 操作。否则一个都不绑定,等待下一轮调度。
第四步:第二个 Job 触发 reclaim
用户提交 job-p1(production 队列,需 4C):
- enqueue 阶段:
job-p1的 minResources = 2 × 2C = 4C,集群当前空闲 = 8 - 6 = 2C < 4C → 看似不满足
这里就是 reclaim 发挥作用的地方。实际判断逻辑不仅看当前空闲,还会考虑"如果回收其他队列超发的资源,能否满足"。Volcano 的 reclaim action 被设计为在 allocate 之后、enqueue 发现问题后触发:
reclaim action 执行:
- 检查 production 队列的 Allocated(0C)是否 ≥ Deserved(6C)→ 0 < 6,说明 production “有资格要回资源”
- 检查 research 队列:Allocated(6C)> Deserved(2C),超出 4C,且
reclaimable: true→ 可以回收 - 回收上限:research 的 Guarantee 是 1C,回收停止条件是
Allocated = Guarantee→ 最多可从 research 回收 6 - 1 = 5C - 选择 victim:
job-a1(6C)作为被回收对象,驱逐其 Pods - 驱逐后 research 的 Allocated = 0C,集群空闲 = 8C
job-p1的 allocate:现在集群空闲 8C ≥ 4C → 成功调度 2 个 Pod(各 2C),production 的 Allocated = 4C
关键细节:被驱逐的 job-a1 的 PodGroup 不会回到 Pending,而是重新回到 Inqueue 状态等待下一轮 allocate——它需要重新排队竞争资源。
第五步:同队列 preempt
如果 production 队列内又提交了 job-p2(需 3C,priorityClassName 更高),而 production 当前 Allocated = 4C,deserved = 6C,集群空闲 = 8 - 4 = 4C ≥ 3C → 正常 allocate,不触发 preempt。
但如果集群空闲只剩 1C(比如有其他负载),job-p2 的 allocate 失败 → preempt action 在同队列内寻找 victim:
job-p1的priorityClassName是 high-priority,job-p2更高 →job-p1被驱逐job-p2调度成功
preempt 只发生在同一 Queue 内部,跨 Queue 的资源竞争走的是 reclaim 而非 preempt。这是两个动作的本质区别。
PodGroup 状态机
整个流程中,PodGroup 的状态转换是观察调度行为的最佳窗口:
| 状态 | 含义 | 触发条件 |
|---|---|---|
| Pending | Job 刚提交,尚未准入 | 初始状态 |
| Inqueue | 通过 enqueue 检查,Pods 将被创建 | minResources 满足 |
| Running | Gang 调度成功,Pods 已绑定 | 可调度 Pod 数 ≥ minAvailable |
| Completed | 所有 Pods 正常结束 | Job 完成 |
| Unknown | 异常状态 | 极少出现 |
一个容易被忽略的细节:PodGroup 处于 Inqueue 但资源正在释放时,PodGroup 的 phase 不会反映"资源正在释放"这一中间态——这是 v1.14 及以前版本的已知行为。如果配置中没有 enqueue action(如 actions: "allocate, backfill"),PodGroup 可能直接进入 Inqueue 而不经过 Pending→Inqueue 的准入检查。
Queue 之间的相互影响机制
上述场景展示了 Queue 间最核心的三种交互模式:
| 交互模式 | 触发条件 | 影响范围 | 示例 |
|---|---|---|---|
| 借用 | 其他队列空闲,本队列 Job 需要更多资源 | 临时超 deserved 使用 | job-a1 借用了 4C |
| 回收 | 队列的 Allocated > Deserved,且其他队列有需求 | 驱逐超发部分的 Pods | job-a1 被回收给 job-p1 |
| 保护 | 队列的 Guarantee 部分 | 其他队列无法回收 | research 保底 1C |
capacity 插件的设计文档明确描述了这三种语义的边界:
- Capability:队列绝对上限,任何情况下不可超过
- Guarantee:保留资源,其他队列无法回收,即使队列空闲也不外借
- Deserved:合理份额,超出部分可被回收
Guarantee 的存在让"回收"不会赶尽杀绝——job-a1 被回收后,如果它改配置为只需要 1C,仍能保证在 research 队列里运行,因为 1C 是 Guarantee 的保底资源,其他队列不能动。
层级 Queue 的额外维度
如果上述场景改为层级结构(比如 root 下挂 research 和 production,各自再下挂子队列),回收逻辑遵循"先兄弟后祖先"原则:
1 | root |
如果 production 缺资源,优先从兄弟节点 research 回收,不会直接跨越到 research-nlp 或 research-cv——除非 research 整个队列都超发了。两个约束:任务只能提交到叶子队列;父队列已有任务时不能再创建子队列。
常见坑与排查命令
坑一:enqueue 与 preempt/reclaim 冲突。如果 Job 因为 minResources 太大卡在 Pending,但你想让它通过抢占获得资源——这不会发生,因为 enqueue 的准入拒绝在前。解法是确保 minResources 设置合理,或者去掉 enqueue action(但这会失去"防止大量僵尸 Pod"的保护)。
坑二:capacity 与 proportion 插件互斥。两个插件不能同时启用,必须在 scheduler 配置里二选一。v1.9+ 推荐 capacity(直观配置 deserved),proportion 自动按 weight 计算。
坑三:reclaim 的连锁反应。A 回收 B 的任务,B 再回收 C 的任务,可能引发"乒乓效应"。生产环境常通过 reclaimable: false 保护关键队列,或将 Guarantee 设置得足够高来稳定边界。
排查命令:
1 | # 查看队列状态和已分配资源 |
本文基于 Volcano 官方设计文档、capacity 插件设计文档、PodGroup 状态机文档及社区实践整理。数据截至 2026 年 8 月,具体字段语义请以所使用 Volcano 版本的官方文档为准。
