Skip to content

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 低不代表负载低),推荐:

  1. 基于并发请求数/排队长度:vLLM 暴露 Prometheus 指标(如 vllm:num_requests_running、vllm:num_requests_waiting),用 KEDA 或 HPA 自定义指标按在途请求伸缩;
  2. 下限保护:minReplicas 至少等于模型能扛住 QPS 的最低副本数;maxReplicas 受集群 GPU 总量约束,避免伸缩打满全集群;
  3. 扩容快、缩容慢:模型加载慢,缩容冷却时间(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/sP99 突增、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 视角下的异构资源管理。

AI-HPC 智算超算专家 · Contact: openaihpc@gmail.com