3、vLLM部署基础:使用Deployment部署vLLM服务、暴露Service、配置资源限制
好,咱们进入实战环节。这一章我会带你亲手把vLLM服务跑在K8s上。说白了,就是三步走:用Deployment把模型拉起来,用Service把端口露出去,再给Pod拴上资源缰绳。这三件事搞定了,你的大模型推理服务才算真正“上线”。
3.1 为什么非得用Deployment?
你可能会问:我直接 docker run 跑一个vLLM容器不行吗?当然行,但那是玩具。在生产环境里,Pod挂了谁帮你重启?流量大了谁帮你扩容?版本更新了谁帮你滚动升级?
Deployment 就是干这个的。它帮你管理Pod的“生老病死”。我个人习惯是,任何无状态服务上来先套一个Deployment,省心。
核心要点: Deployment 保证你声明的副本数(replicas)始终存活。Pod挂了,它自动拉一个新的起来。你只管写YAML,剩下的交给它。
3.2 编写vLLM的Deployment YAML
先看一个最简版本。假设我们要部署 Qwen2.5-7B-Instruct 这个模型。
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-qwen25-7b
labels:
app: vllm-qwen25
spec:
replicas: 1
selector:
matchLabels:
app: vllm-qwen25
template:
metadata:
labels:
app: vllm-qwen25
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
command: ["python3", "-m", "vllm.entrypoints.openai.api_server"]
args:
- "--model"
- "/models/Qwen2.5-7B-Instruct"
- "--port"
- "8000"
ports:
- containerPort: 8000
name: http
volumeMounts:
- name: model-storage
mountPath: /models
volumes:
- name: model-storage
persistentVolumeClaim:
claimName: model-pvc
嗯,这里要注意几个点:
- 镜像版本:别用
latest,我在项目中被坑过——某次latest悄悄升级了,结果模型加载方式变了,服务直接崩了。建议固定到具体版本号,比如v0.6.3。 - 模型加载路径:我习惯把模型文件放在PVC(持久卷)里,这样Pod重启后不用重新下载。如果你用HuggingFace在线拉取,记得给Pod配好网络和认证。
- 启动参数:
--port 8000是vLLM默认的API端口,后面Service要跟这个对上。
3.3 暴露Service:让外界能访问
Deployment把Pod跑起来了,但Pod的IP是动态的。你总不能每次重启都去查IP吧?Service 就是给Pod一个固定的“入口”。
我个人最常用的是 ClusterIP 类型,配合Ingress对外暴露。但如果你只是想快速测试,用 NodePort 也行。
apiVersion: v1
kind: Service
metadata:
name: vllm-qwen25-svc
labels:
app: vllm-qwen25
spec:
type: ClusterIP
ports:
- port: 8000
targetPort: 8000
protocol: TCP
name: http
selector:
app: vllm-qwen25
这段YAML干了什么?
- 它创建了一个名叫
vllm-qwen25-svc的Service - 监听
8000端口,流量转发到Pod的8000端口 - 通过
selector匹配标签app: vllm-qwen25的Pod
小技巧: 如果你想让Service只转发到健康的Pod,可以配合Readiness Probe使用。vLLM本身提供了 /health 端点,我建议加上存活探针和就绪探针,这样K8s能自动摘掉不健康的Pod。
3.4 配置资源限制:别让模型把节点吃垮
大模型推理是典型的“算力饥渴”型负载。不加资源限制,一个Pod能把整台机器的显存和CPU吃光,其他服务全得遭殃。
我曾经遇到过一件事:团队里有人部署了一个70B的模型,没配资源限制,结果直接把GPU节点的显存打爆,节点挂了,上面十几个Pod全被驱逐。从那以后,我定了个规矩——所有推理服务必须显式声明资源请求和限制。
来看一个带资源限制的完整示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-qwen25-7b
spec:
replicas: 1
selector:
matchLabels:
app: vllm-qwen25
template:
metadata:
labels:
app: vllm-qwen25
spec:
containers:
- name: vllm
image: vllm/vllm-openai:v0.6.3
command: ["python3", "-m", "vllm.entrypoints.openai.api_server"]
args:
- "--model"
- "/models/Qwen2.5-7B-Instruct"
- "--port"
- "8000"
- "--max-model-len"
- "8192"
- "--gpu-memory-utilization"
- "0.9"
ports:
- containerPort: 8000
resources:
requests:
memory: "16Gi"
cpu: "4"
nvidia.com/gpu: 1
limits:
memory: "20Gi"
cpu: "8"
nvidia.com/gpu: 1
volumeMounts:
- name: dshm
mountPath: /dev/shm
- name: model-storage
mountPath: /models
volumes:
- name: dshm
emptyDir:
medium: Memory
sizeLimit: "8Gi"
- name: model-storage
persistentVolumeClaim:
claimName: model-pvc
这里有几个关键配置,我展开说说:
| 配置项 | 说明 | 我的建议 |
|---|---|---|
resources.requests |
Pod启动时保证能拿到的资源 | 按模型实际需求来,7B模型至少16GB内存+1块GPU |
resources.limits |
Pod最多能用的资源上限 | 比requests高20%左右,留点余量 |
nvidia.com/gpu |
声明需要GPU | 记得提前装好NVIDIA Device Plugin |
/dev/shm |
共享内存,vLLM内部通信用 | 建议挂载为Memory类型的emptyDir,大小8GB起步 |
--gpu-memory-utilization |
控制GPU显存利用率 | 0.9是比较稳妥的值,留10%给其他进程 |
警告: 千万别把 limits 设得比 requests 大太多。比如你请求1块GPU但限制4块,调度器可能把Pod调度到只有1块GPU的节点上,结果Pod启动后想用4块,直接OOM。requests和limits的GPU数量必须一致。
3.5 本章知识体系图
下面这张图帮你理清Deployment、Service、资源限制三者之间的关系:
3.6 部署与验证
YAML写好了,怎么跑起来?很简单:
# 创建PVC(假设你已经准备好了模型数据)
kubectl apply -f model-pvc.yaml
# 部署vLLM
kubectl apply -f vllm-deployment.yaml
# 暴露Service
kubectl apply -f vllm-service.yaml
# 查看状态
kubectl get pods -l app=vllm-qwen25
kubectl get svc vllm-qwen25-svc
# 测试调用(从集群内)
curl http://vllm-qwen25-svc:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "/models/Qwen2.5-7B-Instruct",
"messages": [{"role": "user", "content": "你好"}]
}'
如果返回了正常的JSON响应,恭喜你,vLLM服务已经在K8s上跑起来了。
避坑指南: 我第一次部署时,curl一直超时。查了半天发现是Pod里的vLLM监听的是 127.0.0.1:8000,而不是 0.0.0.0:8000。记得在启动参数里加 --host 0.0.0.0,否则Service转发不过去。
好了,这一章的内容就这些。Deployment、Service、资源限制,这三板斧抡好了,vLLM部署的基础就算打牢了。下一章我们会聊更进阶的话题——如何用HPA做自动扩缩容,让服务能扛住突发流量。
公众号:蓝海资料掘金营,微信deep3321