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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# research-queue.yaml
apiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: research
spec:
reclaimable: true
weight: 1
deserved:
cpu: "2"
guarantee:
resource:
cpu: "1"
capability:
cpu: "8"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# production-queue.yaml
apiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: production
spec:
reclaimable: true # 注意:字段名是 reclaimable,不是 reclaimary
weight: 3
deserved:
cpu: "6"
guarantee:
resource:
cpu: "3"
capability:
cpu: "8"

配置铁律:guarantee ≤ deserved ≤ capabilityguarantee 是"锁死的底仓别人不能动",deserved 是"合理份额超了会被收",capability 是"绝对上限"。

Job 定义(关键是 queue 字段和 minAvailable):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# job-a1.yaml
apiVersion: batch.volcano.sh/v1alpha1
kind: Job
metadata:
name: job-a1
spec:
schedulerName: volcano
queue: research # 路由到 research queue
minAvailable: 3 # Gang 调度门槛
priorityClassName: low-priority
tasks:
- replicas: 3
name: worker
template:
spec:
containers:
- name: pytorch
image: pytorch/pytorch
resources:
requests:
cpu: "2" # 3 pods × 2C = 6C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# job-p1.yaml
apiVersion: batch.volcano.sh/v1alpha1
kind: Job
metadata:
name: job-p1
spec:
schedulerName: volcano
queue: production
minAvailable: 2
priorityClassName: high-priority
tasks:
- replicas: 2
name: worker
template:
spec:
containers:
- name: tensorflow
image: tensorflow/tensorflow
resources:
requests:
cpu: "2" # 2 pods × 2C = 4C

minAvailabletasks.replicas 可以不同——比如 5 个 replica 只要求 3 个同时运行。

调度器配置(volcano-scheduler-configmap):

1
2
3
4
5
6
7
8
9
10
actions: "enqueue, allocate, preempt, reclaim, backfill"
tiers:
- plugins:
- name: priority
- name: conformance
- plugins:
- name: capacity # 与 proportion 互斥
- name: predicates
- name: nodeorder
- name: binpack

重要约束:enqueuepreempt/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 的 minAvailableminResourcespriorityClassNamequeue 字段。

第二步: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 开始工作:

  1. 队列排序:capacity 插件在 session 构建时基于快照计算每个 Queue 的 share 值 = Allocated / Deserved(allocate action 基于此排序执行,不是动态计算):

    • research:0 / 2 = 0
    • production:0 / 6 = 0

    share 值相同时按队列 priority 排序。如果使用 proportion 插件,则是直接按 weight 比例瓜分集群资源。

  2. Job 排序:同队列内按 Job 的 priorityClassName 排序,高优先级先调度。

  3. 节点选择:3 个 task 各占 2C,research 队列 Allocated = 6C。

    注意:此时 research 的 Allocated(6C)已经超过其 deserved(2C)——这是资源借用:队列空闲时可以用其他队列的空闲资源,这正是 Volcano 弹性队列的设计目标。

  4. 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 执行:

  1. 检查 production 队列的 Allocated(0C)是否 ≥ Deserved(6C)→ 0 < 6,说明 production “有资格要回资源”
  2. 检查 research 队列:Allocated(6C)> Deserved(2C),超出 4C,且 reclaimable: true → 可以回收
  3. 回收上限:research 的 Guarantee 是 1C,回收停止条件是 Allocated = Guarantee → 最多可从 research 回收 6 - 1 = 5C
  4. 选择 victim:job-a1(6C)作为被回收对象,驱逐其 Pods
  5. 驱逐后 research 的 Allocated = 0C,集群空闲 = 8C
  6. 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-p1priorityClassName 是 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
2
3
4
5
root
├── research (deserved: 2C)
│ ├── research-nlp (deserved: 1C)
│ └── research-cv (deserved: 1C)
└── production (deserved: 6C)

如果 production 缺资源,优先从兄弟节点 research 回收,不会直接跨越到 research-nlpresearch-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
2
3
4
5
6
7
8
9
10
11
# 查看队列状态和已分配资源
kubectl get queue research -o yaml
# 关注 status.allocated(当前已分配)和 status.state(Open/Closed/Closing)

kubectl get podgroup job-a1 -o yaml
# 关注 status.phase(Pending/Inqueue/Running)

kubectl describe pod job-a1-worker-0
# 关注 Events 里的 "Scheduling succeeded" 或 "gang unschedulable" 消息

kubectl logs -n volcano-system deployment/volcano-scheduler | grep -E "(enqueue|allocate|reclaim|preempt)"

本文基于 Volcano 官方设计文档、capacity 插件设计文档、PodGroup 状态机文档及社区实践整理。数据截至 2026 年 8 月,具体字段语义请以所使用 Volcano 版本的官方文档为准。