AI Infra实战05:用Helm在K8s中部署vLLM,从安装到压测全流程

本篇配套的全部脚本、YAML、Helm Chart、预检工具和 Terraform 配置已开源在 GitHub:https://github.com/Jich1123/gpu-k8s-lab 。clone 下来按
00-lab/execution-checklist.md的顺序执行即可复现全部实验(仓库不含任何真实 IP、密钥或账号信息)。
本篇目标
用自己编写的 Helm Chart 在 K8s 中部署 vLLM 推理服务,跑通 OpenAI 兼容 API,用 2500 次请求压测拿到真实性能数据,并在 Grafana 上看到 GPU 从空闲到满载的完整曲线。
学完本篇你将掌握:
- 如何把 vLLM 封装成 Helm Chart(Deployment、Service、PVC、ServiceMonitor)
- 单 GPU 环境下的更新策略选择(Recreate vs RollingUpdate)
- T4 上 vLLM 的真实性能表现(吞吐、延迟分布、显存分配)
- 压测方法和结果解读
- T4 的架构限制(V0 引擎回退、XFormers 替代 FlashAttention)
证据等级:B级实验复现。本篇所有命令、输出和性能数据来自一次真实执行(AWS g4dn.xlarge / T4),压测 2500 请求零失败。
实操环境
| 配置 | 值 |
|---|---|
| 平台 | AWS EC2 g4dn.xlarge |
| GPU | Tesla T4(16GB显存) |
| 系统 | Ubuntu 22.04.5 LTS |
| k3s | v1.34.10+k3s1 |
| GPU Operator | 已部署(实验01) |
| 监控 | kube-prometheus-stack 已部署(实验02) |
| vLLM | v0.10.1.1 |
| 模型 | Qwen/Qwen2-1.5B-Instruct |
| 费用 | 约 0.526/小时,本篇实操约 0.5(含模型下载和压测) |
一、为什么用 Helm 部署,而不是直接 kubectl apply
前面第03篇(vLLM部署实战)是在裸机上直接跑 vllm serve,适合快速验证。但在 K8s 环境中,你需要:
- Deployment(管理 Pod 生命周期、重启策略)
- Service(提供稳定的集群内访问入口)
- PVC(模型缓存持久化,避免每次重启都重新下载几个 GB 的模型)
- ServiceMonitor(让 Prometheus 自动发现 vLLM 暴露的 /metrics)
- 可配置的 values.yaml(不同环境改参数,不改模板)
这些打包成一个 Helm Chart,一条 helm install 全部部署,一条 helm uninstall 全部清理。
二、Chart 核心设计
完整 Chart 在配套资料的 05-vllm-helm/vllm-chart/ 中,这里说几个关键设计决策。
更新策略:Recreate
yaml
strategy:
type: Recreate
单 GPU 环境必须用 Recreate。因为 nvidia.com/gpu: 1 是独占资源,如果用 RollingUpdate,新 Pod 和旧 Pod 同时申请同一张 GPU,新 Pod 会一直 Pending。Recreate 先杀旧再起新,代价是升级时有短暂中断,但避免了 GPU 争抢死锁。
模型缓存 PVC
yaml
volumes:
- name: model-cache
persistentVolumeClaim:
claimName: vllm-vllm
20Gi PVC 挂载到 /root/.cache/huggingface,模型权重下载一次就缓存住。Pod 被删重建后,PVC 还在,不用重新下载。
GPU 资源申请
yaml
resources:
requests:
nvidia.com/gpu: "1"
limits:
nvidia.com/gpu: "1"
requests 和 limits 都写 1,确保 Pod 独占这张 T4。
启动命令与参数
Deployment 模板里,容器的启动命令就是 vllm serve,后面跟一串通过 values 注入的参数:
yaml
command: [vllm, serve, "Qwen/Qwen2-1.5B-Instruct"]
args:
- --host
- 0.0.0.0
- --port
- "8000"
- --dtype
- float16
- --max-model-len
- "4096"
- --gpu-memory-utilization
- "0.90"
- --max-num-seqs
- "64"
- --tensor-parallel-size
- "1"
几个和 T4 相关的关键参数:
--dtype float16:T4 不支持 bfloat16 的高效计算,用 float16。vLLM 启动时会自动把模型的 bfloat16 权重转成 float16。--gpu-memory-utilization 0.90:让 vLLM 用 90% 的显存(剩 10% 给系统和碎片)。这个值直接决定 KV Cache 能拿多少显存,进而决定最大并发。--max-model-len 4096:单请求最大 token 数,越大越占显存。--tensor-parallel-size 1:单卡,不做张量并行。多卡才需要调大。
三个探针
模板给容器配了 startup / readiness / liveness 三个探针,都打 /health:
yaml
startupProbe: { httpGet: { path: /health, port: http }, failureThreshold: 180 }
readinessProbe: { httpGet: { path: /health, port: http } }
livenessProbe: { httpGet: { path: /health, port: http } }
startupProbe 的 failureThreshold 给得很大(180)是刻意的:vLLM 首次启动要下载模型 + 加载权重 + 初始化引擎,可能好几分钟。startup 探针没过之前,readiness 和 liveness 不生效,避免容器还在加载模型就被 liveness 判定失败重启,陷入"启动-被杀-重启"的死循环。这是部署大模型推理服务的一个通用技巧。
三、部署
bash
bash install.sh
关键输出:
Release "vllm" does not exist. Installing it now.
STATUS: deployed
NAMESPACE: ai-inference
Pod 启动过程
安装后 Pod 经历 Pending → ContainerCreating → Running 三个阶段:
- ContainerCreating(约4分钟):拉取 vLLM 镜像(vllm/vllm-openai:v0.10.1.1,较大)
- Running 但 0/1(约1-2分钟):容器启动了但 readiness 探针还没过,vLLM 在下载模型和初始化引擎
- Running 1/1:服务就绪

vLLM 启动日志中的关键信息
模型加载成功后,日志里有一段非常有价值的显存分配信息:
total_gpu_memory (14.56GiB) x gpu_memory_utilization (0.90) = 13.11GiB
model weights take 2.89GiB
non_torch_memory takes 0.05GiB
PyTorch activation peak memory takes 0.48GiB
the rest of the memory reserved for KV Cache is 9.70GiB
Maximum concurrency for 4096 tokens per request: 88.64x
这段话翻译一下:
- T4 总显存 14.56 GiB,vLLM 用了 90% = 13.11 GiB
- 其中模型权重 2.89 GiB(Qwen2-1.5B 很小)
- KV Cache 拿到了 9.70 GiB,这是 vLLM 高并发的关键:它用 PagedAttention 动态分配 KV Cache,这 9.7G 能同时服务 88 个并发请求(4096 tokens/请求)

T4 上的特殊行为:T4 是 Turing 架构(Compute Capability 7.5),vLLM 的 V1 引擎不支持,自动回退到 V0 引擎;同时 FlashAttention-2 不支持 Turing,改用 XFormers 后端。这不影响正确性,但性能不如 A100/H100 上的 V1 + FlashAttention-2。这是 T4 上的固有行为,文章里应该说清楚,避免读者用 T4 复现时困惑。
四、验证:API 真的能推理
bash
bash scripts/verify.sh
查询模型
json
{"data":[{"id":"qwen2-1.5b-instruct","object":"model","max_model_len":4096}]}
真实对话
问:什么是 Kubernetes?
json
{
"choices":[{
"message":{
"content":"Kubernetes是一个开源的容器编排和自动化管理系统,用于管理和部署容器化应用程序。"
},
"finish_reason":"stop"
}],
"usage":{"prompt_tokens":26,"completion_tokens":20,"total_tokens":46}
}
推理成功,finish_reason 是 stop(自然结束,不是截断)。
Helm Test
TEST SUITE: vllm-vllm-test-connection
Phase: Succeeded
五、压测:2500 请求的真实性能数据
这是本篇最有价值的部分。用配套的 benchmark.sh,发 2500 次相同请求(单轮对话,max_tokens 50),并发 10:
bash
CONCURRENCY=10 REQUESTS=2500 bash scripts/benchmark.sh
脚本的压测逻辑不复杂,核心是用后台进程控制并发:每发一个请求就放到后台,当在跑的请求数达到 CONCURRENCY 上限时就等一个完成再发下一个,始终维持固定并发。每个请求用 curl -w '%{http_code} %{time_total}' 记录状态码和耗时,最后用 Python 算出 P50/P95/P99。它不依赖任何压测框架,一个 bash 脚本就能拿到可信的延迟分布,方便在任何机器上复现。
压测结果
总请求: 2500
成功: 2500
失败: 0
总耗时: 156730 ms
吞吐量: 15.95 req/s
平均延迟: 0.576 s
P50延迟: 0.574 s
P95延迟: 0.593 s
P99延迟: 0.609 s

怎么解读这些数据
零失败:2500 个请求全部 HTTP 200,vLLM 在持续负载下没有崩溃、超时或拒绝。
P50 和 P99 只差 35ms(0.574 vs 0.609):延迟分布极其集中,几乎没有长尾。这正是 vLLM Continuous Batching 的价值:它把多个请求合并到一个 GPU batch 里一起算,每个请求的延迟不会因为"排在别人后面"而差很多。
吞吐 15.95 req/s:对 T4 + 1.5B 模型来说合理。如果换成更大的模型(7B/14B),吞吐会下降,延迟会上升。
压测期间的 GPU 状态
压测过程中用 nvidia-smi 抓到的实时数据:
| 时间点 | GPU-Util | 功耗 | 温度 | 显存 |
|---|---|---|---|---|
| 压测前 | 0% | 14W | 32C | 0 用 |
| 压测中(峰值) | 92% | 70W(满额) | 50C | 13409 MiB |
| 压测后 | 0% | 27W | 40C | 13409 MiB(常驻) |
注意:压测结束后 GPU-Util 回到 0%,但显存不释放(13409 MiB),因为 vLLM 的模型权重和 KV Cache 预留是常驻的。这是正常行为,不是内存泄漏。
Grafana 对比(配合实验02)
在实验02的 Grafana 面板上,压测前后的对比截图清晰记录了 GPU 从空闲到满载再回落的完整过程。利用率从 0% 跳到 92%,功耗从 14W 顶到 71W(T4 TDP 上限),温度升了 19 度。详细对比图见实验02。
六、升级与回滚
配套脚本包含了 Recreate 策略下的升级回滚验证:
bash
bash scripts/upgrade-rollback.sh
因为策略是 Recreate,升级时会:
- 杀掉旧 Pod
- 启动新 Pod(重新加载模型)
- 中间有约1-2分钟的中断
这是单 GPU 环境的固有代价。生产环境如果有多张 GPU,可以改用 RollingUpdate,新旧 Pod 各占一张卡,实现无中断升级。

七、踩过的坑小结
| 坑 | 现象 | 处理 |
|---|---|---|
| T4 回退 V0 引擎 | 启动日志 WARNING: Compute Capability < 8.0 | T4 是 Turing(7.5),V1 不支持,自动回退 V0,正常 |
| FlashAttention-2 不可用 | Cannot use FlashAttention-2 for Turing | 改用 XFormers 后端,不影响正确性 |
| ContainerCreating 很久 | Pod 卡在 ContainerCreating 4分钟 | 镜像大,正常;如果更久检查网络和镜像源 |
| 显存压测后不释放 | 压测结束显存仍 13.4GB | vLLM 模型+KV Cache 常驻,正常行为 |
小结
这一篇完成了从"裸机跑 vLLM"到"K8s 里用 Helm 管理 vLLM"的跨越。Helm Chart 让部署可重复、可配置、可回滚;压测数据让你对 T4 上的 vLLM 性能有了真实的数字感;监控联动让你能看到推理负载下 GPU 的真实状态。
到这里,GPU 基础(01)、监控(02)、推理服务(05)三篇都跑通了。剩下的实验07(KServe)会在下次 GPU 实例上完成,届时你将看到 Serverless 推理、自动扩缩容和灰度发布的真实验证。
参考链接
- 本系列开源仓库(脚本 + Helm Chart + Terraform):https://github.com/Jich1123/gpu-k8s-lab
- vLLM 官方文档:https://docs.vllm.ai/en/latest/
- Helm 官方文档:https://helm.sh/docs/
- Kubernetes 探针配置(startup/readiness/liveness):https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/