3、环境搭建:在 K8s 集群中部署 vLLM 推理服务(Docker 镜像 + YAML 配置)
好,咱们直接进入正题。上一章我们把 K8s 集群搭起来了,现在要让它真正跑起来——把 vLLM 推理服务部署上去。说白了,就是两步:先搞定 Docker 镜像,再写好 YAML 配置。我刚开始接触这套流程时,总觉得 YAML 写起来像玄学,后来踩了几个坑才摸清门道。
3.1 构建 vLLM 推理镜像
我个人习惯,先把镜像构建流程跑通。vLLM 官方提供了 Dockerfile,但直接拿来用可能会遇到 CUDA 版本不匹配的问题。我在项目中遇到过,镜像拉下来后,GPU 驱动版本对不上,推理速度直接掉到三分之一。
这里我建议你基于官方镜像二次构建。先拉取基础镜像:
docker pull vllm/vllm-openai:latest
然后写一个简单的 Dockerfile,把模型文件打包进去。为什么要打包?你想想看,每次启动都从 HuggingFace 下载模型,网络波动一下就得重来,生产环境根本扛不住。
FROM vllm/vllm-openai:latest
# 把本地下载好的模型拷贝进去
COPY ./models/Qwen2-7B-Instruct /models/Qwen2-7B-Instruct
# 设置默认启动参数
ENTRYPOINT ["python3", "-m", "vllm.entrypoints.openai.api_server"]
CMD ["--model", "/models/Qwen2-7B-Instruct", "--port", "8000"]
构建镜像时注意标签规范。我习惯用 项目名-模型名:日期 的格式,方便回滚。
docker build -t my-inference/qwen2-7b:20240501 .
docker login。我见过有人镜像构建好了,推不上去,排查半天发现是没登录。
3.2 推送镜像到仓库
镜像构建好之后,得推送到 K8s 节点能拉取到的仓库。我一般用 Harbor 或者阿里云镜像仓库。命令很简单:
docker tag my-inference/qwen2-7b:20240501 registry.example.com/my-inference/qwen2-7b:20240501
docker push registry.example.com/my-inference/qwen2-7b:20240501
嗯,这里要注意一点:镜像大小。vLLM 镜像加上模型,动不动就 10GB+。推送前最好确认下网络带宽,我曾经在办公室网络推了半小时没推完,最后改用内网仓库才搞定。
3.3 编写 Deployment YAML
镜像准备好了,接下来就是 K8s 的核心——YAML 配置。我把它分成三块:Deployment、Service、ConfigMap。先看 Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-inference
namespace: ai-infra
spec:
replicas: 2
selector:
matchLabels:
app: vllm-inference
template:
metadata:
labels:
app: vllm-inference
spec:
containers:
- name: vllm
image: registry.example.com/my-inference/qwen2-7b:20240501
ports:
- containerPort: 8000
resources:
requests:
nvidia.com/gpu: 1
memory: "32Gi"
cpu: "8"
limits:
nvidia.com/gpu: 1
memory: "64Gi"
cpu: "16"
env:
- name: CUDA_VISIBLE_DEVICES
value: "0"
volumeMounts:
- name: model-cache
mountPath: /root/.cache/huggingface
volumes:
- name: model-cache
hostPath:
path: /data/model-cache
type: DirectoryOrCreate
nvidia.com/gpu: 1表示每个 Pod 申请 1 张 GPU。别贪多,一张卡跑一个模型实例最稳。- 内存申请 32Gi,限制 64Gi。vLLM 推理时,显存不够会 spill 到内存,留点余量。
- hostPath 挂载模型缓存,避免每次重启都重新下载。
3.4 编写 Service 和 ConfigMap
Deployment 只管跑 Pod,要让外部能访问,还得配 Service。我习惯用 ClusterIP 类型,内部服务间调用足够了。
apiVersion: v1
kind: Service
metadata:
name: vllm-service
namespace: ai-infra
spec:
selector:
app: vllm-inference
ports:
- protocol: TCP
port: 8000
targetPort: 8000
type: ClusterIP
ConfigMap 用来管理推理参数。为什么要单独拎出来?你想想看,每次调参都要改 YAML 重新部署,多麻烦。把参数抽出来,改 ConfigMap 后重启 Pod 就行。
apiVersion: v1
kind: ConfigMap
metadata:
name: vllm-config
namespace: ai-infra
data:
max-model-len: "4096"
gpu-memory-utilization: "0.9"
tensor-parallel-size: "1"
gpu-memory-utilization 设成 0.95,结果模型加载时直接 OOM。建议从 0.8 开始调,稳定后再往上加。
3.5 部署与验证
配置写好了,执行部署命令:
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f configmap.yaml
查看 Pod 状态:
kubectl get pods -n ai-infra -w
等 Pod 变成 Running 状态后,测试一下推理接口:
# 先端口转发到本地
kubectl port-forward -n ai-infra pod/vllm-inference-xxx 8000:8000
# 测试推理
curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "/models/Qwen2-7B-Instruct",
"prompt": "你好,请介绍一下你自己",
"max_tokens": 100
}'
如果返回了正常的 JSON 响应,恭喜你,部署成功了!
3.6 知识体系总览
为了让你对整个流程有个直观认识,我画了张图:
这张图把整个部署流程串起来了。从镜像构建到最终调优,每一步都有它的意义。我个人觉得,最容易被忽略的是 ConfigMap 那一步——很多人直接把参数写死在代码里,后面改起来想哭。
tensor-parallel-size 没配好,或者模型加载参数有问题。
好了,环境搭建这块就讲到这里。你按照这个流程走一遍,应该能顺利把 vLLM 推理服务跑起来。记住,镜像构建要细心,YAML 配置要规范,验证测试不能省。