第 10 篇|源码阅读报告:从 RayJob 到用户程序

本文最后更新于:7 分钟后

前言

从第零篇走到第九篇,我们先拆开 KubeRay 的资源模型、协调、并发、恢复和高可用,再进入 Volcano 的 Gang、Queue 与接入流程。这一篇不再展开新的机制,而是把这些结论放回同一次 RayJob 执行:每个阶段由谁推进,状态保存在哪里,某一层完成以后还要等待什么。

这份报告沿用全系列的固定源码基线:KubeRay 6bf05eb17a3e、Ray 3f785c0711b9、Volcano d8984501e4ad。文中的容量、故障和时序仍是源码推演,没有加入集群压测或故障注入结果。

从 RayJob 到用户程序

在本文的 Kubernetes 场景中,使用者先提交 RayJob。KubeRay 根据 RayJob 创建专用 RayCluster,再由 RayCluster Controller 创建 head、worker Pod 和 Service。集群进入 Ready 后,KubeRay 创建 submitter Kubernetes Job;submitter 连接 Ray Dashboard 的 Jobs API,把入口命令交给 Ray。程序从这一步开始进入 Ray 自己的任务、Actor 和资源调度体系。

启用 Volcano 时,这条主干增加了两组调度信息:创建 RayCluster 之前先写入 PodGroup;创建 Pod 时再写入 schedulerName、PodGroup 名和成员角色。Volcano 接管的是 Pod 获得节点的过程。它不会替 KubeRay 判断 RayCluster 是否 Ready,也不会替 Ray 执行用户程序。

一次 RayJob 从声明、集群准备、Pod 调度到 Ray 程序运行的完整路径。Volcano 是 Pod 调度阶段的可选接入,RayCluster Ready 之后才会创建 submitter。
一次 RayJob 从声明、集群准备、Pod 调度到 Ray 程序运行的完整路径。Volcano 是 Pod 调度阶段的可选接入,RayCluster Ready 之后才会创建 submitter。 查看原图

把流程按阶段展开,可以得到下面的分工:

阶段 主要组件 关键对象或状态 向前推进的条件
声明作业 使用者、Kubernetes API RayJob spec 对象写入 API
准备调度需求 KubeRay PodGroup,可选 创建或更新请求成功
创建集群 RayJob Controller RayCluster 专用集群存在
创建成员 RayCluster Controller Service、head 和 worker Pod 期望资源逐步建立
分配节点 默认调度器或 Volcano Pod、PodGroup、Node Pod 满足调度条件并完成绑定
判断集群可用 KubeRay、Ray RayCluster status、Ray 服务状态 Controller 观察到就绪条件
提交程序 submitter、Ray Jobs API Kubernetes Job、Ray 作业标识 submitter 能连接 Dashboard 并完成提交
执行与收尾 Ray、KubeRay、Kubernetes Ray 作业状态、RayJob status、删除策略 程序结束,并按策略保留或删除集群

这张表也解释了系列中几个名字相近的对象:RayJob 管理一次 Kubernetes 上的提交生命周期;submitter Kubernetes Job 运行提交程序;Ray Jobs API 接收真正交给 Ray 的作业;Volcano 的 JobInfo 则是 Scheduler 对一组 Pod 的内存表示。

三个控制循环怎样协作

完整流程中有三套节奏不同的循环。

KubeRay Controller 观察 RayJob、RayCluster 和 Pod。事件进入工作队列后,Reconcile 重新读取当前对象,比较期望状态与实际状态,再创建资源或写回 status。它处理的是 Kubernetes 资源怎样逐步收敛。

Volcano Scheduler 周期性建立 Session,把缓存中的 Node、Queue、JobInfo 和 TaskInfo 组织成一次调度快照。enqueue、allocate、backfill,以及按需启用的 preempt、reclaim,在 Session 中计算候选结果并提交绑定。它处理的是一组 Pod 能否获得节点,以及多个作业怎样竞争资源。

Ray 的运行时循环发生在集群内部。head、worker 建立连接后,Ray 根据可用资源执行 task 和 actor;Ray Jobs API 管理用户程序的提交与状态。它处理的是计算任务怎样运行。

KubeRay 协调循环、Volcano 调度循环和 Ray 运行时通过 Kubernetes 对象及 Ray API 依次衔接。三套循环各自读取状态并推进自己负责的阶段。
KubeRay 协调循环、Volcano 调度循环和 Ray 运行时通过 Kubernetes 对象及 Ray API 依次衔接。三套循环各自读取状态并推进自己负责的阶段。 查看原图

三套循环没有共享一份进程内状态。KubeRay 与 Volcano 通过 Kubernetes 对象交接信息:PodGroup 表达最低成员和资源需求,Pod 上的字段表达调度器选择与组关系,RayCluster status 表达 KubeRay 观察到的集群状态。submitter 再通过 Ray Jobs API 跨入 Ray 的运行时边界。

这种协作方式允许各组件独立重试。某个 watch 事件重复到达,工作队列可以合并同一个对象键;某个事件没有对应一次独立执行,后续 Reconcile 仍会读取对象的最新状态。Scheduler 也根据当前 Session 重新计算候选,不依赖逐条重放此前发生的资源变化。

状态、并发与恢复

源码中最容易混在一起的是事件、状态和正在执行的工作。它们承担不同职责:

  • Kubernetes API 中的 spec、status、owner reference 和对象是否存在,是 Controller 重启后仍可读取的事实。
  • informer cache、工作队列、expectations 和正在运行的协程属于进程内状态,用于提高效率、限制并发或吸收缓存观察延迟。
  • Ray 的 GCS、对象存储和运行中进程保存计算侧状态;它们的恢复条件与 Operator 是否重新选主不同。
持久化对象、Controller 进程内状态和 Ray 运行时状态的边界。组件重启后能否继续,取决于推进流程所需的信息是否仍可从对应状态源重新取得。
持久化对象、Controller 进程内状态和 Ray 运行时状态的边界。组件重启后能否继续,取决于推进流程所需的信息是否仍可从对应状态源重新取得。 查看原图

由此可以归纳出几条贯穿前文的结论。

watch 提醒状态变化,Reconcile 决定现在该做什么

Controller 不需要让每个事件都对应一次完整处理。同一个对象键在队列中可以合并;处理开始以后再次发生变化,键还可以重新进入队列。Reconcile 读取的是执行当时的对象状态,因此它关心当前是否还需要创建、更新或删除资源。

管理并发与计算并发位于不同层次

多个 worker 允许 KubeRay 同时协调不同 RayCluster;同一个对象键仍要避免并行修改。expectations 用于等待已发出的创建或删除被缓存观察到,减少重复操作。Ray 能同时执行多少任务,则由集群资源和 Ray 调度决定,不受 Controller worker 数直接控制。

恢复依赖可重建的状态链

Operator 重启后会重新 list/watch Kubernetes 对象,再从 spec、status、label、owner reference 和实际子资源恢复协调。进程内队列和锁可以丢失,因为它们能从对象状态重新建立。若推进下一步所需的信息只存在于已丢失的进程内状态,恢复链就会中断;这也是阅读恢复逻辑时需要持续核对的问题。

高可用要按层拆开

Leader Election 解决多个 Operator 副本中谁写控制面;Kubernetes 控制面是否可用、Ray head 和 GCS 是否恢复、RayService 是否能切换服务集群,是另外几层条件。某一层恢复,不代表正在执行的计算或外部请求已经恢复。

调度结论放回完整系统

Volcano 的几个调度概念也可以放回同一条路径理解:

  • PodGroup 把多个 Pod 表达为一组。minResources 在 enqueue 阶段参与资源准入,minMember 则参与 Gang 的提交判断。
  • Gang 判断发生在试分配过程中。条件不满足时可以撤销本轮 Statement 中的尝试,避免只绑定一部分成员。
  • Queue 组织多组作业的资源竞争。weight、capability、request、allocated 和 deserved 共同影响 Queue 还能否继续使用资源。
  • DRF 等策略在相应层次决定检查顺序;节点 predicates 最终仍要确认某个具体 Node 能否接纳 Pod。
  • preempt 和 reclaim 选出需要释放的成员后,资源还要等待 Pod 实际退出,才能重新用于绑定。

这些机制提供的是 Pod 调度条件。PodGroup 满足最低成员数,不等于所有可能扩出的 worker 都已运行;Queue 得到份额,也不等于某组节点被永久划给它;RayCluster Ready 更不等于用户程序已经成功完成。每一项状态只回答它所在层次的问题。

这份源码报告能够支持哪些判断

基于固定版本源码,可以确认下面这些行为关系:

可以从源码确认 仍需部署或实验确认
Controller 的 watch、队列、Reconcile 和重试路径 特定集群规模下的协调延迟与吞吐
RayJob、RayCluster、PodGroup 和成员 Pod 的创建顺序 安装版本、CRD、Webhook 与 Kubernetes 版本的实际兼容性
Leader Election、状态写回和对象重建所依赖的信息 控制面故障时的真实恢复时间
Gang、Queue 份额、抢占与回收的源码条件 具体工作负载下的排队时间和资源利用率
KubeRay 与 Volcano 通过哪些字段协作 Admission 注入、Pod overhead 等环境差异造成的最终资源请求
submitter 何时创建,以及它怎样进入同一个 PodGroup 用户代码、依赖包、存储和结果提交的可靠性

这也是全系列一直保留源码版本和条件分支的原因。源码能够说明控制路径与判断条件;时间、容量和稳定性数据仍需要在目标环境中测量。

怎样继续使用这套阅读方法

这次阅读没有从文件列表开始,而是始终沿一个问题寻找下一次状态交接:谁观察对象,谁把键放进队列,谁重新读取状态,谁创建下级资源,谁写回结果。遇到并发和失败分支时,再分别检查持久化状态、进程内状态和外部组件状态。

全系列的阅读路线:先建立对象与生命周期,再进入协调、并发和恢复,随后阅读 Volcano 的调度与资源竞争,最后回到接入流程和系统边界。
全系列的阅读路线:先建立对象与生命周期,再进入协调、并发和恢复,随后阅读 Volcano 的调度与资源竞争,最后回到接入流程和系统边界。 查看原图

把这种方法用于其他 Controller 或调度器时,可以保持下面的顺序:

  1. 从用户提交的顶层对象开始,画出它创建或引用的下级对象。
  2. 找到 watch 注册、队列键和 Reconcile 入口,确认事件怎样变成一次状态检查。
  3. 沿每次 API 写入继续追踪下一位观察者,直到请求进入真正执行工作的组件。
  4. 将 status、缓存、锁、队列和外部存储分开,判断重启后哪些信息仍然存在。
  5. 对版本条件和失败分支单独记录,不用当前版本的实现替代其他版本行为。

对于部署选择,也可以沿组件边界判断。Ray 可以独立运行;希望用 Kubernetes 管理 Ray 集群生命周期时再引入 KubeRay;需要最低成员共同启动、Queue 份额或批作业资源竞争时,再为相应工作负载接入 Volcano。每增加一层能力,也会增加一组需要维护的对象、状态和版本关系。

小结:沿状态交接理解整个系统

从 RayJob 到用户程序,系统没有一条贯穿所有组件的同步调用链。KubeRay、Kubernetes 调度器或 Volcano、Ray 依次观察属于自己的状态,并把结果写到下一层能够读取的位置。watch、队列和重试让协调可以重复发生;Kubernetes 对象让控制过程能够在重启后重新建立;PodGroup 和 Queue 为多个 Pod、多个作业补充调度约束;Ray 在资源就绪后接管计算。

读源码时抓住对象、状态和控制循环,就能判断一项能力由哪一层提供,也能看出某个状态成立之后,系统还需要经过哪些阶段。到这里,这组源码阅读形成了一条完整路径:从架构分工出发,经过协调、并发、恢复和调度,最后回到一次用户程序如何真正运行。

参考资料


第 10 篇|源码阅读报告:从 RayJob 到用户程序
https://tanxinyu.work/kuberay-volcano-10-source-reading-report/
作者
谭新宇
发布于
2026年9月15日
许可协议