在K8s上部署vLLM的完整踩坑记录

37 阅读3分钟

我们公司上个月把LLM推理服务迁移到了K8s+vLLM,跑了一个月,整体稳定。这篇文章记录一下我们踩过的坑,给想上车的朋友一些参考。

为什么自建

直接用云服务商API不好吗?

说实话,对于大部分场景确实够用了。我们迁移主要是三个原因:

  1. 数据合规:金融业务,不能把数据发到第三方
  2. 成本:日均调用量上百万,自建比API便宜得多
  3. 定制:需要跑微调模型,公有API不支持

如果你的场景没有这些需求,直接用API就行,别折腾。

集群准备

我们用的是阿里云ACK,配置如下:

# 7B模型,单卡A10G(24GB显存)
Worker节点配置:
- 数量:2台
- GPU:NVIDIA A10G × 1
- CPU:8核
- 内存:32GB
- 系统盘:100GB SSD

生产环境跑70B的话,需要4卡A100。这个成本就比较感人了,一块A100 80GB差不多小十万。

GPU Operator必须装,不然K8s识别不了显卡:

# 安装GPU Operator
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/gpu-operator/main/statefulset.yaml

# 验证安装
kubectl get pods -n gpu-operator

vLLM单节点部署

测试环境用Deployment跑就够了:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-inference
  namespace: llm-serving
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vllm
  template:
    metadata:
      labels:
        app: vllm
    spec:
      containers:
      - name: vllm
        image: vllm/vllm-openai:v0.4.0
        ports:
        - containerPort: 8000
        resources:
          limits:
            nvidia.com/gpu: "1"
            memory: "24Gi"
            cpu: "8"
        env:
        - name: MODEL_NAME
          value: "deepseek-ai/DeepSeek-V4-Flash"
        args:
        - --model
        - $(MODEL_NAME)
        - --tensor-parallel-size
        - "1"
        - --gpu-memory-utilization
        - "0.9"
        - --max-model-len
        - "8192"

服务暴露:

apiVersion: v1
kind: Service
metadata:
  name: vllm-service
  namespace: llm-serving
spec:
  type: ClusterIP
  ports:
  - port: 8000
    targetPort: 8000
  selector:
    app: vllm

压测验证

部署完了别急着上线,先压测:

# 测试并发
for i in {1..10}; do
  curl http://vllm-service.llm-serving:8000/v1/completions \
    -H "Content-Type: application/json" \
    -d '{"model": "deepseek-ai/DeepSeek-V4-Flash", "prompt": "写一个快速排序", "max_tokens": 200}' &
done
wait

我们测出来单卡A10G跑7B模型:

  • 并发8请求,延迟稳定在200-300ms
  • 并发16请求,开始出现排队
  • 并发32请求,显存不够,开始OOM

多卡并行踩坑

70B的模型必须多卡,不然显存根本装不下。

# 4卡并行部署
spec:
  containers:
  - name: vllm
    resources:
      limits:
        nvidia.com/gpu: "4"  # 关键:申请4张卡
    args:
    - --model
    - $(MODEL_NAME)
    - --tensor-parallel-size
    - "4"  # 张量并行,一张卡放不下四分之一
    - --pipeline-parallel-size
    - "1"
    - --gpu-memory-utilization
    - "0.95"

踩的坑:

  1. 卡间通信带宽不够——我们之前用的机器网络是25G的,跑起来卡顿严重。换到100G网络才好
  2. NCCL配置问题——多机并行的时候NCCL经常超时,加了这些参数才解决:
# Node上设置
export NCCL_IB_TIMEOUT=30
export NCCL_IB_DISABLE=0
export NCCL_DEBUG=INFO  # 调试用,生产关掉

自动扩缩容

用KEDA根据请求数扩缩:

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: agent-scaler
spec:
  scaleTargetRef:
    name: vllm-inference
  pollingInterval: 10
  cooldownPeriod: 60
  minReplicaCount: 1
  maxReplicaCount: 10
  triggers:
  - type: prometheus
    metadata:
      serverAddress: http://prometheus:9090
      metricName: pending_requests
      threshold: "20"
      query: sum(rate(vllm_server_num_requests_waiting_total[1m]))

有个坑:vLLM的metrics有时候不准,我们遇到过一次扩缩容没触发的情况。后面加了自定义metrics解决了。

KServe标准化

生产环境建议用KServe管理InferenceService,配置如下:

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: llm-service
  namespace: llm-serving
spec:
  predictor:
    model:
      modelFormat:
        name: vLLM
      runtime: vllm
      storageUri: s3://my-bucket/llama-3-8b
      resources:
        limits:
          nvidia.com/gpu: "1"
          memory: "24Gi"

KServe的好处是统一了推理接口,以后换模型只需要改storageUri,不用动代码。

总结

跑了一个月,稳定是真的稳定,平均每天处理请求300万+,P99延迟控制在800ms以内。

成本方面:4卡A100一个月云服务费大概8万,我们日均请求量800万,换算下来比用API便宜了60%。

值不值得上,看你们体量。如果日均请求量没上百万,建议还是先用API,省心。


有问题评论区问,看到了回。