













前五篇讨论了 KubeRay 怎样把一个 RayJob 变成运行中的 Ray 集群。这里还留着一个问题:Operator 已经创建了 Pod,集群却没有足够的空闲资源,几套 Ray 集群该怎样分配这些资源?
第三篇里的协调并发数决定多少个 RayCluster 可以同时被检查。调大这个数字,Operator 可以更快地创建 Pod,却不会增加机器上的 CPU、内存和 GPU。接下来四篇沿着这条边界,阅读负责批调度的 Volcano:这一篇先看对象、进程和协作关系,第七篇跟踪一次成组调度,第八篇分析资源竞争,第九篇回到 KubeRay 的接入实现。
源码基线为 Volcano d8984501e4ad,KubeRay 仍为 6bf05eb17a3e。下面的资源数量用于源码推演,没有进行集群实验。主案例继续使用 default/demo-job,一个 head Pod、compute 组的两个 worker Pod,使用 K8sJobMode,关闭自动扩缩容。相对前五篇,这里新增的条件是启用 Volcano。
假设有两个独立提交的 RayJob:demo-job 和 demo-job-b。为了只看调度问题,先把每个作业所需的三个 Ray Pod 简化为三个等大的资源槽,并假定业务需要三个成员都到齐才能有效计算。submitter 稍后才创建,其资源要求在第九篇单独计算。
机器一共能放下四个这样的 Pod。如果两个作业各拿到两个槽,资源已经用满,双方却都还在等第三个成员。先让一组拿到三个槽,就有机会完成一次计算,释放资源后再安排另一组。
这里先考虑未配置成组策略、逐个 Pod 寻找节点的调度路径。调度器不会从 Ray 程序里自动推导出这三个 Pod 必须配合。Volcano 用 PodGroup 表达这种关系,再由 Gang 插件检查一组成员的最低运行要求。Gang 在这里指成组调度:尝试分配资源时,要检查作业整体是否满足门槛。本文分析这一种实现,不据此判断其他版本或扩展调度器是否提供类似能力。
这个前提不能反过来理解为“所有 Ray 程序都要等全部 worker”。Ray task 可以按可用资源逐步执行,部分应用也允许弹性成员数。是否需要成组调度,要看应用对最低资源量和成员数量的要求。配置过高的门槛会延长等待,甚至让原本可以逐步运行的程序始终无法启动。
此外,四个槽只是便于推演。实际资源要同时记录 CPU、内存、GPU 等多个数量,下文称为资源向量:某台节点空闲 CPU 很多,也可能没有 GPU;集群总量够,也可能被分散在几台放不下单个 Pod 的节点上。组的需求和单 Pod 的放置条件都需要检查。
读 Volcano 时经常遇到 Job。这个名字至少出现在三个不同的位置,先把它们放回各自的 API 里。表中的 Volcano task 指一类 Pod 的模板和副本要求;进入调度器后,一个 TaskInfo 通常对应一个具体 Pod。它与 Ray 中的一次函数调用不是同一层对象。
Volcano Job 是一条原生提交入口。若工作负载已经由 KubeRay 这样的 Operator 管理,可以继续使用原有资源,通过 PodGroup 把调度要求交给 Volcano。
PodGroup 是命名空间级资源,主要描述哪些 Pod 作为一组参与调度,以及组的最低要求。它不包含一套用来创建 Ray head、worker 的模板。下面是 scheduling/v1beta1/types.go 中几个字段的节选,省略了注释和其他字段:
1 | |
minMember 表达最低成员数,minResources 表达最低资源量。queue 指定这组 Pod 属于哪个 Queue,priorityClassName 引用 Kubernetes PriorityClass。PriorityClass 是把一个名称映射为数值优先级的集群级对象,供调度器比较先后次序。字段怎样参与判断取决于启用的插件,第七、八篇会沿调用位置展开。
Queue 是集群级资源,用来组织共享资源的工作负载。与按 namespace 区分的 PodGroup 不同,Queue 在整个 Kubernetes 集群里按名称识别,一个队列可以被多个命名空间中的 PodGroup 引用。它有权重、资源上限、保障资源和状态等信息;资源策略插件读取这些配置,决定哪些作业可以进入调度、哪个队列应先获得资源。
后文会检查 Queue 是否处于 Open,这是允许接纳新作业的开放状态。它说明队列可接受工作,至于资源是否足够,还要继续计算。
主案例的 Queue 命名为 ray-batch,PodGroup 命名为 ray-demo-job-pg。Pod 通过 scheduling.k8s.io/group-name annotation 指向组。这个关联与 owner reference 是两回事:前者供调度器归组,后者表达 Kubernetes 对象的拥有关系,影响事件映射和垃圾回收。
Volcano Scheduler 内部还有一个 JobInfo。它是调度器组织 PodGroup 和成员信息的内存模型,不能看到变量名 job 就认定来源一定是 Volcano Job。KubeRay 创建的 PodGroup 同样可以形成这里的 JobInfo。
把基本部署拆开看,有三个主要的常驻服务角色:Scheduler、Controller Manager 和 Admission Webhook。Webhook 是供 API Server 调用的 HTTP 回调服务;这里的 Admission 指资源写入前的准入处理,可以校验请求或补全字段。这三个角色可以分别部署,也可以有多个副本。这里数的是职责与进程角色,实际 Pod 数量由安装配置决定。
客户端工具用于提交、查询和管理资源,不是每次作业都需要附带运行的常驻服务。当前源码还有拓扑、分片等扩展组件,这四篇先分析基本调度路径,不把所有 cmd 目录都算成必装进程。
Controller Manager 内部的多个控制器也不对应多个独立进程。cmd/controller-manager/app/server.go 的 startControllers 遍历已注册且启用的控制器,初始化后启动各自的运行循环。关键动作是:
1 | |
它们可以在同一进程里共享客户端和 informer。informer 持续从 API Server 接收资源变化,维护本地缓存,再通知感兴趣的控制器。这与前面读 KubeRay 时看到的观察、排队和协调思路相通,不过具体框架和实现需要分别阅读。
Scheduler 的 Action 和 Plugin 则运行在 Scheduler 进程内。Action 组织调度阶段,Plugin 注册排序、过滤、资源准入和就绪判断等函数。它们通常通过 Go 函数调用协作,不是每个插件起一个远程服务。
这里还要区分两种“准入”。Admission Webhook 检查这次 API 请求能否接受;Scheduler 的 enqueue 阶段检查一组工作负载是否可以进入调度。RayJob 被 API Server 保存,只说明声明已经写入,后面仍可能因为资源不足而等待。
先看 Volcano Job 原生路径。用户提交 Job 后,Volcano Job Controller 维护对应的 PodGroup。源码 job_controller_actions.go 会观察 PodGroup 的 phase;在这个版本中,组仍处于空状态或 Pending 时,不进入正常的 task 同步创建流程。Scheduler 通过 enqueue 推进准入,Controller 再据此继续创建 Pod。
这个先准入、后创建成员的过程,可以减少原生作业在资源不足时生成大量等待中的 Pod。也说明两端靠资源状态协作:Controller 写出需求,Scheduler 更新调度状态,Controller 从后续观察中得知下一步可以继续。
KubeRay 的路径有所不同。RayJob Controller 创建 PodGroup 和 RayCluster,RayCluster Controller 随后维护 head、worker Pod。它不会复用 Volcano Job Controller 的那套“等待 PodGroup 准入再创建 task”的流程。因此,接入 Volcano 后仍可能看到已经创建、尚未调度的 Ray Pod。
Pod 出现后,Scheduler 根据 schedulerName 处理属于自己的待调度 Pod,再通过 annotation 找到 PodGroup。Queue 策略和 Gang 约束参与资源分配;选好节点后,通过 Kubernetes API 把 Pod 与该 Node 的关系确定下来,这一步称为绑定(Bind)。绑定完成只说明确定了运行位置,节点上的 kubelet 还要观察结果、调用容器运行时启动容器。
KubeRay 继续观察集群是否就绪。等到条件满足,RayJob Controller 创建 submitter 的 Kubernetes Job,后者生成 submitter Pod。它也要经过调度、启动,才有进程调用 Ray Jobs API 提交程序。Volcano 在这里安排 Pod;用户函数和 actor 在 Ray 集群内部怎样执行,仍由 Ray 负责。
现在可以给几个容易混用的词划出范围。
如果 demo-job 的 Pod 一直没有运行,可以先检查对象在哪一步停下:PodGroup 是否创建,成员 Pod 是否存在,Queue 是否允许进入调度,Pod 是否已经绑定 Node。绑定后还停在启动阶段,就应继续查镜像、存储、网络和容器,而不能只调队列权重。
下一篇从 Scheduler 的 runOnce 开始,跟踪 ray-demo-job-pg 怎样在一次 Session 里试分配,以及为什么 Gang 判断通过以后仍然需要处理绑定失败。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。