从 Ray 到 Kubernetes:扩 Pod 的完整链路(逐跳拆解)

8 阅读3分钟

从 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

它做三件事:

  1. 扫描:

    pending tasks / actors
    
  2. 汇总资源需求:

    pending GPU = 2
    
  3. 计算所需 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 才正式参与到调度与资源分配中。