KubeRay + vLLM:大模型推理的 K8s 分布式方案
KubeRay 架构全景解析:RayCluster/RayJob/RayService 三层 CRD、vLLM + Ray Serve 多节点 TP 推理、PD 分离编排,以及 9 个 K8s LLM 推理项目的横向对比与选型建议。
一、为什么需要 KubeRay
1.1 原生 K8s Deployment 的局限
你已经知道 vLLM 可以用标准 Deployment 跑在 K8s 上。但多节点 TP(Tensor Parallelism)时,原生 Deployment 有一个致命的短板:
原生 Deployment 启动 4 个 Pod 做 TP=4:
Pod-0: vllm serve --tensor-parallel-size 4 # 我是 rank 0,rank 1/2/3 在哪?
Pod-1: vllm serve --tensor-parallel-size 4 # 我是 rank 1,rank 0 在哪?
Pod-2: vllm serve --tensor-parallel-size 4 # 我是 rank 2,谁告诉我地址?
Pod-3: vllm serve --tensor-parallel-size 4 # 我是 rank 3,同上
问题:4 个 Pod 互不知道对方的 IP。
需要手动注入 rank 地址表,或依赖 NCCL 环境变量。
K8s StatefulSet 可以给固定 hostname,但 NCCL 初始化仍然脆弱。
1.2 Ray 的解法
Ray 从第一天就是为分布式计算设计的。它的核心能力正好解决这个问题:
| 问题 | 原生 K8s | Ray |
|---|---|---|
| 多节点 rank 发现 | 手动或 hack | Ray 内置(GCS 全局调度) |
| GPU 分配感知 | 只知道数量,不知道拓扑 | Ray 了解 GPU 拓扑(NVLink/PCIe) |
| 进程生命周期管理 | Pod = 进程 | Ray Actor = 进程(更灵活) |
| 失败重试 | Pod 重启 | Actor 重启(可保留 GPU 分配) |
| 多服务编排 | 手写多个 Deployment | Ray Serve 原生支持 |
1.3 KubeRay 在栈中的位置
┌─────────────────────────────────────┐
│ 你的推理请求 │
├─────────────────────────────────────┤
│ vLLM (推理引擎) │
├─────────────────────────────────────┤
│ Ray Serve (服务层:HTTP 路由/扩缩) │
├─────────────────────────────────────┤
│ Ray Core (分布式调度:rank 协调) │
├─────────────────────────────────────┤
│ KubeRay (K8s Operator: 管理 Pod) │
├─────────────────────────────────────┤
│ Kubernetes (容器编排) │
└─────────────────────────────────────┘
KubeRay 不是替代 vLLM,而是给 vLLM 提供 K8s 原生的分布式运行环境。
二、KubeRay 三层 CRD
KubeRay 在 K8s 中注册了三种自定义资源:
RayCluster → 定义一个 Ray 集群(Head + Workers)
RayJob → 一次性任务(跑完就停)
RayService → 长期运行的服务(Ray Serve + 滚动更新)
2.1 RayCluster — 基础
apiVersion: ray.io/v1
kind: RayCluster
metadata:
name: raycluster-vllm
spec:
rayVersion: '2.40.0'
headGroupSpec:
rayStartParams:
dashboard-host: '0.0.0.0'
template:
spec:
containers:
- name: ray-head
image: rayproject/ray:2.40.0
resources:
limits:
cpu: "4"
memory: "16Gi"
workerGroupSpecs:
- groupName: gpu-workers
replicas: 4
minReplicas: 4
maxReplicas: 8
rayStartParams: {}
template:
spec:
containers:
- name: ray-worker
image: rayproject/ray:2.40.0
resources:
limits:
nvidia.com/gpu: "1"
memory: "80Gi"
关键概念:
headGroupSpec:Head 节点,运行 GCS(全局调度器)和 DashboardworkerGroupSpecs:Worker 节点,实际执行推理的 GPU 节点minReplicas/maxReplicas:支持自动扩缩(根据 GPU 利用率)
2.2 RayJob — 一次性任务
apiVersion: ray.io/v1
kind: RayJob
metadata:
name: vllm-benchmark
spec:
entrypoint: python /scripts/benchmark.py
rayClusterSpec:
# ... 同 RayCluster 定义
submissionMode: HTTPMode
RayJob 创建一个临时的 RayCluster,执行完 entrypoint 后自动销毁。
2.3 RayService — 长期推理服务(你的场景)
apiVersion: ray.io/v1
kind: RayService
metadata:
name: vllm-serve
spec:
serveConfigV2: |
applications:
- name: vllm
import_path: vllm_serve:app
route_prefix: /
rayClusterConfig:
rayVersion: '2.40.0'
headGroupSpec:
# ... Head 配置
workerGroupSpecs:
- groupName: gpu-prefill
replicas: 4
template:
spec:
containers:
- name: ray-worker
image: rayproject/ray:2.40.0
resources:
limits:
nvidia.com/gpu: "8"
- groupName: gpu-decode
replicas: 2
template:
spec:
containers:
- name: ray-worker
image: rayproject/ray:2.40.0
resources:
limits:
nvidia.com/gpu: "8"
RayService 的核心价值:
- 自动滚动更新:修改
serveConfigV2→ RayService 自动做零停机更新 - 健康检查:Serve 应用挂了自动重启
- 多版本部署:可以做 A/B 测试(两个版本同时在线)
三、vLLM + Ray Serve 部署实战
3.1 最小示例
# vllm_serve.py
from ray import serve
from vllm import LLM, SamplingParams
@serve.deployment(
ray_actor_options={"num_gpus": 1},
autoscaling_config={
"min_replicas": 1,
"max_replicas": 4,
"target_num_ongoing_requests_per_replica": 10,
}
)
class VLLMDeployment:
def __init__(self):
self.llm = LLM(
model="Qwen/Qwen3-72B",
tensor_parallel_size=4, # 每个 replica 占 4 张 GPU
max_model_len=32768,
)
async def __call__(self, request):
prompt = await request.json()
result = self.llm.generate(
prompt["text"],
SamplingParams(temperature=0.7, max_tokens=256)
)
return result[0].outputs[0].text
app = VLLMDeployment.bind()
# 部署到 KubeRay
serve run vllm_serve:app
3.2 多节点 TP(4 卡跨 2 节点)
@serve.deployment(
ray_actor_options={"num_gpus": 4}, # 每个 replica 4 张 GPU
)
class VLLMTPDeployment:
def __init__(self):
self.llm = LLM(
model="Qwen/Qwen3-72B",
tensor_parallel_size=4, # TP=4
pipeline_parallel_size=1,
)
Ray 会自动找到有 4 张空闲 GPU 的节点,如果单节点不够 4 张,Ray 会跨节点分配并自动配置 NCCL 通信。
3.3 PD 分离在 KubeRay 上的实现
# prefill_serve.py
@serve.deployment(
ray_actor_options={"num_gpus": 8},
name="prefill"
)
class PrefillDeployment:
def __init__(self):
self.llm = LLM(
model="GLM-5.2-w8a8",
tensor_parallel_size=8,
enforce_eager=True,
# Prefill 模式
max_num_batched_tokens=4096,
)
async def prefill(self, prompt: str):
# 只做 Prefill,返回 KV Cache
...
# decode_serve.py
@serve.deployment(
ray_actor_options={"num_gpus": 4},
name="decode",
autoscaling_config={"min_replicas": 2, "max_replicas": 8}
)
class DecodeDeployment:
# 从 KV Cache 继续生成
...
# 组合
@serve.deployment
@serve.ingress(app)
class Gateway:
def __init__(self, prefill, decode):
self.prefill = prefill
self.decode = decode
async def __call__(self, request):
kv = await self.prefill.prefill.remote(prompt)
result = await self.decode.generate.remote(kv)
return result
app = Gateway.bind(
PrefillDeployment.bind(),
DecodeDeployment.bind()
)
注意:这个 PD 分离示例是 Ray Serve 层面的编排,不是 vLLM 原生的 PD 分离(
--disaggregation-mode)。vLLM 原生 PD 分离 + KubeRay 的组合仍在早期阶段。
四、KubeRay vs 原生 Deployment vs AIBrix
4.1 三方案对比
| 方案 | 多节点 TP | PD 分离 | GPU 自动扩缩 | Ascend | 社区 |
|---|---|---|---|---|---|
| 原生 K8s Deployment | ❌ 手动 | ✅ 手写 | ✅ HPA | ✅ | 通用 K8s |
| KubeRay | ✅ 自动 | ⚠️ 半自动 | ✅ Ray 原生 | ❌ | 2.6k ★ |
| AIBrix | ✅ | ❓ | ❓ | ❌ | 5k ★ |
| docker-compose | ❌ | ✅ | ❌ | ✅ | 你现在的 |
4.2 选型决策
你的场景:
单节点推理 (TP=1-8) → 原生 Deployment 足够
多节点 TP (TP>8) → KubeRay(NVIDIA)或 手写 StatefulSet(Ascend)
PD 分离 (Ascend) → 你现在的 docker-compose 方案是最成熟的
PD 分离 (NVIDIA) → KubeRay + Ray Serve 编排
追求 K8s 原生体验 → AIBrix(目前仅 NVIDIA)
五、KubeRay 在你的场景中的现实评估
5.1 能用上的
| 特性 | 你的场景 |
|---|---|
| RayService 服务管理 | 替代 docker-compose 的 restart: unless-stopped 和健康检查 |
| GPU 自动扩缩 | Decode 节点根据 QPS 自动扩缩(如果有 NVIDIA GPU) |
| 滚动更新 | 模型版本升级零停机 |
5.2 用不上的(Ascend 限制)
- Ascend NPU 的 Device Plugin 不如 NVIDIA GPU Operator 成熟
- Ray 的 GPU 感知调度在 Ascend 上未适配
- Mooncake RDMA 在 KubeRay 中没有现成支持
- 多节点 HCCL 初始化需要 hostNetwork 或手写 NCCL 环境变量
5.3 务实路线
阶段1(现在):继续 docker-compose + hostNetwork
→ Ascend NPU + PD 分离 + Mooncake 的最稳定方案
阶段2(学习):用 KubeRay 跑 NVIDIA GPU 的 vLLM(如果有多余 GPU)
→ 理解 KubeRay 的工作方式,积累经验
阶段3(未来):等 AIBrix 或华为 Ascend K8s Operator 成熟
→ 再考虑 Ascend 上的 K8s 化
六、KubeRay 部署速查
6.1 安装
# Helm 安装 KubeRay Operator
helm repo add kuberay https://ray-project.github.io/kuberay-helm/
helm install kuberay-operator kuberay/kuberay-operator
# 验证
kubectl get crd | grep ray
# rayclusters.ray.io
# rayjobs.ray.io
# rayservices.ray.io
6.2 部署 vLLM 推理服务
# 1. 写 RayService YAML(见 2.3 节)
# 2. 写 vLLM Serve 脚本(见 3.1 节)
# 3. 打包成 ConfigMap
kubectl create configmap vllm-serve-script --from-file=vllm_serve.py
# 4. 部署
kubectl apply -f rayservice-vllm.yaml
# 5. 查看状态
kubectl get rayservice vllm-serve
kubectl logs -l ray.io/cluster=raycluster-vllm
# 6. 端口转发测试
kubectl port-forward service/vllm-serve-serve-svc 8000:8000
curl http://localhost:8000/
6.3 监控
# Ray Dashboard
kubectl port-forward service/raycluster-vllm-head-svc 8265:8265
# 浏览器打开 http://localhost:8265 → 查看 GPU 利用率/任务队列/Serve 状态
# KubeRay 状态
kubectl describe rayservice vllm-serve
七、AIBrix 速览
AIBrix(vllm-project/aibrix)是 vLLM 官方推出的 K8s-native 方案:
AIBrix 组件:
Gateway → 统一推理入口(路由 + 限流 + 负载均衡)
Scheduler → GPU 感知调度(拓扑感知 + 碎片整理)
ModelMgr → 模型缓存管理(PVC + 预加载)
Router → PD 分离路由(Prefill/Decode 自动分发)
一句话:AIBrix 想做的是 “vLLM 的 K8s Operator”,但目前仅 NVIDIA。如果你以后有 NVIDIA GPU 集群,AIBrix 比 KubeRay 更轻量。
八、面试要点
Q: vLLM 怎么部署到 K8s?多节点 Tensor Parallelism 怎么解决?
单节点 TP ≤ 8:原生 Deployment + GPU limits + /dev/shm 扩容,足够。
多节点 TP > 8:需要分布式 rank 协调。
方案A(推荐):KubeRay + Ray Serve。
Ray 内置 GCS 全局调度,自动分配 GPU,自动 NCCL 初始化。
定义 RayService CRD → 写 @serve.deployment → serve run。
方案B:AIbrix(vLLM 官方 K8s Operator)。
更轻量,但社区还在早期(仅 NVIDIA)。
方案C:手写 StatefulSet + headless Service + NCCL 环境变量。
最原始但最可控,Ascend NPU 上用这个。
Q: KubeRay 的核心 CRD 是什么?
RayCluster → 定义一个 Ray 集群(Head + Workers)
RayJob → 一次性任务(跑完销毁,如 benchmark)
RayService → 长期推理服务(Ray Serve + 滚动更新 + A/B 测试)