机器学习平台技术栈之Volcano:概念、架构与关键技术深度剖析
随着大模型和人工智能技术的爆发,底层的算力基础设施迎来了前所未有的挑战。传统的 Kubernetes 调度器最初为微服务(长运行的无状态或有状态服务)设计,在面对机器学习训练、大数据分析等 高性能计算(HPC) 和 批处理(Batch Processing) 任务时,显得捉襟见肘。
为了解决云原生环境下机器学习平台对高级调度的需求,CNCF 首个批量计算项目 Volcano 应运而生。本文将深度解析 Volcano 的核心概念、架构设计、关键技术细节,以及它在机器学习平台中扮演的底层基石角色。
摘要
在云原生 AI 基础设施中,调度系统的能力直接决定了算力集群的利用率和分布式训练的效率。本文从 Kubernetes 原生调度器的痛点出发,系统性地阐述 Volcano 的核心对象及其相互关系,深度拆解其架构模块,并详细剖析了 Gang Scheduling(组调度)、Fair-share(公平调度)、资源队列与多租户机制、拓扑感知等关键技术细节,最后探讨其在真实机器学习平台生态(如 Kubeflow、Ray 结合)中的最佳实践。
一、 引言:为什么机器学习平台需要 Volcano?
1. 原生 Kubernetes 调度的局限性
在构建机器学习平台(特别是分布式训练平台)时,工作负载(Workload)相较于 Web 微服务有着截然不同的特征:
- All-or-Nothing(全有或全无)范式:分布式训练(如 MPI、TensorFlow Parameter-Server 架构)通常包含多个互相通信的 Worker/PS 节点。如果集群资源只够启动一半的 Worker,这些 Worker 会一直等待剩余 Worker 启动而空转,导致资源死锁(Deadlock)和严重浪费。Kubernetes 原生
kube-scheduler是一款逐个 Pod 调度的调度器,无法感知”一组 Pod“的存在。 - 作业排队与多租户资源共享:原生的 ResourceQuota 只能做硬性限制。机器学习平台常需要多个团队共享大规模 GPU 集群。此时需要队列(Queue)机制,既能做资源隔离,又能支持配额借用(Elastic Quota)和基于权重的公平调度(Fair-Share)。
- 异构计算与拓扑感知:大模型训练对 GPU 互联(NVLink、NVSwitch)、NUMA 节点、RDMA 网络提出了极高要求。调度器不仅要找到有资源的节点,还要找到通信距离最短、拓扑最优的节点组合。而原生调度器缺乏对底层硬件拓扑的深度感知。
- 作业的生命周期管理与任务类型复杂性:AI 作业往往由多个不同的角色组成(如 Spark 的 Driver/Executor,TF 的 PS/Worker)。原生 workload(Deployment, Job)缺乏对这种拓扑结构的支持。
2. Volcano 的定位
Volcano 是由华为云主导开源、CNCF 托管的云原生批处理计算项目。它在 Kubernetes 原生调度能力的基础上,提供了一个针对 AI、大数据、基因测序等计算密集型场景的大规模、高性能调度引擎。它弥补了 K8s 与机器学习平台之间的鸿沟,是诸如 Kubeflow、KubeSphere 等诸多平台的底层依赖。
二、 Volcano 核心概念解析
为了打破 Kubernetes 中 Pod 作为最小调度单元的限制,Volcano 引入了一系列针对批量计算领域定制的 CRD(自定义资源)。深入理解这些概念是掌握 Volcano 的基础。
1. Job (Volcano Job / vcjob)
vcjob 是 Volcano 自定义的批量计算作业抽象。与 Kubernetes 原生 Job 相比,vcjob 支持更复杂的拓扑结构:
- Task(任务)类型抽象 :一个
vcjob可以包含多个Task。例如,一个 TensorFlow 作业可以配置一个包含 2 个 PS 副本的 Task,以及一个包含 4 个 Worker 副本的 Task。不同的 Task 可以指定不同的资源申请和镜像。 - 生命周期策略 (Policies) :支持作业级别的生命周期管理(如某个 Task 失败时,是重启整个 Job 还是仅重启该 Task?),处理批处理任务的容错逻辑。
- 插件体系抽象 (Plugins) :允许为作业挂载不同的插件(如 ssh, env, svc 等),自动为分布式训练配置 SSH 互信、环境变量(如
MASTER_ADDR,MASTER_PORT)和内部 DNS 解析。
2. PodGroup (PG)
PodGroup 是 Volcano 最核心的概念之一,是实现 Gang Scheduling(组调度)的载体。
- 定义:它将一组逻辑上强关联的 Pod 绑定在一起。
- 核心属性
minMember:定义了这个 PodGroup 能够启动工作的最小 Pod 数量阈值。只有当集群中可分配给该组的资源满足minMember的需求时,调度器才会真正为这组 Pod 进行节点绑定(Bind),否则所有 Pod 都保持 Pending 状态。
3. Queue (队列)
队列用于管理资源池和作业排队。它是集群资源分配与多租户共享的基础。
- Weight(权重):当多个队列竞争集群资源时,按照权重比例分配资源。
- Capability(容量上限) & Deserved(应得资源):在 Elastic Quota 场景下支持资源的超卖和借用。
- 作业提交时必须指定目标队列,Volcano 根据队列状态决定哪些作业可以出队进入调度周期。
4. Command
用于声明式地控制 vcjob 的生命周期动作(Action),如 Abort、Restart、Resume 等。主要提供更底层的控制面。
三、 概念间的关系与生命周期作业流
在机器学习平台生态中,用户(或上一层 Operator)触发的任务,底层的对象转换和调度流如下:
graph TD
User(["机器学习平台 / 用户"]) -->|1. 提交训练任务| API("K8s API Server")
API -->|2. 触发| Ctrl["Volcano Job Controller"]
Ctrl -->|3. 生成原生物件| API_PG("PodGroup / Pods")
API_PG -.->|4. 加入| Queue["Queue 队列排队"]
subgraph S1 ["Volcano Scheduler 周期调度"]
Queue -->|5. Enqueue Action| Cache["Session Cache 快照"]
Cache -->|6. Allocate and Plugins 打分| Pre["预分配 Virtual Allocation"]
Pre -->|7. 满足 minMember| Bind["Batch Bind 批量绑定"]
end
Bind -->|8. 下发到节点| Nodes[("集群 Nodes: GPU/CPU")]- 作业创建 :机器学习平台(如 Kubeflow TF-Operator)解析用户的训练请求,生成 K8s 的原生物件或 Volcano
vcjob,并指定属于某个Queue。 - PodGroup 的生成 :如果是
vcjob,Volcano 控制器会自动为该 Job 创建对应的PodGroup;如果使用原生组件部署,可通过 Annotation 将 Pod 注入到显式的PodGroup中。 - 入队排队 (Enqueuing) :新创建的
PodGroup携带minMember需求进入Queue排队。Volcano 调度器周期性扫描队列状态。 - 配额与准入分配 :调度器计算集群空闲资源与各个
Queue的权重关系。只有资源满足的PodGroup才会进入InQueue状态等待后续绑定。 - Session 调度周期 :调度器在一个 Session(调度周期)内,通过多个 Action(如 enqueue, allocate, preempt)和 Plugins(如 gang, proportion, drf),为 PodGroup 内的 Pod 寻找合适的 Node。
- All-or-Nothing 资源预留与绑定 :由于
minMember机制,调度器在内部缓存中模拟分配资源,直到分配总数达到minMember后,才全量执行真实集群里的 Bind 动作,启动 Worker。 - 生命周期回收 :当作业计算完成,触发相应的 Policies 清理资源并释放回 Queue。
四、 Volcano 整体架构设计
Volcano 的架构极为精简且高度可扩展,充分体现了云原生解耦模式。其核心组件主要包含三环:volcano-scheduler、volcano-controllers 和 volcano-admission。
graph TD
classDef comp fill:#f9f,stroke:#333,stroke-width:2px;
classDef kube fill:#e6f2ff,stroke:#333,stroke-width:1px;
API["Kubernetes API Server"]:::kube
NodeOS["Kubelet Nodes"]:::kube
subgraph S2 ["Volcano Components"]
VA["Volcano Admission Webhook拦截校验"]:::comp
VC["Volcano Controllers Job/PG/Queue 控制器"]:::comp
VS["Volcano Scheduler 核心调度引擎"]:::comp
VS_Cache["Session and Cache"]:::comp -.-> VS
VS_Action["Actions: Enqueue/Allocate"]:::comp -.-> VS
VS_Plug["Plugins: Gang/DRF/Binpack"]:::comp -.-> VS
end
User("AI Platform") -->|"1. Submit/Review"| VA
VA -->|"Mutate/Validate"| API
API -->|"Watch/Update"| VC
VC -->|Update| API
API -->|"Watch/Bind"| VS
VS -->|Bind| API
VC -->|"管理生命周期"| API
API -->|Deploy| NodeOS1. Volcano Scheduler (调度器心脏)
Volcano 的调度器并没有采用原生 K8s 调度器的扩展(如 Scheduler Framework),而是完全重构了一套专注于批处理的调度逻辑。
它的架构设计极其灵活,由 Action(动作)和 Plugin(插件)两套扩展机制交织而成:
- Cache (缓存层) :实时监听 APIServer(Watch K8s 的 Node、Pod、PodGroup、Queue),维护集群全局的资源视图(状态机)。
- Session (调度周期抽象) :Scheduler 采用事务性的周期调度。每次调度循环称为一个 Session,在一个 Session 开始时,它会获取一个集群资源的 snapshot(快照),后续该周期所有的调度计算都在快照上进行。这大幅降低了锁竞争并提升了高并发下的吞吐量。
- Actions (调度动作:流程引擎) :定义了调度的宏观步骤。例如:
enqueue:决定哪些作业能进入队列。allocate:为待调度的 Pod 分配节点(核心分配)。preempt:资源不足时,抢占低优先级作业。reclaim:队列资源倾斜时,回收被”借用”出的配额。backfill:回填,充分利用利用碎片化资源对小任务进行调度。
- Plugins (插件体系:算法支撑) :为 Action 中涉及的具体计算提供算法支持(即“打分”或“过滤”标准)。不同的 Plugin 会注入到不同 Action 的钩子函数中(如
JobValidFn,NodeFilterFn,TaskOrderFn)。
2. Volcano Controllers
它是整个系统的驱动器,主要包括:
- Job Controller:管理 vcjob 的整个生命周期,负责将 Job 翻译成后端的 Pod、Service、ConfigMap 等资源,处理重启、重试等失败策略。
- Queue Controller:维护和更新各个队列的资源状态。
- PodGroup Controller:通过监听原生 Pod 的注解(Annotation)为第三方 Operator 提供自动注入支持。
3. Volcano Admission (Webhook)
基于准入控制机制(Mutating/Validating Webhook),提供请求合法性校验。同时,它还负责为提交的作业注入缺省值配置,并在多租户防越权上提供安全防线。
五、 关键技术细节深度剖析
对于机器学习平台工程师而言,真正的核心在于理解 Volcano 的内部调度算法与能力。
1. Gang Scheduling (组调度) 与 All-or-Nothing 的实现机制
问题背景:传统的深度学习训练是同步的(如 Ring-AllReduce 架构的 PyTorch DDP)。若 8 张卡只有 7 张就绪,训练流程将陷入无休止的 Wait。原生调度器逐个调度 Pod,在多任务竞争时会极易发生**资源死锁 (Deadlock)**。
graph TD
classDef pod_class fill:#ffcce6,stroke:#ff66b2;
classDef node_class fill:#e6f3ff,stroke:#66b3ff;
subgraph S3 ["原生K8s调度-易死锁"]
direction LR
JobA_P1["A1: 已分配"]:::pod_class --> N1["Node 1: 满"]:::node_class
JobB_P1["B1: 已分配"]:::pod_class --> N2["Node 2: 满"]:::node_class
JobA_P2["A2: Pending"]:::pod_class -.->|"无资源"| N1
JobB_P2["B2: Pending"]:::pod_class -.->|"无资源"| N2
note1("互相卡死等待: Deadlock") -.-> JobA_P2
end
subgraph S4 ["Volcano Gang 调度 All-or-Nothing"]
direction LR
PG["PodGroup minMember=2"]
P1["Job C-1"]:::pod_class --> PG
P2["Job C-2"]:::pod_class --> PG
PG -->|"满足 2 个"| N3["Node 3 批量分配"]:::node_class
PG -->|"满足 2 个"| N4["Node 4 批量分配"]:::node_class
note2("不够资源则全部 Pending 不霸占") -.-> PG
end技术实现:
Volcano 中的 gang 插件主导了这个过程,其本质是在调度器内存中执行一次预分配事务:
- 预判定 (Validating) :
gang插件在enqueue阶段会判断:集群当前所有空闲资源 + 可抢占资源 是否 $\ge$ 当前PodGroup的minMember需求限制?如果不满足,该作业直接原地 Waiting,绝不浪费哪怕一丁点集群算力。 - 虚拟分配 (Virtual Allocation) :在
allocate动作中,调度器遍历组内的 Pods 并为其匹配最佳节点,更新 Session Cache 中的占用情况(此刻并未真实写入 etcd)。 - Commit(提交校验) :当该组被成功虚拟分配的 Pod 数量到达
minMember设定阈值后,状态机会将这批分配操作标记为“就绪”。 - 批处理下发 (Batch Binding) :只有标志位为就绪,才批量调用 K8s APIServer 绑定节点,保障任务被”同进同出”地调度。
2. 队列模型:多租户与 Fair-Share(公平调度算法)
在大型 AI 集群(成百上千张 GPU)中,算法团队 A 和团队 B 经常为了 GPU 资源发生争执。Volcano 提供强大的队列模型,支持弹性超卖。
Dominant Resource Fairness (DRF) 算法:
经典的 DRF 算法起源于 Mesos 和 Hadoop YARN。DRF 解决的问题是在多维资源约束(如 CPU, Memory, GPU)下,如何公平地给不同任务分配资源。
- Volcano 的
drf插件会计算每个作业在各个资源维度上的主导资源份额(Dominant Resource Share)。 - 主导份额 = 作业需要的各类资源占集群该类资源总量的最大比例。
- 在排序时,主导份额低(即当前占用集群核心资源较少或饥饿时间较长)的作业优先级会被提升,从而获得更多的调度机会。
- 结合
proportion插件控制由于各个 Queue 被设置了不同 Weight(如权重比为 A:B = 2:1),系统不仅能在整体维度实现 DRF,还能保证在队列粒度上严格遵循按配比分配。
弹性资源共享与回收 (Reclaim) :
- 当队列 A 繁忙而队列 B 空闲时,Volcano 允许队列 A 借用 (Borrow) 队列 B 的空闲 GPU。这就是资源超卖率的实现基础。
- 但当队列 B 恢复活跃需要资源时,Volcano 触发
reclaim动作,识别出 A 中使用优先级较低的作业(超出了 A 的 guarantees 配额边界的作业),优雅终止它们并将资源还给 B,保障 SLA(服务级别协议)。
3. Preemption 抢占机制
在机器学习的线上/线下混合部署集群中,高优先级(如核心在线推荐模型更新)必须要能打断低优先级(如探索性研究训练)。
- 通过
priority插件与preemptAction,调度器在资源耗尽时会主动驱逐低优先级的 Pods。 - 受害者选择算法 (Victim Selection) :并非盲目抢占。调度引擎计算出抢占最小代价路径,优先选择优先级级差最大、资源贴合度最高的低优 PodGroup 进行干预。
4. 拓扑感知 (Topology Awareness) 调度
模型规模步入千亿级,仅仅找到 GPU 已经不够用了。不同节点下的 NUMA 架构,以及同一台服务器上的 PCIe/NVLink 拓扑对于训练性能影响极大。如果跨 NUMA 节点调度 CPU 与 GPU 的数据拷贝,将产生巨大的显存带宽瓶颈。
技术实现与 NUMA 亲和性:
- NFD (Node Feature Discovery) :依赖集群节点上报详尽的微架构拓扑(多少个 NUMA Socket、GPU 插槽位置与互联情况等)。
numa-aware插件 :在 Node 打分阶段起作用。- 当为一个深度学习 Pod 分配诸如 CPU 和内存时,Volcano 会与 kubelet Topology Manager 联动或者自身算法评估,尽量将 CPU Core 和相关联的 GPU 分配在同一个 NUMA Node 下。
- Task Topology :对于含有多个大任务的分布式训练作业,调度器支持配置
taskTopology策略。可以将 PS 和 Worker 以anti-affinity打散提高容灾容错率,或者通过特定亲和性尽量汇聚在同一个机架内的节点上(优化 RDMA RoCE 网络距离跳数)。
5. Binpack 与装箱优化
传统的 Kubernetes 过滤分配为了避免争抢(Spread 策略),倾向于将 Pod 打散到所有节点。但这对于离线计算是致命的——这会产生大量的资源碎片(比如一台有 8 张卡的机器,被零散地占用了 3 张,导致后续需要一次性申请 4 卡以上的训练任务永远无法被分配到一个 Node 上)。
Binpack 插件则反其道而行之:
它采用“最坏适应降配版”(即集中填满机制),对节点打分时,优先给剩余可用资源较少(但刚好能塞下当前调度的 Pod)的节点打高分。
通过尽量填满一台主机的资源(如塞满一整台 8 卡 GPU 机器),将大块的空余整机资源留给后续大型分布式训练作业,大幅度提升了机器学习集群的整体利用率。
六、 Volcano 在机器学习生态中的实战地位
在真实的工业界中,上层的机器学习平台(MLOps)多通过整合 Volcano 来打造底层调度底座。
1. 与 Kubeflow 的深度绑定
Kubernetes 社区上层的 AI 生态中,Kubeflow 的 training-operator(涵盖 TFJob、PyTorchJob、MPIJob、XGBoostJob)原生内置了对 Volcano 的支持。
- 用户部署时,只需在 PyTorchJob 的 YAML 中加上
schedulingPolicy: volcano。 training-operator在生成底层 Pod 之前,会自动创建PodGroup资源对象。- 整个训练由于 Volcano 的接管,具备了强劲的组调度、排队和死锁规避能力。这被很多公司(如腾讯云、阿里云的底层容器调度方案)采纳为标配。
2. 与 Ray 的集成协同
大模型时代,Ray 作为分布式计算框架正在迅速崛起(服务于 vLLM、强化学习场景)。KubeRay 项目集成 Volcano 极大增强了异构池化调度:
- 可以保障 Ray Cluster 中的 Head Pod 与核心 Worker Pod 以 Gang 形式拉起,避免因为算力不足导致的集群启动时效性问题。
3. 数据流预处理与弹性作业
对于数据清洗与预处理(ETL Pipeline/Spark 任务),它们虽然不需要全有或全无机制,但也极其需要依赖队列管理的超卖和公平共享体系,Volcano 通过支持 Spark-on-K8s 对离线处理提供极佳支撑,从而打通了从”数据导入” $\to$ “预处理” $\to$ “训练” $\to$ “评估” 的整个生命周期的资源一体化管理。
七、 总结与未来展望
作为以云计算视角切入 HPC 与 AI 基础设施建设的核心组件,Volcano 通过一套极具扩展性的精妙架构,一举解决了跨节点的协同调度、资源共享的安全与公平、大规模集群的利用碎片以及硬件拓扑敏感性等痛点。它已然是当代构筑千万级 MLOps 机器学习平台不可或缺的底层基础设施。
面对 AIGC 以及万亿参数大模型带来的海量算力挑战,Volcano 也在不断演进:
- 多集群调度与联邦计算 :在混合云场景下跨地域(Region)跨算力中心实现弹性的 Job 调度与迁移。
- 容错与弹性伸缩 (Elastic Training) :面对 GPU 日常的掉线和硬件故障,结合 PyTorch TorchElastic 提供更加平滑的重调度体验,在任务中途进行扩缩容而不用从头进行 Checkpoint 重现。
- GPU 切分感知 :支持对细粒度 GPU 资源(如 Nvidia MIG、vGPU 算力隔离)的更精准感知调度。
在拥抱智能时代的浪潮中,谁能构建出更高效的算力调度流转体系,谁就能在 AI 竞赛中建立护城河。而 Volcano,正是赋能这一体系运转的核心引擎。





