从 Ray 到 Kubernetes:扩 Pod 的完整链路(逐跳拆解)
目标:回答清楚
Ray 是“怎么把 PENDING 变成 K8s Pod 创建动作的”
一、起点:Ray Head 里发生了什么?
1️⃣ Task 进入 PENDING(这是唯一触发条件)
@ray.remote(num_gpus=1)
def task(): ...
当前状态:
Worker GPU = 1
Task GPU 需求 = 3
Ray Scheduler 结果:
Task-1 → RUNNING
Task-2 → PENDING
Task-3 → PENDING
⚠️ 到这里为止,Kubernetes 还完全没参与。
二、Ray Autoscaler:真正的“桥”
2️⃣ Autoscaler Loop 周期运行(Head Pod 内)
在 Ray Head Pod 的 container 里,有一个后台循环:
monitor.py / autoscaler loop
它做三件事:
-
扫描:
pending tasks / actors -
汇总资源需求:
pending GPU = 2 -
计算所需 Worker 数:
workers_to_add = ceil(pending_gpu / gpu_per_worker)
到这里为止,仍然在 Ray 内部。
三、关键分叉:Ray 怎么“碰到” Kubernetes?
这里是你最模糊、也是最重要的地方。
四、方式一(99% 场景):KubeRay
如果你用的是 Ray on K8s 的正规姿势,答案就在这里。
3️⃣ Autoscaler 不碰 Pod,只改 CR
Ray Autoscaler 不会直接 create Pod,而是:
PATCH RayCluster CR
例如:
spec:
workerGroupSpecs:
- groupName: gpu-workers
replicas: 1 → 3
这一步是:
Ray Head Pod
↓ (K8s API)
修改 RayCluster 对象
在 KubeRay 架构下,Ray Head Pod 内部的 Autoscaler 并不直接操作 Pod,而是通过 Kubernetes API 修改 RayCluster 这一自定义资源以表达扩缩容意图;RayCluster 作为 Kubernetes 侧的声明式对象,由 KubeRay Operator 监听并负责将其期望状态转化为具体的 Ray Worker Pod 的创建与回收,从而形成 Ray 与 Kubernetes 之间清晰的控制边界。
KubeRay Operator 作为 Kubernetes 原生控制器,具备直接创建和回收 Pod 的权限,但其行为严格受 RayCluster 等自定义资源的期望状态约束;Ray Head Pod 通过修改 RayCluster 来表达扩缩容意图,而不直接操作 Pod,从而在 Ray 的计算调度与 Kubernetes 的资源调度之间形成清晰、稳定的控制边界。
4️⃣ KubeRay Operator 接手(真正碰 Pod 的地方)
KubeRay Operator 是一个 独立运行的 Controller:
watch RayCluster CR
当它发现:
replicas 增加
它会:
create Ray Worker Pod × 2
⚠️ 这是 Kubernetes 世界第一次“知道”要扩容
五、Kubernetes 终于开始干活了
5️⃣ K8s Scheduler 调度新 Pod
Ray Worker Pod
resources:
hami.io/gpu: 1
K8s Scheduler:
- 找到满足 GPU 条件的 Node
- 把 Pod 调度过去
6️⃣ kubelet + HAMi(节点侧)
Pod → Node
kubelet → HAMi Device Plugin
Allocate GPU unit
GPU 算力真正被绑定。
六、最后一步:Ray Worker “回到” Ray
7️⃣ Worker Pod 启动后主动注册
在 Worker Pod 的 container 启动时:
ray start --address=<head>
它会:
向 Ray Head 注册:
CPU = 4
GPU = 1
Worker Node A 中的 Raylet 通过 Kubernetes Service 与位于 Node B 的 Ray Head Pod 建立 gRPC 连接,并向其中运行的 GCS Server 注册本节点的资源信息。
Ray Head 更新资源表:
available GPU += 1
8️⃣ Pending Task 被立刻调度
Task-2 → RUNNING
Task-3 → RUNNING
至此,一个闭环完成。
七、用一张“因果链”压缩记忆
Task PENDING
↓
Ray Autoscaler 计算缺口
↓
修改 RayCluster.replicas
↓
KubeRay Operator 创建 Pod
↓
K8s Scheduler 调度 Pod
↓
HAMi 绑定 GPU
↓
Worker 注册 Ray
↓
Pending Task 解除
当 Ray 内部出现因资源不足而处于 Pending 状态的 Task 或 Actor 时,Ray Autoscaler 会在 Head 节点周期性地汇总这些未满足的资源需求,并通过修改 RayCluster 等自定义资源的副本数,向 Kubernetes 表达“需要更多 Worker”的意图;随后由 KubeRay Operator 将这一意图转化为具体的 Pod 创建行为,Kubernetes 才正式参与到调度与资源分配中。