Volcano 笔记
按:一个任务的一生来梳理 Volcano,重点包括:
- 从 vcjob 提交、controller 创建 PodGroup/Pod、scheduler 执行 enqueue/allocate/bind,一直到 kubelet 起容器、Job Policy 处理异常、TTL 自动清理
- 把 Queue、PodGroup、gang、action、plugin 这些核心概念挂到同一条主线上,方便理解调度过程
- 补充网络拓扑感知、层级队列、在线离线混部、反调度、TDM、GPU/NPU、扩展方式、生态和运维排查
- 最后记录一次 CRD 版本不匹配导致 vcjob 不创建 PodGroup 的真实排查过程
Volcano 笔记:以"一个任务的一生"为主线
基于看完官方文档 + 一次真实排查整理。 Volcano 版本迭代较快(尤其 CRD 集合),照着做前先确认自己集群的版本。
一句话定位
Volcano = 给 K8s 装一个批处理 / AI 友好的调度大脑。
K8s 原生 kube-scheduler 是「一个 Pod 一个 Pod 地看」,但 AI / 大数据任务需要:
- 一组 Pod 要么一起上、要么都别上(gang,避免分布式任务部分启动死锁)
- 队列级的公平与配额(多团队共享集群)
- 抢占 / 回收 / 防饿死(资源紧张时的腾挪)
这些原生调度器都不擅长,正是 Volcano 的存在意义。
核心扩展点:Volcano Job(vcjob) 这种新工作负载 + 自研 volcano-scheduler(action + plugin 可插拔)+ volcano-controller(把 vcjob 翻译成 PodGroup/Pod)+ 一套 webhook。
1. 主线:一个任务从提交到结束,经历了什么
这是整篇笔记的骨架。其余所有概念都挂在这条线上。
flowchart TD
A[提交 vcjob] --> B[controller 创建 PodGroup + Pod]
B --> C{enqueue<br/>基本资源够吗?}
C -- 不够 --> C1[PodGroup: Pending<br/>继续排队]
C -- 够 --> D[PodGroup: Inqueue]
D --> E{allocate<br/>串行遍历 job<br/>plugin 过滤+打分}
E -- gang 凑不齐/无合适节点 --> E1[保持 Pending<br/>等下一轮]
E -- 资源不足且需腾挪 --> F[preempt 队内抢占<br/>reclaim 队间回收]
E -- 选到节点 --> G[bind 到节点]
F --> G
E2[backfill: 边角资源喂 BestEffort] -.-> G
G --> H[【脱离 Volcano】<br/>kubelet: 拉镜像/起容器]
H --> I[Pod Running<br/>job plugin 已注入组网信息]
I --> J[运行中: Job Policy 处理 Pod 失败/驱逐事件]
J --> K[结束: TTL 到点自动清理]
关键分界线:Pod 被 bind 到节点之前,一切都在 Volcano 手里(Pending 的 vcjob pod 等的是 volcano-scheduler,不是 kube-scheduler);bind 之后进入 ContainerCreating,就脱离 Volcano,问题归 kubelet / 镜像 / CNI / 存储。
2. 基本概念(挂在主线上)
四个概念是层层包含关系,不是并列:
| 概念 | 是什么 | 在主线哪一步 |
|---|---|---|
| Volcano Job (vcjob) | 批处理任务定义,内含多个 task(每个 task 多副本) | 第①步:你提交的东西 |
| Queue | 资源池 / 配额边界,多团队共享集群的隔离单位 | vcjob 归属某 queue;proportion/reclaim 以它为单位 |
| PodGroup (pg) | 一组 Pod 的调度单位,gang 调度的最小单位 | controller 据 vcjob 创建;调度以它为单位 |
| Cron VolcanoJob | 定时触发的 vcjob(类似 CronJob) | 定时产生 vcjob,后续流程相同 |
关系:
vcjob提交 → controller 建PodGroup(归属某Queue)→ gang 以 PodGroup 为单位「全有或全无」。
PodGroup 的关键状态机(排查时最常看):
Pending ──enqueue放行──> Inqueue ──allocate成功──> Running
3. 调度器内核:action(流程)+ plugin(算法)
核心区分:
- action = 调度流水线的「步骤」,定义"走哪几步”。
- plugin = 挂在 action 上的「算法」,定义"每步用什么逻辑、谁说了算”。
- configmap(
volcano-scheduler-configmap)里的actions和tiers只能组合已编译进二进制的 action/plugin,不能凭空造新逻辑。
调度循环:每隔 ~1 秒 OpenSession(快照资源+排序 job)→ 按序执行 actions → CloseSession(真正 bind)。
单 session 串行处理所有 job,每分配一个就立即在内存快照里扣减资源 → 天然不会 over-commit(不会出现两个 gang 任务都判断通过的"写倾斜”)。
3.1 Action 清单
| action | 作用 | 维度 | 默认开? |
|---|---|---|---|
| enqueue | 判断基本资源够不够进队,Pending→Inqueue | 进场 | ✅ |
| allocate | 给有明确资源请求的 task 精确选节点 | 分配 | ✅ |
| backfill | 把剩余边角资源喂给 BestEffort / 说不清需求的小任务 | 填空 | ✅ |
| preempt | 队内按优先级抢占低优任务 | 队内 | ❌ 需手动加 |
| reclaim | 队间按配额回收被借走的资源 | 队间 | ❌ 需手动加 |
preempt vs reclaim 是最容易混的一对:
- preempt:同一 queue 内,依据 task 优先级(PriorityClass),驱逐队内低优任务。
- reclaim:queue 之间,依据 queue 权重/配额,把别队超额借用的资源要回来。
开启抢占+回收的典型配置:
|
|
3.2 Plugin 清单(按职责分四组记,比平铺好用)
A. 能不能一起上
| plugin | 作用 |
|—|—|
| gang | 全有或全无:满足 minAvailable 才整组调度,否则一个都不下发。Volcano 的灵魂。 |
B. 给谁(公平 / 优先级 / 防饿死)
| plugin | 作用 |
|—|—|
| priority | 按显式 PriorityClassName 排座次(人为指定)。排在 DRF 前,是更强的决定因素。 |
| drf | Dominant Resource Fairness:多资源下取每个 job 占比最高的「主导资源」,让各 job 的「主导份额」尽量拉平,占得少的优先。管 job 之间 公平。 |
| proportion | 按 queue 权重分配集群配额。管 queue 之间。也是 reclaim 的依据。 |
| capacity | 比 proportion 更新的队列资源管理插件;SchedulingGatesQueueAdmission 功能实现在这里面。 |
| sla | Service Level Agreement:给 job 设最长等待时间 JobWaitingTime,等够久就提优先级,防饿死。 |
| cdp | Cooldown Protection:任务起来后给一段冷却保护期,防止刚调度上就被抢占/驱逐反复横跳。与 sla 是一对(一个防等不到,一个防刚上就被踢)。 |
C. 能放哪(过滤 / 约束) | plugin | 作用 | |—|—| | predicates | 节点是否满足 task 的硬性条件(资源、亲和、污点等),GPU 场景常用。 | | numa-aware | 节点内 NUMA 感知:把 pod 的 CPU/内存收拢到单个 NUMA node,减少跨 NUMA 损耗。需节点开 cpuManager=static + 装 resource exporter。 | | task-topology | 同一 job 内不同 task 的亲和/反亲和(如 TF 的 ps 与 worker 同机)。 |
D. 放哪最好(打分 / 排序)
| plugin | 作用 |
|—|—|
| nodeorder | 多维度综合打分,选最合适节点。 |
| binpack | 装箱:尽量塞满已用节点、减少碎片,利于节点缩容。 |
| resource-strategy-fit | 按资源类型分别设策略:CPU/内存 LeastAllocated(摊开)、GPU MostAllocated(塞满腾整机)。比 binpack 更细。 |
其它/混部相关(见第 6 节单列)
- TDM(Time Division Multiplexing):和 YARN 等错峰分时共用节点。
3.3 tiers 分层与 tie-break
tiers 把 plugin 分层,同名函数按 tier 顺序逐层调用,前层可「一票否决」(返回 <0 即停)。
两个 job 谁先调度的 tie-break 链:priority → drf → … → 创建时间(先到先得) 兜底,保证排序确定性。
→ 想在平手时指定赢家,用 PriorityClass(priority 在 DRF 前),而不是动 DRF。
默认配置参考:
|
|
4. 任务跑起来:Job Plugin(组网与运行)
gang 保证「一起被调度」,但「调度上了怎么组成一个分布式任务」靠 job plugin(写在 vcjob 的 spec.plugins,和 scheduler plugin 是两回事):
| job plugin | 作用 | 给身份/互联/发现 |
|---|---|---|
| env | 注入环境变量(如 task 索引 VK_TASK_INDEX) |
给身份 |
| svc | 建 headless service + 主机信息,pod 间 DNS 互相发现 | 给发现 |
| ssh | pod 间免密 SSH(MPI 必备) | 给互联 |
| pytorch | 自动注入 MASTER_ADDR/PORT/WORLD_SIZE/RANK、建 svc、让 worker 等 master | 框架级编排(底层强制依赖 svc) |
| ray | 配 head/worker 启动命令、开三端口、建 head svc(需同时开 svc) | 框架级编排 |
| mpi | mpirun launcher + worker 编排(依赖 ssh) | 框架级编排 |
pytorch/ray/mpi 本质 = 把 env/svc/ssh 组合 + 再注入框架专属配置。
5. 任务的容错与清理(生命周期收尾)
| 机制 | 作用 | 例子 |
|---|---|---|
| Job Policy | event → action 的容错规则 | PodEvicted → RestartJob(任一 pod 被驱逐整组重启) |
| Job TTL | 结束后定时自动清理 | ttlSecondsAfterFinished: 3600 |
Policy 可选 action:RestartJob / RestartTask / AbortJob / TerminateJob / CompleteJob。
6. 主线之外的重要模块
这些不一定出现在「单个任务提交」的主线里,但都是 Volcano 的重要能力。
6.1 网络拓扑感知(多机训练用)
- 节点之间的网络层级(node → 交换机/tor → spine),让多卡 worker 尽量同交换机,减少跨交换机通信。
- 靠 HyperNode CR 描述拓扑;支持自动发现(v1.12+,源:UFM = NVIDIA InfiniBand 管理平台 / RoCE(暂不支持)/ label)。
- vcjob 里用
networkTopology(mode: hard/soft + highestTierAllowed)+partitionPolicy(子组)表达约束。 - ⚠️ 和 NUMA 区分:NUMA 是「节点内」CPU/内存局部性,HyperNode 是「节点间」网络局部性,两套数据源。
层级示意(让一个 job 的多卡 worker 收拢在同一 tier1 交换机下):
flowchart TD
S[Spine tier2<br/>跨交换机骨干]
S --> A[交换机 A tier1<br/>同交换机=低延迟高吞吐]
S --> B[交换机 B tier1<br/>另一个性能域]
A --> N1[node-1<br/>8 GPU]
A --> N2[node-2<br/>8 GPU]
B --> N3[node-3<br/>8 GPU]
B --> N4[node-4<br/>8 GPU]
C[调度约束 networkTopology<br/>hard=必须同域 / highestTierAllowed] -.约束.-> A
H[拓扑来源 HyperNode CR<br/>UFM/label 自动发现] -.提供层级.-> S
读法:hard 模式下,一个训练 job 的 4 个 worker 会被强制收拢在同一个 tier1(交换机 A 或 B)内,避免跨 spine 通信。拓扑数据由 HyperNode CR 提供,可经 UFM 或节点 label 自动发现生成。
YAML ①:vcjob 侧——声明拓扑约束
|
|
超大规模(一个交换机放不下)用 partitionPolicy 做"多级”:整体允许跨 tier2,每个子组内部仍锁 tier1。
|
|
YAML ②:HyperNode 自动发现(UFM 源,NVIDIA InfiniBand)——controller configmap
|
|
|
|
没有 UFM 时可用
source: label,靠给节点打volcano.sh/hypernode、volcano.sh/hypercluster等标签从下往上构建层级。
6.2 Hierarchical Queue(层级队列)
- 队列按部门/团队分层,配合 hierarchical DRF 做多层公平与配额。
6.3 Cloud Native Colocation(在线离线混部)
- 在线(高优、延迟敏感)和离线(低优、可被压制)业务混跑同一批节点,提升利用率。
- 需要额外的 volcano-agent(节点侧组件):做资源超卖、QoS 保障、干扰检测、必要时驱逐/压制离线任务。
- 相关 CRD:
colocationconfigurations.config.volcano.sh(⚠️ 排查就是因为这个 CRD 缺失,导致新版 job-controller 卡在 cache sync)。
结构示意(同一节点分层共享,agent 兜底 QoS):
flowchart TD
subgraph NODE[一个物理节点 · 资源池]
ON[在线业务 高优<br/>推理/Web,占住 request,优先扩张]
OFF[离线业务 低优<br/>批处理/训练,吃超卖资源,一抢就让路]
end
AGENT[volcano-agent 节点侧<br/>超卖 · QoS · 干扰检测 · 压制/驱逐离线]
AGENT -->|监控并保障| ON
AGENT -->|压制/驱逐| OFF
读法:在线占住自己的 request 且优先;离线吃节点空闲/超卖资源跑。volcano-agent 实时盯着,一旦干扰检测发现在线受影响,就压制甚至驱逐离线任务,优先保障在线 QoS。 ⚠️ 注意和 6.1 的区别:拓扑感知管「任务放哪台机器」(节点间),混部管「同一台机器上两类业务怎么共处」(节点内)。
YAML ①:工作负载侧——用 QoS 等级标记在线/离线
|
|
YAML ②:ColocationConfiguration——超卖与压制策略(CRD)
|
|
部署与排查
|
|
6.4 Load-aware Descheduling(反调度)
- 调度是「放上去的那一刻」最优,但运行久了节点负载会失衡(热点)。
- 反调度器周期性检查,驱逐部分 pod 让它们重新被调度,把负载从过热节点摊匀。
- 和调度互补:一个管「进来时放哪」,一个管「跑起来后再平衡」。
6.5 TDM(时分复用)
- K8s 与 YARN 混部同一批机器,按时间窗错峰共用。把共享节点标记为 revocable node,在 revocable time 内放 preemptable task、窗外驱逐。
- 仅在和 Hadoop/YARN 混部时才用得上。
6.6 GPU / NPU 能力(vGPU)
| 能力 | 说明 |
|---|---|
| GPU Number / Sharing | 按整卡数 / 多 pod 共享一张卡 |
| vGPU / GPU Virtualization | 虚拟化切分(显存+算力),主要 NVIDIA |
| Ascend vNPU | 华为昇腾 NPU 的虚拟化(注意是 NPU 不是 GPU) |
| DRA | 动态资源分配(K8s 新机制)在 Volcano 的支持 |
6.7 其它特性
- Multi-Cluster AI Job Scheduling:跨多个 K8s 集群分发 AI 任务。
- Unified Scheduling:统一调度原生工作负载 + vcjob。
- SchedulingGatesQueueAdmission(v1.15+,在 capacity 插件里):用 K8s schedulingGates 把「排队中」的 pod 对 autoscaler 隐藏,避免把"排队"误判成"缺机器"而触发 Cluster Autoscaler / Karpenter 乱扩节点。代价:目前无配额预留超时。
7. 自定义扩展(三条路)
| 方式 | 改源码/编译? | 适合 |
|---|---|---|
| in-tree plugin | 要 | 复杂、性能敏感、要深度访问调度上下文(抢占/入队/gang) |
| 自定义 action | 要 | 加一整个新流水线步骤(很少需要) |
| extender(HTTP 回调) | 不要 | 大多数「加点自定义过滤/打分」需求,迭代独立 |
extender 是挂在已有 allocate action 上的已有 extender plugin,只覆盖**过滤(predicate)+ 打分(prioritize)**两个点,管不了 enqueue/preempt/gang。
- 协议沿用 K8s
kube-scheduler/extender/v1(ExtenderArgs / ExtenderFilterResult / HostPriorityList)。 - 用任意语言(如 Spring Boot)起 HTTP 服务实现
/predicate、/prioritize即可。 - ⚠️ 性能敏感(每轮 session、每 pod、每节点都可能回调):务必
extender.ignorable: true+ 紧httpTimeout。
|
|
8. 生态(各计算框架 on Volcano)
让这些框架的任务用 Volcano 做 gang 调度,而不是各自的默认调度:
Flink / Kubeflow / MindSpore / MPI / PaddlePaddle / Spark / TensorFlow / Ray on Volcano。
共同价值:把「一组相关 pod」交给 Volcano 统一 gang 调度 + 队列公平,避免框架各自为战导致的资源死锁与不公平。
9. 监控 & 运维
9.1 可观测
- 自带 Dashboard。
- Prometheus 指标(调度延迟、队列状态、pending 任务数等)。
9.2 排查 SOP(实战核心)
flowchart TD
Q0[现象:任务起不来] --> Q1{PodGroup 创建了吗?}
Q1 -- 否 --> C[controller 层问题<br/>查 vc-controllers 日志 / CRD 是否齐 / 版本匹配]
Q1 -- 是 --> Q2{PodGroup 什么状态?}
Q2 -- Pending --> P[enqueue 没过<br/>queue 配额 / queue Closed / 集群资源不足]
Q2 -- Inqueue --> S[allocate 没过,看 pod]
Q2 -- Running --> OK[PG 层 OK,看 pod]
S --> Q3
OK --> Q3{Pod 还 Pending?}
Q3 -- 是,describe pod 看 scheduler Events --> R{报错类型}
R -- gang --> R1[minAvailable 没凑齐]
R -- Insufficient xxx --> R2[节点资源不够 GPU/CPU]
R -- predicate/topology --> R3[节点过滤/拓扑约束没过]
R -- queue --> R4[队列配额/状态问题]
Q3 -- 否,已 ContainerCreating --> OUT[已脱离 Volcano<br/>查 kubelet/镜像/CNI/存储]
常用命令:
|
|
9.3 真实踩坑记录(这次排查的复盘)
- 现象:vcjob 提交成功,但无 Pod、无 PodGroup;但 volcano-system 的 pod 都 Running;同集群 Deployment 能正常建 PG。
- 关键日志:
Failed to watch *v1alpha1.ColocationConfiguration: could not find the requested resource。 - 错误猜测:以为 controller-manager 整体挂了(被 pod Running 否定)。
- 真因:异常集群 controller 是更新版本(多了 serving 类 CRD、少了 nodeshards),其 job-controller 依赖的 CRD(colocation/nodeshards 等)没装齐 → informer 永远 cache sync 不上 → job-controller 卡死,不为 vcjob 建 PG;而 podgroup-controller 不依赖这些 → Deployment 照常建 PG → 这就解释了「为什么只有 vcjob 不行」。
- 修复:用 controller 镜像对应版本的清单完整重装 CRD,重启 controller。
- 教训:① pod Running ≠ controller 健康(可能卡在 WaitForCacheSync);② CRD 集合必须和 controller 版本严格一致;③「只有某类工作负载不行」是定位到具体 controller 的关键线索。
10. 贯穿全局的几条规律(最该记住的)
- 三层分工:
controller(建 PG/Pod)→scheduler(决定放哪)→kubelet(真正跑)。排查先定位在哪一层。 - PodGroup 是 gang 的灵魂:以 PodGroup 为单位「全有或全无」,是 Volcano 区别于原生调度器的根本。
- 串行调度避免 over-commit:单 session 顺序处理 + 内存即时扣减 → 不会写倾斜。
- action 是流程,plugin 是算法:configmap 只能组合已编译进二进制的;新逻辑要么改源码(in-tree),要么 extender 外置。
- 公平有三个维度别混:priority(人为优先级)/ drf(job 间多资源公平)/ proportion(queue 间配额)。
- 抢占/回收/防饿死/防驱逐是一组对立统一:preempt+reclaim 让"抢得到”,sla 防"被饿死”,cdp 防"刚上就被踢”。
- 边界意识:Pod 一旦 bind 进 ContainerCreating,就不再是 Volcano 的问题。