2、vLLM部署基础:镜像选择、单Pod部署、服务暴露与验证

好,咱们正式开始动手部署vLLM。这一章是地基,地基打不牢,后面优化全白搭。我见过不少团队一上来就搞什么自动扩缩容、GPU共享,结果连Pod都起不来,排查半天发现是镜像版本不对——这种坑,咱们今天一次性填平。

2.1 vLLM镜像选择:别小看这个步骤

vLLM的镜像,说白了就两个流派:官方镜像和自己构建的镜像。我个人习惯,初期先用官方镜像,跑通了再考虑定制。

官方镜像地址是:vllm/vllm-openai。你可能会问,为什么不是 vllm/vllm?嗯,这里有个小故事。早期vLLM的镜像命名确实就是 vllm/vllm,但后来社区发现,很多人只需要OpenAI兼容的API接口,不需要完整的推理引擎代码。所以官方干脆拆成了两个:

  • vllm/vllm-openai:只包含OpenAI兼容的API服务,体积小,启动快。适合生产环境。
  • vllm/vllm:包含完整的vLLM引擎和所有依赖,适合开发和调试。

我在项目中遇到过,有人图省事直接拉 vllm/vllm:latest,结果镜像1.2GB,拉取就花了5分钟。后来换成 vllm/vllm-openai,只有600MB,部署速度快了一倍。所以我的建议是:

生产环境用 vllm/vllm-openai,开发环境用 vllm/vllm。

版本号怎么选?我建议锁定 0.4.0 或以上版本。为什么?因为0.4.0开始支持了PagedAttention v2,显存利用率提升明显。你想想看,如果还在用0.3.x,同样的模型可能要多占20%显存,这不划算。

小技巧: 拉取镜像时加上 --platform linux/amd64,避免在ARM架构的节点上拉错版本。我曾经在Mac M1上踩过这个坑,镜像拉下来跑不了,排查了半天才发现是架构不匹配。

2.2 单Pod部署vLLM:最简配置

好,镜像选好了,接下来咱们写一个最简单的Deployment YAML。别急着上什么StatefulSet、DaemonSet,先让Pod跑起来再说。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-server
  labels:
    app: vllm
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vllm
  template:
    metadata:
      labels:
        app: vllm
    spec:
      containers:
      - name: vllm
        image: vllm/vllm-openai:0.4.0
        command: ["python3", "-m", "vllm.entrypoints.openai.api_server"]
        args:
          - "--model"
          - "mistralai/Mistral-7B-Instruct-v0.1"
          - "--port"
          - "8000"
          - "--max-model-len"
          - "4096"
          - "--gpu-memory-utilization"
          - "0.9"
        ports:
        - containerPort: 8000
        resources:
          requests:
            memory: "16Gi"
            cpu: "4"
            nvidia.com/gpu: 1
          limits:
            memory: "20Gi"
            cpu: "6"
            nvidia.com/gpu: 1

这个配置里,有几个关键点我要强调一下:

  • command和args:vLLM的入口是 vllm.entrypoints.openai.api_server,不是直接跑 vllm serve。虽然 vllm serve 也能用,但那是给命令行用的,在K8s里还是用Python模块更稳定。
  • --gpu-memory-utilization 0.9:这个参数的意思是,vLLM最多使用90%的显存。为什么不是100%?因为系统本身也要占一点显存,留10%给操作系统和CUDA驱动,避免OOM。我刚开始部署时设成0.95,结果模型加载到一半就崩了,后来改成0.9就稳了。
  • resources:这里我故意把requests和limits设得不一样。requests是保证值,limits是上限。CPU可以超分,但GPU不能。所以GPU的requests和limits必须一致,否则调度器会出问题。

注意: 如果你用的是NVIDIA GPU,别忘了安装NVIDIA Device Plugin。否则Pod会一直Pending,报错 0/1 nodes are available: 1 Insufficient nvidia.com/gpu。这个坑我踩过不止一次。

2.3 暴露服务:ClusterIP vs NodePort

Pod跑起来了,但怎么访问它?这就涉及到Service了。K8s里暴露服务的方式很多,但最常用的就两种:ClusterIP和NodePort。

ClusterIP:只能在集群内部访问。适合微服务之间的调用,比如你的前端服务要调vLLM的API。

NodePort:在集群每个节点上开一个端口,外部可以通过 节点IP:端口 访问。适合测试环境,或者需要从集群外调用的场景。

我个人习惯,生产环境用ClusterIP + Ingress,测试环境用NodePort。但这一章咱们先讲基础,所以两种都给你看看。

ClusterIP Service

apiVersion: v1
kind: Service
metadata:
  name: vllm-service
spec:
  selector:
    app: vllm
  ports:
  - protocol: TCP
    port: 8000
    targetPort: 8000
  type: ClusterIP

这个Service很简单,就是把Pod的8000端口映射到Service的8000端口。集群内的其他Pod可以通过 vllm-service:8000 访问。

NodePort Service

apiVersion: v1
kind: Service
metadata:
  name: vllm-service-nodeport
spec:
  selector:
    app: vllm
  ports:
  - protocol: TCP
    port: 8000
    targetPort: 8000
    nodePort: 30080
  type: NodePort

这里我指定了 nodePort: 30080,这样外部就可以通过 任意节点IP:30080 访问vLLM了。如果不指定,K8s会随机分配一个端口(30000-32767之间)。

小技巧: 如果你用的是云服务商(比如AWS EKS、GKE),NodePort可能被安全组拦截。记得检查节点的安全组规则,确保30080端口是开放的。

2.4 验证部署是否成功:别光看Pod状态

很多人部署完,看到Pod状态是Running就觉得万事大吉了。其实不然。Pod Running只代表容器启动了,不代表vLLM服务已经就绪。vLLM加载模型需要时间,尤其是大模型,可能要好几分钟。

我一般用三步来验证:

  1. 看日志kubectl logs <pod-name>,看到 Uvicorn running on http://0.0.0.0:8000 才算真正启动完成。
  2. 健康检查:vLLM提供了 /health 端点,返回200就说明服务正常。
  3. 实际推理:调用 /v1/completions/v1/chat/completions,看能不能返回结果。

具体命令如下:

# 1. 查看日志
kubectl logs -l app=vllm --tail=50

# 2. 健康检查(假设Service是ClusterIP,在集群内执行)
curl http://vllm-service:8000/health

# 3. 实际推理
curl http://vllm-service:8000/v1/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mistralai/Mistral-7B-Instruct-v0.1",
    "prompt": "Hello, how are you?",
    "max_tokens": 50
  }'

如果返回了类似这样的JSON,就说明部署成功了:

{
  "id": "cmpl-xxx",
  "object": "text_completion",
  "created": 1234567890,
  "model": "mistralai/Mistral-7B-Instruct-v0.1",
  "choices": [
    {
      "text": " I'm doing well, thank you! How can I assist you today?",
      "index": 0,
      "logprobs": null,
      "finish_reason": "length"
    }
  ]
}

注意: 如果健康检查返回503,别慌。这通常是因为模型还在加载中。等个一两分钟再试。如果一直503,检查一下Pod的日志,看看是不是显存不够或者模型路径错了。

2.5 本章知识体系图

下面这张图,帮你把这一章的核心逻辑串起来。从镜像选择到Pod部署,再到服务暴露和验证,每一步都有对应的操作和注意事项。

vLLM部署基础流程 1. 镜像选择 vllm/vllm-openai 2. 单Pod部署 Deployment + GPU 3. 暴露服务 ClusterIP / NodePort 4. 验证 /health 镜像选择 • 生产环境:vllm/vllm-openai(600MB) • 开发环境:vllm/vllm(1.2GB) • 版本锁定:≥ 0.4.0 单Pod部署 • 入口:vllm.entrypoints.openai.api_server • 关键参数:--gpu-memory-utilization 0.9 • GPU资源:requests和limits必须一致 暴露服务 • ClusterIP:集群内部访问,适合微服务 • NodePort:外部访问,适合测试 验证 • 三步验证:日志 → /health → 实际推理

这张图里,每一步都对应着具体的操作。你跟着这个流程走,基本不会出大问题。如果卡住了,回头看看每个步骤的注意事项,大概率能找到原因。


好了,这一章的内容就到这里。vLLM部署的基础操作,说白了就是选对镜像、写好YAML、配好Service、做好验证。下一章咱们会深入聊聊资源限制和性能调优,那才是真正考验功力的时候。