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加载模型需要时间,尤其是大模型,可能要好几分钟。
我一般用三步来验证:
- 看日志:
kubectl logs <pod-name>,看到Uvicorn running on http://0.0.0.0:8000才算真正启动完成。 - 健康检查:vLLM提供了
/health端点,返回200就说明服务正常。 - 实际推理:调用
/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部署的基础操作,说白了就是选对镜像、写好YAML、配好Service、做好验证。下一章咱们会深入聊聊资源限制和性能调优,那才是真正考验功力的时候。