饮墨

子安饮墨馀三斗,留与卿儿作赋来

Kubernetes 部署 vLLM 推理服务实战:4 步打造生产级大模型 Serving

8 views

痛点:大模型上了 K8s,但推理服务总是翻车

越来越多团队把 LLM 推理服务搬上 Kubernetes——听起来很美,实际落地全是坑。

典型场景:团队用 FastAPI 包了个 Hugging Face Transformers 模型,丢进 K8s 跑。结果呢?单个请求吃满 24GB 显存,并发一上来就 OOM;模型加载要 3 分钟,Pod 重启一次用户等到崩溃;GPU 利用率忽高忽低,A100 一个月烧几万刀却跑不满。

核心问题:传统的模型推理方式没有针对 LLM 的长序列、大显存、高并发做优化。你需要一个专为 LLM 设计的推理引擎,而 vLLM 就是目前最成熟的选择。

方案:vLLM + Kubernetes GPU 调度

vLLM 是一个高性能 LLM 推理引擎,核心优势:

  • PagedAttention:像操作系统管理内存一样管理 KV Cache,显存利用率提升 2-4 倍
  • Continuous Batching:动态合并请求,吞吐量比静态 Batching 高 5-20 倍
  • OpenAI 兼容 API:零改动替换 OpenAI 后端,前端代码无需修改
  • Tensor Parallel:多卡并行推理,支撑 70B+ 大模型

架构很简洁:NVIDIA GPU Operator 管理 GPU 驱动 → vLLM Pod 挂载 GPU → Service/Ingress 暴露 API → Prometheus 采集推理指标 → HPA 自动扩缩。

实操步骤

Step 1:准备 GPU 节点和 NVIDIA Device Plugin

前提:K8s 集群有 GPU 节点(推荐 A10G/A100/H100),已安装 NVIDIA GPU Operator。

# 安装 NVIDIA GPU Operator(如果尚未安装)
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update

helm install gpu-operator nvidia/gpu-operator \
  --namespace gpu-operator \
  --create-namespace \
  --set driver.enabled=true \
  --set toolkit.enabled=true

# 验证 GPU 节点就绪
kubectl get nodes -l nvidia.com/gpu.present=true
kubectl describe node <gpu-node> | grep -A5 "Allocatable"
# 应能看到 nvidia.com/gpu: 1(或更多)

确认 GPU 资源可被调度后,给 GPU 节点打标签,方便后续调度:

kubectl label nodes <gpu-node> workload-type=llm-inference

Step 2:部署 vLLM 推理服务

创建 Deployment,以部署 Qwen2.5-7B-Instruct 为例:

# vllm-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-qwen25-7b
  namespace: llm-serving
  labels:
    app: vllm-qwen25-7b
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vllm-qwen25-7b
  template:
    metadata:
      labels:
        app: vllm-qwen25-7b
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8000"
        prometheus.io/path: "/metrics"
    spec:
      nodeSelector:
        workload-type: llm-inference
      containers:
      - name: vllm
        image: vllm/vllm-openai:latest
        args:
        - "--model"
        - "Qwen/Qwen2.5-7B-Instruct"
        - "--tensor-parallel-size"
        - "1"
        - "--max-model-len"
        - "8192"
        - "--gpu-memory-utilization"
        - "0.90"
        - "--enable-prefix-caching"
        - "--port"
        - "8000"
        ports:
        - containerPort: 8000
          name: http
          protocol: TCP
        resources:
          limits:
            nvidia.com/gpu: "1"    # 7B 模型单卡即可
          requests:
            cpu: "4"
            memory: "16Gi"
        readinessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 120  # 模型加载需要时间
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 180
          periodSeconds: 30
        volumeMounts:
        - name: model-cache
          mountPath: /root/.cache/huggingface
        - name: shm
          mountPath: /dev/shm
      volumes:
      - name: model-cache
        persistentVolumeClaim:
          claimName: model-cache-pvc   # 预下载模型,避免每次拉取
      - name: shm
        emptyDir:
          medium: Memory
          sizeLimit: 8Gi             # PyTorch 共享内存,必须给够
---
apiVersion: v1
kind: Service
metadata:
  name: vllm-qwen25-7b
  namespace: llm-serving
spec:
  selector:
    app: vllm-qwen25-7b
  ports:
  - port: 8000
    targetPort: 8000
    protocol: TCP
  type: ClusterIP

部署并验证:

kubectl create namespace llm-serving
kubectl apply -f vllm-deployment.yaml

# 等待 Pod Running(首次需要下载模型,可能 5-10 分钟)
kubectl -n llm-serving get pods -w

# 测试推理(OpenAI 兼容接口)
kubectl -n llm-serving port-forward svc/vllm-qwen25-7b 8000:8000 &

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen2.5-7B-Instruct",
    "messages": [{"role": "user", "content": "用一句话解释 Kubernetes"}],
    "max_tokens": 100
  }'

Step 3:配置基于推理指标的 HPA 自动扩缩

vLLM 暴露 Prometheus 指标,我们可以基于 等待中的请求数 做弹性扩缩:

# vllm-hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: vllm-qwen25-7b
  namespace: llm-serving
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: vllm-qwen25-7b
  minReplicas: 1
  maxReplicas: 4
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
      - type: Pods
        value: 1
        periodSeconds: 120        # GPU Pod 启动慢,扩容别太激进
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Pods
        value: 1
        periodSeconds: 180
  metrics:
  - type: Pods
    pods:
      metric:
        name: vllm_num_requests_waiting  # vLLM 排队请求数
      target:
        type: AverageValue
        averageValue: "5"                # 排队超过 5 个就扩容

需要配合 Prometheus Adapter 将 vLLM 指标暴露为 K8s Custom Metrics:

# prometheus-adapter 规则片段
rules:
- seriesQuery: 'vllm_num_requests_waiting{namespace!="",pod!=""}'
  resources:
    overrides:
      namespace: {resource: "namespace"}
      pod: {resource: "pod"}
  name:
    matches: "^(.*)$"
    as: "vllm_num_requests_waiting"
  metricsQuery: 'avg(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>)'

Step 4:监控大盘与关键告警

vLLM 内置 /metrics 端点,核心指标:

指标 含义 告警阈值建议
vllm_num_requests_running 正在处理的请求数 —
vllm_num_requests_waiting 排队等待的请求数 > 20 持续 2min 告警
vllm_gpu_cache_usage_perc KV Cache 显存使用率 > 95% 告警
vllm_avg_generation_throughput_toks_per_s 生成吞吐(tokens/s) < 基线 50% 告警
vllm_e2e_request_latency_seconds 端到端延迟 P99 > 10s 告警

Prometheus 告警规则示例:

groups:
- name: vllm-alerts
  rules:
  - alert: VLLMHighQueueDepth
    expr: vllm_num_requests_waiting > 20
    for: 2m
    labels:
      severity: warning
    annotations:
      summary: "vLLM 请求排队过长 ({{ $value }} 个请求等待)"
  - alert: VLLMKVCacheNearFull
    expr: vllm_gpu_cache_usage_perc > 0.95
    for: 1m
    labels:
      severity: critical
    annotations:
      summary: "vLLM KV Cache 即将耗尽 ({{ $value | humanizePercentage }})"

避坑指南

坑 1:Pod 启动 OOMKilled,但 GPU 显存还有余量

原因:vLLM 模型加载阶段需要大量 CPU 内存(模型权重先加载到 CPU 再转到 GPU)。7B 模型需要约 14GB CPU 内存,70B 模型可能需要 140GB+。

解法:resources.requests.memory 至少设为模型参数量 × 2(FP16),同时 /dev/shm 的 sizeLimit 要给够,PyTorch 的 DataLoader 和 NCCL 通信依赖共享内存。

坑 2:模型每次 Pod 重启都重新下载,启动要 10 分钟

原因:默认模型从 Hugging Face Hub 下载到容器内,Pod 销毁后缓存丢失。

解法:用 PVC 持久化模型缓存目录 /root/.cache/huggingface,或预先把模型上传到 S3/MinIO,用 initContainer 拉取。大规模集群推荐用 Dragonfly 做 P2P 分发:

# 预下载模型到 PVC(一次性 Job)
kubectl -n llm-serving run model-download --rm -it \
  --image=vllm/vllm-openai:latest \
  --overrides='{"spec":{"containers":[{"name":"dl","image":"vllm/vllm-openai:latest","command":["python3","-c","from huggingface_hub import snapshot_download; snapshot_download(\"Qwen/Qwen2.5-7B-Instruct\")"],"volumeMounts":[{"name":"cache","mountPath":"/root/.cache/huggingface"}]}],"volumes":[{"name":"cache","persistentVolumeClaim":{"claimName":"model-cache-pvc"}}]}}' \
  -- /bin/true

坑 3:并发上来后延迟飙升,吞吐反而下降

原因:--max-model-len 设太大,导致 KV Cache 预分配过多显存,实际可容纳的并发请求数反而减少。

解法:根据实际业务场景设置合理的 --max-model-len。如果 90% 的请求 input+output 不超过 4096 tokens,就别设 32768。用 --gpu-memory-utilization 0.90 控制显存上限(留 10% 安全余量),并开启 --enable-prefix-caching 让相同 system prompt 的请求共享 KV Cache。

总结

环节 关键配置 一句话建议
GPU 调度 NVIDIA GPU Operator + nodeSelector 确保 Device Plugin 就绪再部署
推理引擎 vLLM + PagedAttention 比原生 Transformers 吞吐高 5-20x
弹性扩缩 HPA + vllm_num_requests_waiting 扩容慢缩容稳,别用 CPU 指标
模型缓存 PVC 持久化 + 预下载 冷启动从 10min 降到 1min
监控 KV Cache 使用率 + 排队深度 这两个指标比 GPU 利用率更有用

vLLM 在 K8s 上部署的核心思路:用 PagedAttention 最大化单卡并发 → 用 Continuous Batching 榨干吞吐 → 用自定义指标 HPA 按需扩缩 → 用 PVC 消灭冷启动。GPU 算力贵,每一点利用率都是钱。

分享:

相关文章