07 Volcano 实战
07 Volcano 实战
这一章解决上一章留下的「Gang 问题」。Volcano 是 CNCF 首个基于 K8s 的批处理调度系统(华为开源),AI/大数据训练的标配调度器。你之前对话里反复问的「Volcano 是什么、怎么用」,这里一次讲透。
💡 给你的话:Volcano 对你来说不陌生——它就是个装在 K8s 上的「批处理调度器」,用 CRD 描述作业,和你用过的任何 Operator 体系同构。
7.1 为什么需要 Volcano
默认调度器痛点(见 06):
- 分布式训练「整组卡同时到位」做不到 → 死锁。
- 没有队列/配额/公平份额。
- 没有任务依赖、优先级、抢占的批处理语义。
Volcano 补上这些,专为 AI 训练、HPC(高性能计算)、大数据 批量负载设计。
名词解释:HPC(High-Performance Computing,高性能计算) 指传统科学/工程超算,也是批处理负载,和 AI 训练调度需求类似。批处理(Batch) 指一次性提交、跑完即退的工作负载(区别于常驻服务)。
7.2 核心概念
| 概念 | 含义 | K8s 类比 |
|---|---|---|
| Queue | 资源池/租户,带配额(如 team-a 最多用 64 卡) |
类似 ResourceQuota,但更强 |
| Job(vcjob) | 一次完整训练/批处理作业 | 类似 Job/Deployment,但支持多任务 |
| Task | Job 内的任务单元(对应一组 Pod) | Job 里的 template |
| PodGroup | 把多个 Pod 绑成「一组」统一调度(Gang 的载体) | Volcano 核心 CRD |
关键心智:Volcano 把「一组 Pod」当一个整体调度,而不是一个个孤 Pod 各自抢资源。
⭐ 重点:
PodGroup是 Gang 的载体——它声明「这一组 Pod 必须minAvailable个同时就绪才能启动」。这是 Volcano 区别于原生调度的灵魂。
7.3 Gang Scheduling(⭐最重要)
问题:8 卡训练任务,默认调度器可能先给 4 卡就启动 4 个 Pod,剩 4 卡一直等 → 已启动的 4 卡空转、整任务卡死。
Gang 语义:整组 Pod 必须同时拿到全部资源才启动;否则全部等待,不占任何资源。
8 卡 Job
├─ 集群只剩 6 卡空闲 → Gang 不满足 → 全部等待(不启动任何 Pod)
└─ 集群有 8 卡空闲 → 同时满足 → 8 个 Pod 同时启动
Volcano 的 gang 插件实现此语义,避免资源碎片死锁。
💡 类比:Gang Scheduling 像餐厅「8 人桌必须人到齐才入座」——避免来 4 个先占一桌、剩下 4 个永远等不到的尴尬。默认调度器是「来一个坐一个」,所以会卡死。
7.4 公平调度与优先级
- Queue 公平:多个 Queue 按
weight分配集群算力(如 team-a:team-b = 2:1)。- 公平份额(Fair Share):按权重瓜分资源,避免一个团队独占。
- 优先级:Job 可设
priorityClassName,高优抢占低优。 - binpack / spread:
- binpack(装箱):集中填满节点,减少碎片。
- spread(打散):分散到多节点,容错/拓扑。
- 拓扑感知:
numa-topology等插件考虑 NVLink/NUMA 亲和。- 名词解释:NUMA(Non-Uniform Memory Access,非一致内存访问) 是多 CPU 节点的内存架构,跨 NUMA 访问慢。调度考虑 NUMA 亲和能让任务用本地内存更快。
7.5 部署与实战
# 安装(Helm)
helm repo add volcano https://volcano-sh.github.io/helm-charts
helm repo update
helm install volcano volcano/volcano -n volcano-tns --create-namespace
# 创建队列
cat <<EOF | kubectl apply -f -
apiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata: {name: team-a}
spec: {capability: {nvidia.com/gpu: 8}, weight: 1}
EOF
提交一个 8 卡 Gang 训练 Job:
apiVersion: batch.volcano.sh/v1beta1
kind: Job
metadata: {name: train-8gpu}
spec:
minAvailable: 8 # Gang:必须 8 个 Pod 同时就绪
queue: team-a
tasks:
- name: worker
replicas: 8
template:
spec:
containers:
- name: train
image: pytorch/pytorch:2.2.0-cuda12.1
command: ["python","train.py"]
resources:
limits: {nvidia.com/gpu: 1}
restartPolicy: Never
EOF
kubectl apply -f job.yaml
观察:kubectl get pods——要么 8 个同时 Running,要么都在 Pending(Gang 未满足),不会出现「启动 4 个卡死」。
⭐ 注意 API 版本已统一为 v1beta1(早期资料可能写 v1alpha1,已过时)。
7.6 源码导读(面试加分:能读源码)
Volcano scheduler 核心目录:pkg/scheduler/
cache:集群资源/状态的本地缓存session:一次调度会话(在一个调度周期内构建)action:调度动作(如enqueue、allocate、preempt)plugin:各调度策略(gang插件就在pkg/scheduler/plugins/gang)
读源码路径建议:
- 从
pkg/scheduler/scheduler.go的Schedule()入手 - 看
action/allocate.go:怎么选 Job → 选节点 - 读
plugins/gang/gang.go:怎么判断minAvailable是否满足 - 理解
session怎么在一次调度里复用 cache,避免反复访问 API Server
⭐ 重点:面试能说「我读过 Volcano gang 插件源码,它怎么用 PodGroup 的 minAvailable 判断整组是否满足」——这比「我会用 Volcano」强一个量级,正好呼应你云原生 + 读源码的优势。
7.7 Volcano 的局限(引出 08)
Volcano 解决了 Gang 和队列,但到千卡/万卡、在离线混部、细粒度隔离时仍不够:
- 调度吞吐串行,大集群慢。
- 偏离线批处理,在线+离线混部弱。
- 细粒度算力/显存切分要靠 qGPU 等外挂。
这就是
08要讲的:大厂在 Volcano/kube-scheduler 之上做了什么。
7.8 实战:在 kind 上安装 Volcano 并体验 Gang 调度
这一节在 MacBook/Linux 上用 kind 集群安装 Volcano,亲手提交一个 Gang 训练任务,观察「要么全起、要么全等」的效果。
7.8.1 环境准备(复用 04 章的 kind 环境)
# 如果还没有 kind 集群,创建一个
cat <<EOF | kind create cluster --name volcano-lab --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
- role: worker # 两个 worker,模拟多节点
EOF
kubectl get nodes
# 应有 1 个 control-plane + 2 个 worker
7.8.2 安装 Volcano
# ① 添加 Volcano Helm 仓库
helm repo add volcano-sh https://volcano-sh.github.io/helm-charts
helm repo update
# ② 安装 Volcano(含 scheduler + controller + admission)
helm install volcano volcano-sh/volcano \
-n volcano-system --create-namespace
# ③ 等待组件就绪
kubectl get pods -n volcano-system -w
# 等到所有 Pod 为 Running:
# volcano-admission-xxx Running
# volcano-controllers-xxx Running
# volcano-scheduler-xxx Running
# ④ 验证 CRD 已安装
kubectl get crd | grep volcano
# 应看到:
# commands.bus.volcano.sh
# jobs.batch.volcano.sh
# podgroups.scheduling.volcano.sh
# queues.scheduling.volcano.sh
目的:Volcano 安装后会注册 4 个 CRD——
Job(训练任务)、PodGroup(Gang 组)、Queue(队列)、Command。这三个 CRD 是你后面提交任务的接口。
7.8.3 创建队列
# 创建一个队列(模拟「训练团队」的资源池)
cat <<EOF | kubectl apply -f -
apiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: training-queue
spec:
weight: 1 # 队列权重(多队列时按权重分资源)
reclaimable: true # 空闲时资源可被其他队列 reclaim
capability:
cpu: "4" # 队列最多用 4 核 CPU
memory: "8Gi"
EOF
# 验证队列
kubectl get queue training-queue
# NAME WEIGHT
# training-queue 1
目的:Queue 是 Volcano 的资源配额单元。多团队共用集群时,每团队一个 Queue,按
weight分配公平份额。
7.8.4 提交一个 Gang 训练任务
# 提交一个 4 worker 的「训练任务」
# minAvailable: 4 表示「4 个 worker 必须全部拿到资源才启动」
cat <<EOF | kubectl apply -f -
apiVersion: batch.volcano.sh/v1alpha1
kind: Job
metadata:
name: gang-training-demo
spec:
minAvailable: 4 # ★ Gang 调度的核心:必须 4 个全到位
schedulerName: volcano # 用 Volcano 调度器(不是默认调度器)
queue: training-queue
policies:
- event: PodEvicted
action: RestartJob # 有 Pod 被驱逐时,整个 Job 重启
tasks:
- replicas: 4 # 4 个 worker
name: worker
template:
spec:
containers:
- name: worker
image: busybox:1.36
command: ["sh", "-c", "echo 'Worker $HOSTNAME started!'; sleep 300"]
resources:
limits:
cpu: "1" # 每个 worker 要 1 核 CPU
memory: "1Gi"
restartPolicy: Never
EOF
# 观察 Pod 状态
kubectl get pods | grep gang-training
# 应看到 4 个 Pod 都快速进入 Running(因为资源够)
# 看 PodGroup 状态
kubectl get podgroup
# NAME STATUS
# gang-training-demo Running
目的:这是 Gang 调度的正例——4 个 worker 同时拿到资源、同时启动。如果用默认调度器,可能会出现「先起 2 个、再等 2 个」的情况,Gang 调度保证「要么 4 个一起起、要么都不起」。
7.8.5 观察 Gang 死锁(负例)
# 提交一个需要 10 个 worker 的任务(但集群只有 ~4 核 CPU 可用)
cat <<EOF | kubectl apply -f -
apiVersion: batch.volcano.sh/v1alpha1
kind: Job
metadata:
name: gang-deadlock-demo
spec:
minAvailable: 10 # ★ 要 10 个,但资源不够
schedulerName: volcano
queue: training-queue
tasks:
- replicas: 10
name: worker
template:
spec:
containers:
- name: worker
image: busybox:1.36
command: ["sleep", "300"]
resources:
limits:
cpu: "1"
restartPolicy: Never
EOF
# 观察——所有 Pod 都 Pending,不会启动任何 1 个
kubectl get pods | grep gang-deadlock
# STATUS 应全部为 Pending
# 看 PodGroup 状态
kubectl get podgroup gang-deadlock-demo -o yaml | grep -A3 status
# status.phase 应为 Inqueue(在队列里,但不够资源调度)
# ★ 关键:这些 Pending 的 Pod 不占资源!
# 默认调度器会让部分 Pod 启动占着资源等其余的(死锁)
# Volcano 让它们全等,不占资源——其他任务可以用空闲资源
目的:这是 Gang 调度的核心价值——资源不够时全部等待,不占资源。如果用默认调度器,可能先起 5 个占着 5 核 CPU,剩下 5 个永远等不到 → 死锁,且 5 核 CPU 被白白占用。
7.8.6 验证不占资源的好处
# 此时 gang-deadlock-demo 的 10 个 Pod 都 Pending(不占资源)
# 提交一个普通任务,应该能正常调度
kubectl run normal-job --image=busybox --command -- sleep 60
kubectl get pod normal-job
# 应为 Running——因为 gang-deadlock 没占资源
# 如果用默认调度器(非 Gang):
# gang-deadlock 会先起 5 个 Pod 占 5 核 → normal-job 可能 Pending(资源被占)
# 这就是「死锁」的危害
7.8.7 安装 Kueue 管配额(进阶)
# Kueue 是 K8s sig-scheduling 的队列管理工具
# 添加仓库
helm repo add kueue https://kubernetes-sigs.github.io/kueue
helm repo update
# 安装 Kueue
helm install kueue kueue/kueue \
-n kueue-system --create-namespace
# 创建 ResourceFlavor(抽象资源形态)
cat <<EOF | kubectl apply -f -
apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
name: default-flavor
EOF
# 创建 ClusterQueue(集群级队列,管配额)
cat <<EOF | kubectl apply -f -
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
name: team-a-queue
spec:
namespaceSelector: {}
queueingStrategy: BestEffortFIFO
resourceGroups:
- coveredResources: ["cpu", "memory"]
flavors:
- name: default-flavor
resources:
- name: cpu
nominalQuota: "4"
- name: memory
nominalQuota: "8Gi"
EOF
# 创建 LocalQueue(命名空间级,用户用它提交任务)
cat <<EOF | kubectl apply -f -
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
name: user-queue
namespace: default
spec:
clusterQueue: team-a-queue
EOF
# 验证
kubectl get clusterqueue,localqueue
目的:Kueue 管的是「配额」——每个团队最多能用多少资源。Volcano 管「Gang 调度」——整组同时起。两者配合:Kueue 管配额,Volcano 管调度策略。
7.8.8 清理
kubectl delete job.batch.volcano.sh gang-training-demo gang-deadlock-demo --ignore-not-found
helm uninstall volcano -n volcano-system
kubectl delete namespace volcano-system --ignore-not-found
kind delete cluster --name volcano-lab
7.8.9 实战总结
| 你做了什么 | 对应概念 |
|---|---|
| 安装 Volcano | 7.1-7.3 架构与 CRD |
| 创建 Queue | 7.4 队列与公平 |
| 提交 Gang Job(4 worker) | Gang 正例:全起 |
| 提交 Gang Job(10 worker) | Gang 负例:全等不占资源 |
| 验证不占资源 | Gang 避免死锁的价值 |
| 安装 Kueue | 配额管理 |
7.9 自测题
- Volcano 是什么、谁开源、在 CNCF 什么地位?
- Queue/Job/Task/PodGroup 分别是什么?哪个是 Gang 的载体?
- 什么是 Gang Scheduling?解决了什么死锁?用餐厅类比解释。
minAvailable: 8的含义?API 版本是 v1alpha1 还是 v1beta1?- Queue 的
weight控制什么?binpack 和 spread 有何区别? - Volcano 源码里 gang 插件大概在哪个目录?它依据什么判断整组可调度?
- ⭐ Volcano 在大规模/混部场景有哪些局限?
答上即可进
08——看字节/腾讯在 Volcano 之上还做了什么。