跳到主要内容
推理优化

10.1 容器化与 Kubernetes 部署:把 vLLM 送上生产集群

vLLM 容器化(镜像、非 root、共享内存)、K8s GPU 调度(nvidia.com/gpu)、探针配置(/health 与 gRPC)、vLLM Production Stack Helm 部署

KubernetesDockerHelmGPU调度探针vLLM Production Stack

前面的章节把服务”调快”,10.1 把服务”送上线”。容器化 + Kubernetes 编排是 LLM 推理服务的标准交付形态。这一节解决三个核心问题:镜像怎么打、GPU 怎么调度、探针怎么配——其中探针是 LLM 服务最容易踩坑的地方(模型加载要几分钟,探针阈值没放宽,Pod 会被反复杀死)。

📑 目录


1. 容器化:镜像、非 root 与共享内存

1.1 官方镜像

# 预构建镜像(含 OpenAI 兼容 API)
docker pull vllm/vllm-openai:latest

docker run --runtime nvidia --gpus all \
  -v ~/.cache/huggingface:/root/.cache/huggingface \
  -p 8000:8000 \
  --ipc=host \
  vllm/vllm-openai:latest \
  --model mistralai/Mistral-7B-Instruct-v0.3
  • 镜像分层:vllm/vllm-openai 是基于 CUDA 的推理镜像(跑 vllm serve);另有 vllm/vllm(无 API 的库镜像)
  • --ipc=host--shm-size必须——Tensor Parallel(第 6 章)依赖共享内存做进程间通信,默认 64MB 的 /dev/shm 会直接 OOM

1.2 非 root 运行(安全基线)

CUDA 镜像默认以 root 运行(历史兼容),生产建议非 root:

docker run --runtime nvidia --gpus all \
  --user 1000:1000 \
  --ipc=host \
  -p 8000:8000 \
  vllm/vllm-openai:latest \
  --model meta-llama/Llama-3.1-8B-Instruct

📌 关键点:容器化的三件必备:共享内存(—ipc=host)+ 模型缓存挂载(HF 缓存目录)+ 非 root 用户。缺第一个 TP 起不来,缺第二个每次冷启动重新下载权重,缺第三个过不了安全审计。


2. K8s 部署清单:GPU 调度与模型缓存

2.1 最小 Deployment(NVIDIA GPU)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mistral-7b
spec:
  replicas: 1
  selector: { matchLabels: { app: mistral-7b } }
  template:
    metadata:
      labels: { app: mistral-7b }
    spec:
      volumes:
      - name: cache-volume          # 模型权重缓存(避免每次冷下载)
        persistentVolumeClaim: { claimName: mistral-7b }
      - name: shm                   # 共享内存(TP 通信必需)
        emptyDir:
          medium: Memory
          sizeLimit: "2Gi"
      containers:
      - name: mistral-7b
        image: vllm/vllm-openai:latest
        command: ["/bin/sh", "-c"]
        args: [
          "vllm serve mistralai/Mistral-7B-Instruct-v0.3 \
           --trust-remote-code --enable-chunked-prefill \
           --max-num-batched-tokens 1024"
        ]
        ports: [{ containerPort: 8000 }]
        resources:
          limits:
            cpu: "10"; memory: 20G
            nvidia.com/gpu: "1"      # ← GPU 调度入口
          requests:
            cpu: "2"; memory: 6G
            nvidia.com/gpu: "1"
        volumeMounts:
        - mountPath: /root/.cache/huggingface
          name: cache-volume
        - mountPath: /dev/shm
          name: shm

2.2 GPU 调度的三个要点

要点说明
资源声明NVIDIA:nvidia.com/gpu(需安装 NVIDIA device plugin);AMD:amd.com/gpu
request = limitGPU 必须 requests=limits(K8s 不调度超额 GPU)
整卡语义nvidia.com/gpu: "1" 是一整张卡(无 MIG 时),TP 场景一个 Pod 要 N 张卡

2.3 模型权重的两种加载方式

方式 A:PVC 挂载 HF 缓存(Deployment 直挂)
  首次冷启动下载 → 后续从 PVC 读
  优点:简单;缺点:多副本各挂各的 PVC → 每副本都要一份权重

方式 B:initContainer 预下载(Production Stack 默认)
  下载 Job 把权重拉进共享 PVC(ReadWriteMany)
  优点:副本共享一份权重,秒级启动;缺点:多一个存储依赖

💡 提示:多副本场景一定要用共享存储 + 预下载(方式 B)——否则每个副本冷启动各下载一份权重,扩容 10 个副本 = 10 次下载,扩缩容的延迟收益全被吃掉。这也是 vLLM Production Stack 把”模型预下载”做成默认 initContainer 的原因。


3. 探针配置:LLM 服务的生死线

3.1 探针的作用

探针判定LLM 服务的特殊性
startupProbe容器是否”开始工作”模型加载要 1-10 分钟——必须用 startup 探针兜住加载期
readinessProbe是否可接收流量/health 就绪前不要转发请求(否则请求排队爆掉)
livenessProbe是否存活(死则重启)阈值太紧 → 加载慢被误杀;LLM 推理长请求不能设太激进

3.2 vLLM 官方示例的探针

livenessProbe:
  httpGet: { path: /health, port: 8000 }
  initialDelaySeconds: 60      # 留出模型加载时间
  periodSeconds: 10
readinessProbe:
  httpGet: { path: /health, port: 8000 }
  initialDelaySeconds: 60
  periodSeconds: 5

3.3 最经典的故障:KeyboardInterrupt: terminated

K8s 官方文档专门列出此坑:startup/readiness 探针的 failureThreshold 太低,容器还没加载完模型就被 K8s 判死杀掉。特征:

1. Pod 反复 CrashLoopBackOff
2. kubectl get events 显示 "failed startup probe, will be restarted"
3. 容器日志尾部是 KeyboardInterrupt: terminated

对策:去掉探针先量一次”从启动到 /health 就绪的真实耗时”,再把 failureThreshold 调到该耗时 + 裕量(而不是拍脑袋定 3 次 × 10s)。

📌 关键点:探针的唯一正确配置方法是用数据说话:先裸跑(无探针)测 readiness 时间 → initialDelaySecondsfailureThreshold × periodSeconds 都要覆盖它。LLM 服务的加载时间随模型大小、存储速度波动(磁盘慢时 10 倍差距),阈值必须留 2-3 倍裕量。

3.4 gRPC 探针(--grpc 模式)

vLLM 支持 --grpc 启动(标准 gRPC Health Checking Protocol),K8s 1.24+ 可用原生 gRPC 探针:

livenessProbe:
  grpc: { port: 50051 }
  initialDelaySeconds: 120
  periodSeconds: 10
readinessProbe:
  grpc: { port: 50051 }
  initialDelaySeconds: 120
  periodSeconds: 5

gRPC 健康服务每次探针都检查引擎状态:引擎不健康或正在关停 → 返回 NOT_SERVING(比 HTTP /health 更精细)。


4. 模型加载慢的两个配套方案

探针只能”容忍”加载慢,真正解决加载慢的是这两个方案:

方案原理收益
共享 PVC 预下载initContainer 提前把权重拉进共享存储副本启动从分钟级 → 秒级
--model 用本地路径 + 只读挂载权重常驻共享存储,Pod 直接读同上,且省 Pod 内存储

💡 提示:vLLM 冷启动 = 权重下载(网络)+ 权重加载(磁盘/内存)+ 图捕获(CUDA Graph 预热)。生产经验:图捕获(约 30s-2min)是加载完成后才发生的事,探针的 /health 在引擎 ready 后才返回——所以探针阈值必须覆盖”下载+加载+图捕获”全链路。


5. vLLM Production Stack:官方全家桶

5.1 是什么

vLLM 官方(Berkeley-UChicago 合作)的生产部署套件:Helm chart 一键部署”多模型服务引擎 + 路由 + 缓存 + 观测”全家桶。原则:不修改上游 vLLM,只做编排层

sudo helm repo add vllm https://vllm-project.github.io/production-stack
sudo helm install vllm vllm/vllm-stack -f values.yaml
# values.yaml(最小示例)
servingEngineSpec:
  modelSpec:
  - name: "opt125m"
    repository: "vllm/vllm-openai"
    tag: "latest"
    modelURL: "facebook/opt-125m"
    replicaCount: 1
    requestCPU: 6
    requestMemory: "16Gi"
    requestGPU: 1
    pvcStorage: "10Gi"

5.2 全家桶成员

组件职责
Serving Engine多模型多副本的 vLLM 引擎(modelSpec 列表)
Router(lmstack-router)前缀感知/模型感知路由(10.3 详解)
LMCache ServerKV Cache 跨实例/CPU offload(--kv-offloading-backend lmcache
LoRA Controller多 LoRA 适配器管理(8.3 的服务化形态)
Grafana Dashboards内置 vLLM + LMCache 看板(一键启用)
KEDA Autoscaling基于指标的扩缩容(10.3 详解)

5.3 内置能力清单

  • 多模型混合部署(一个 chart 管 N 个模型)
  • 模型预下载 initContainer(共享 PVC,秒级启动)
  • vllmApiKey 原生支持(API Key 挂到引擎,解决”裸奔”问题)
  • Grafana 看板:TTFT、缓存命中率、LMCache 检索速度等(grafanaDashboards.enabled: true

📌 关键点有 Production Stack 就不建议手搓 Deployment——它把探针、预下载、路由、观测、扩缩容全部做成标准件,且随上游演进。学习顺序建议:先手写一遍 Deployment 理解原理(本节),再上 Production Stack 省事(本节第 5 节)。自建基础设施时,至少把它的设计抄走(预下载、探针、路由三件套)。


6. KServe 与替代方案

方案定位特点
KServe云原生模型服务标准(Kubeflow 生态)Serverless 自动扩缩容、多框架(vLLM/Triton/TEI)、InferenceGraph 路由
Triton + K8sNVIDIA 推理栈动态批处理、模型集成(ensemble)
Seldon Core通用 ML 部署多框架、解释性/监控扩展
自建 Deployment最小化场景本节 2-3 节的模式,可控但缺生态

选择逻辑

单模型、团队小 → 自建 Deployment(本节模式)或 Production Stack
多模型、要 Serverless 扩缩容 → KServe
NVIDIA 全栈(Triton 推理 + 引擎调优)→ Triton + K8s
要"官方支持 + 全家桶" → vLLM Production Stack(推荐起点)

💡 提示别在”部署框架选型”上花太多时间——它们的差异远小于”有没有可观测性、有没有预下载、探针对不对”。先把本节的基础件做对,框架随时可换。选型评估表就三行:模型管理、扩缩容方式、与监控栈的集成度。


📝 总结

  • 容器化三件套--ipc=host(共享内存)、模型缓存挂载、非 root
  • GPU 调度nvidia.com/gpu(device plugin 前置)、request=limit、整卡语义
  • 探针是 LLM 服务的生死线:加载期由 startup/长阈值兜住;KeyboardInterrupt: terminated = 阈值太紧
  • 探针配置用数据说话:先裸跑测 readiness 耗时,再定阈值(留 2-3 倍裕量)
  • 加载慢的正解:共享 PVC + initContainer 预下载(多副本秒级启动)
  • vLLM Production Stack:官方 Helm 全家桶——引擎+路由+LMCache+LoRA+Grafana+KEDA
  • 选型:默认 Production Stack;KServe(Serverless 多模型)、Triton(NVIDIA 全栈)

🎯 自我检验清单

  • 容器跑 vLLM 为什么必须 --ipc=host?(TP 通信依赖共享内存)
  • K8s 里 GPU 资源声明的两个注意点?
  • 多副本为什么必须共享 PVC + 预下载?
  • 探针三兄弟各自判定什么?LLM 场景分别怎么配?
  • KeyboardInterrupt: terminated 的成因与排查步骤?
  • 怎么用数据确定探针阈值?
  • Production Stack 全家桶有哪些组件?和手搓 Deployment 的差异?
  • 部署框架选型的评估标准?

📚 参考资料