Kubernetes 推理服务生产化:vLLM 从 Deployment 到高可用
实验目标
在 GPU 集群里用 Kubernetes 跑 vLLM 推理服务,"kubectl apply 一个 Deployment"只是起点。生产化要回答的问题在编排侧:
- 如何设计 Deployment 让 GPU 资源被正确分配与隔离?
- 服务发布/升级时如何做到不中断、不重复打满显存?
- 负载波动时如何弹性伸缩,而不是把 GPU 卡死?
- 服务出问题时,监控与告警如何第一时间定位到"模型服务还是平台层"?
本篇从 Kubernetes 编排视角逐段拆解 vLLM 推理服务的生产化落地,覆盖 Deployment 设计、GPU 配额、滚动更新与优雅退出、HPA 与监控。说明:本篇为综述 + 架构解析路线,代码片段为通用实践示例,无第一手实验数据;具体版本与参数以官方文档为准。
Deployment 设计:把 GPU 资源正确地"钉"住
1. GPU 资源声明
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-serve
spec:
replicas: 2
selector:
matchLabels: { app: vllm-serve }
template:
metadata:
labels: { app: vllm-serve }
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
command: ["python", "-m", "vllm.entrypoints.openai.api_server"]
args:
- --model
- /models/Qwen2.5-72B-Instruct
- --max-model-len
- "32768"
- --gpu-memory-utilization
- "0.92"
- --port
- "8000"
ports:
- containerPort: 8000
resources:
requests:
nvidia.com/gpu: "1" # 与 Device Plugin 的资源名一致
cpu: "8"
memory: "64Gi"
limits:
nvidia.com/gpu: "1"
cpu: "8"
memory: "64Gi"要点:
- GPU 资源名(
nvidia.com/gpu)由 Device Plugin 注册,不同插件(NVIDIA/昇腾/自研)资源名不同,混布集群要区分; - requests 与 limits 必须一致(GPU 不可压缩,只声明 requests 会留下超卖漏洞);
--gpu-memory-utilization 0.92预留显存给 KV cache 与模型加载,不要设 1.0(加载与碎片风险)。
2. 调度约束与亲和性
yaml
spec:
template:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu-type
operator: In
values: ["h20", "a100"]
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule- nodeSelector/affinity 按 GPU 型号分流:不同型号混布时,用节点标签把服务钉到正确机型,避免"调度到无卡/错卡节点";
- topologySpreadConstraints 打散副本:多副本尽量分布在不同节点,单节点故障不影响全部副本;
- 多卡服务(张量并行)需保证副本内多卡在同一节点——用
requiredDuringScheduling的节点级约束,或配合自定义调度器。
3. 模型加载与持久化
- 模型权重用 PVC 或只读卷挂载,避免镜像体积爆炸与重复下载;
- 多副本共享同一权重时,用 ReadOnlyMany 卷(对象存储/NFS 兼容 CSI)或镜像内置;
- 首次冷启动加载 70B 模型耗时长,用 StartupProbe 放宽就绪时间,而不是让 ReadinessProbe 反复失败误报。
滚动更新与优雅退出:发布不打断推理
vLLM 在线服务发布新模型/新参数时,粗暴 kubectl rollout restart 会造成"新 Pod 还在加载模型、旧 Pod 已被终止"的空窗。
优雅退出配置
yaml
spec:
template:
spec:
terminationGracePeriodSeconds: 300 # 给足模型卸载/请求排空时间
containers:
- name: vllm
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"] # 等流量摘除再退出- terminationGracePeriodSeconds 给足:vLLM 停止时要排空 in-flight 请求并卸载显存,30 秒默认值不够;
- preStop sleep:让 Service 端点先摘除(EndpointSlice 更新存在延迟),再触发进程退出;
- ReadinessProbe 配合:新 Pod 加载完成(模型就绪)才接流量,旧 Pod 在健康检查摘除后才终止。
滚动策略
yaml
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0 # 保证服务容量不降maxUnavailable: 0 保证发布期间始终有可用副本(代价是峰值时多占一台 GPU,小集群需权衡);maxSurge 控制新副本并行数,避免瞬时把集群显存打满。
弹性伸缩:HPA 的正确打开方式
GPU 服务不适合用 CPU 指标做 HPA(CPU 低不代表负载低),推荐:
- 基于并发请求数/排队长度:vLLM 暴露 Prometheus 指标(如
vllm:num_requests_running、vllm:num_requests_waiting),用 KEDA 或 HPA 自定义指标按在途请求伸缩; - 下限保护:
minReplicas至少等于模型能扛住 QPS 的最低副本数;maxReplicas受集群 GPU 总量约束,避免伸缩打满全集群; - 扩容快、缩容慢:模型加载慢,缩容冷却时间(cooldown)要大于模型加载时长,防止抖振。
yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: vllm-hpa
spec:
scaleTargetRef: { apiVersion: apps/v1, kind: Deployment, name: vllm-serve }
minReplicas: 2
maxReplicas: 8
behavior:
scaleDown:
stabilizationWindowSeconds: 600 # 缩容冷却
metrics:
- type: Object
object:
metric: { name: vllm_num_requests_waiting }
describedObject: { apiVersion: v1, kind: Service, name: vllm-serve }
target: { type: AverageValue, averageValue: "10" }监控与告警:分层定位
推理服务故障要能快速区分"模型层 vs 平台层":
| 层 | 指标示例 | 告警示例 |
|---|---|---|
| 模型服务层 | vllm:num_requests_running/waiting、P99 latency、吞吐 tokens/s | P99 突增、waiting 队列打满、OOM |
| GPU 层 | DCGM 指标(GPU 利用率、显存、温度、NVLink 带宽、ECC 错误) | 显存持续 100%、温度超阈、NVLink 降级、ECC 增长 |
| 平台层 | Pod 状态、重启次数、Endpoint 数量、节点 GPU 可分配数 | 反复 CrashLoop、GPU 资源不足(Unschedulable) |
工程观察:
- GPU 利用率与"服务健康"不是一回事:利用率 0% 可能是空闲,也可能是卡死(请求堆积但无计算)。告警必须同时盯利用率与请求队列。
- 显存是硬约束:推理服务 OOM 通常表现为
CUDA out of memory崩溃重启。配置--gpu-memory-utilization与 HPA 上限联动,避免副本数增长把显存打爆。 - 节点 GPU 可分配数监控:集群 GPU 资源碎片化(每节点剩 1 卡但无法组成多卡服务)是推理集群常见隐性瓶颈,用节点可分配 GPU 数与调度失败事件联合告警。
生产化检查清单
- [ ] GPU 资源 requests=limits、资源名与 Device Plugin 一致
- [ ] 多副本按机型亲和 + 节点打散,多卡服务卡在同节点
- [ ] 模型权重用只读卷,StartupProbe 覆盖冷启动时长
- [ ] terminationGracePeriodSeconds ≥ 300,preStop 摘流量
- [ ] 滚动更新 maxUnavailable=0,发布演练验证过
- [ ] HPA 基于请求指标 + 缩容冷却,上限受 GPU 总量约束
- [ ] 监控覆盖模型层/GPU 层/平台层,告警能区分故障来源
工程启示
- 推理生产化 = 编排细节的集合:Deployment 声明、滚动策略、优雅退出、弹性伸缩、监控分层,任何一环缺失都会在上线后暴露为事故。
- 先写清"不可变事实":GPU 资源、机型亲和、显存上限这些在部署清单里写死,业务迭代时才不会悄悄破坏平台约束。
- 发布即演练:每次模型升级走同一套滚动更新流程,把"发布不中断"练成肌肉记忆。
下一篇预告:推理服务在 K8s 上稳定运行后,集群规模继续变大——多厂商加速卡(NVIDIA/昇腾/寒武纪)开始混布。下一篇《异构加速卡统一资源管理:多厂商 GPU/NPU 混布调度》拆解 Slurm 视角下的异构资源管理。