使用 Helm 在 Kubernetes 上部署 vLLM 推理服务:安装步骤、values 配置详解与模型自动下载原理
本篇基于 vLLM 仓库中的 Helm 部署文档 与官方示例 chart examples/deployment/chart-helm,完整讲解如何用 Helm 在 Kubernetes 上部署 vLLM 推理服务:包括前置条件、安装与卸载命令、全部 values 参数、部署架构,以及结合 chart 模板源码剖析"模型自动下载 + init 容器等待"这套默认机制的底层实现。读完后,你可以直接复制命令在自己集群中部署 vLLM,并理解每个 K8s 资源对象是由哪个模板、依据哪些参数渲染出来的。
部署架构概览
Helm 是 Kubernetes 的包管理器。借助它,vLLM 可以把整套 K8s 资源(Deployment、Service、PVC、Job、HPA 等)打包为一个 chart,以不同配置下发到多个 namespace。
从 chart 目录结构 可以看到,这个 chart(Chart.yaml 中声明 name: chart-vllm,type: application)由一组 Go Template 模板渲染而成:
| 模板文件 | 渲染出的 K8s 资源 | 作用 |
|---|---|---|
| templates/deployment.yaml | Deployment | 运行 vllm serve 主容器,含 init 容器、健康探针、GPU 亲和性 |
| templates/job.yaml | Job | 用 aws-cli 把模型权重从 S3 同步到 PVC(默认启用) |
| templates/pvc.yaml | PersistentVolumeClaim | 存放模型权重的持久化存储(ReadWriteOnce) |
| templates/service.yaml | Service | ClusterIP 类型,对外暴露推理端口 |
| templates/hpa.yaml | HorizontalPodAutoscaler | 按 CPU/内存利用率自动扩缩容(默认关闭) |
| templates/poddisruptionbudget.yaml | PodDisruptionBudget | 自愿中断时限制不可用副本数 |
| templates/configmap.yaml / templates/secrets.yaml | ConfigMap / Secret | 通过 envFrom 注入环境变量与 S3 凭据 |
| templates/custom-objects.yaml | 任意自定义对象 | 以 Helm 模板语法渲染任意 K8s 资源 |
整体数据流是:Job 先把模型权重从 S3 写入 PVC,Deployment 主容器把 PVC 挂载到 /data,容器内执行 vllm serve /data/ 启动 OpenAI 兼容服务,Service 将流量导入 Pod。
前置条件
在开始之前,确保你具备以下条件(引自 Helm 文档):
- 一个可用的 Kubernetes 集群;
- NVIDIA Kubernetes Device Plugin(
k8s-device-plugin),用于向 K8s 注册 GPU 资源; - 集群中有可用的 GPU 资源;
- (可选)存放模型权重的 S3 桶或其他存储 —— 仅在使用"自动模型下载"时需要。
S3 凭据会在安装时以 --set secrets.* 的方式传入,最终由 templates/secrets.yaml 渲染为一个 Opaque Secret(值经 b64enc 编码),命名规则为 {{ .Release.Name }}-secrets。
安装与卸载 Chart
文档以 examples/deployment/chart-helm 目录中的 chart 为例。helm upgrade --install 命令在 chart 所在目录(即 examples/deployment/chart-helm)下执行,安装一个 release 名为 test-vllm 的部署:
helm upgrade --install --create-namespace \
--namespace=ns-vllm test-vllm . \
-f values.yaml \
--set secrets.s3endpoint=$ACCESS_POINT \
--set secrets.s3bucketname=$BUCKET \
--set secrets.s3accesskeyid=$ACCESS_KEY \
--set secrets.s3accesskey=$SECRET_KEY
要点说明:
--create-namespace --namespace=ns-vllm:自动创建并使用独立 namespace,便于把同一 chart 部署到多个 namespace 做不同配置;-f values.yaml:使用 chart 自带 values.yaml 作为基线值,也可以用自定义文件覆盖;- 四个
--set secrets.*覆盖 S3 凭据,供默认启用的模型下载 Job 使用;values.yaml中的secrets默认为空对象{},即 S3 地址/桶名/密钥必须显式提供,否则wait-download-model与下载 Job 无法工作。
卸载命令:
helm uninstall test-vllm --namespace=ns-vllm
文档特别强调:该命令会删除 chart 关联的所有 Kubernetes 组件,包括持久卷(PVC),并删除 release。也就是说 test-vllm-storage-claim 这个 PVC 会被一并移除,模型权重不会保留,重新部署时需要再次下载。
主容器与 Deployment:源码级细节
渲染逻辑集中在 templates/deployment.yaml 与 templates/_helpers.tpl 中,几个关键实现细节:
-
镜像与启动命令。主容器镜像为
{{ .Values.image.repository }}:{{ .Values.image.tag }},默认vllm/vllm-openai:latest。image.repository与image.tag使用required强制校验,未定义会渲染失败。仓库 values.yaml 中默认的启动命令为:command: ["vllm", "serve", "/data/", "--served-model-name", "opt-125m", "--enforce-eager", "--dtype", "bfloat16", "--block-size", "16", "--host", "0.0.0.0", "--port", "8000"]即从 PVC 挂载目录
/data/加载模型、以 125M 参数的小模型opt-125m为例做演示,并显式指定--enforce-eager(禁用 CUDA Graph 以便在资源受限环境启动)、bfloat16精度与 16 的 block size。实际部署时替换为你的模型路径与参数即可。 -
模型存储挂载。主容器把
{{ .Release.Name }}-storage卷(指向 PVC{{ .Release.Name }}-storage-claim)挂载到/data,与vllm serve /data/形成闭环。 -
GPU 调度。当
resources.requests与resources.limits中nvidia.com/gpu均大于 0 时,模板会自动追加runtimeClassName: nvidia,并生成基于节点标签nvidia.com/gpu.product的nodeAffinity,候选值取自gpuModels列表(默认占位符TYPE_GPU_USED,必须替换成实际卡型,如 A10G/H100 对应的 product 标签值)。这也解释了前置条件中要求安装 NVIDIA Device Plugin——没有插件就没有nvidia.com/gpu资源可请求。 -
副本数与更新策略。
replicas来自replicaCount(默认 1);deploymentStrategy未配置时,helpers 中的chart.strategy默认使用rollingUpdate: maxSurge 100% / maxUnavailable 0的零中断滚动更新,且 Deployment 的progressDeadlineSeconds为 1200 秒,为 vLLM 这类大模型容器拉镜像、加载权重的慢启动留出余量。 -
环境变量注入。若配置了
configs/secrets/externalConfigs,模板通过envFrom引用{{ .Release.Name }}-configsConfigMap 与{{ .Release.Name }}-secretsSecret。这意味着所有 S3 凭据最终都以环境变量形式进入容器(见下一节),也可以借此注入VLLM_*系列环境变量给 vLLM 主进程。
模型自动下载机制:Job + 等待 init 容器
这是该 chart 默认开启(extraInit.modelDownload.enabled: true)的核心机制,由两个协作者构成:
下载 Job(templates/job.yaml):当 modelDownload.enabled 为真时渲染出名为 {{ .Release.Name }}-init-vllm 的 Job,其容器 job-download-model 使用 aws-cli 镜像(默认 amazon/aws-cli:2.6.4)执行:
aws --endpoint-url $S3_ENDPOINT_URL s3 sync s3://$S3_BUCKET_NAME/$S3_PATH /data
把桶内 s3modelpath 指定的模型目录同步到 PVC 的 /data。Job 的 restartPolicy: OnFailure,完成后 100 秒内自动清理(ttlSecondsAfterFinished: 100),资源请求/上限仅为 200m/500m CPU、1Gi/2Gi 内存——下载任务本身很轻。
等待 init 容器(templates/deployment.yaml 第 75–98 行附近):Deployment 的 wait-download-model init 容器在主容器之前运行,通过轮询"干跑"同步结果来阻塞,直到模型文件就绪:
while aws --endpoint-url $S3_ENDPOINT_URL s3 sync --dryrun s3://$S3_BUCKET_NAME/$S3_PATH /data | grep -q download; do sleep 10; done
即:只要 --dryrun 输出中还有 download 条目(说明 /data 里还缺文件),就每 10 秒重试一次。这样主容器启动时模型必然已就位,避免了 vllm serve 因权重缺失而反复崩溃。
凭据如何流转。init 容器与 Job 的环境变量由 helpers 中的 chart.extraInitEnv 统一生成:S3_ENDPOINT_URL、S3_BUCKET_NAME、AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY 四个变量分别通过 secretKeyRef 从 {{ .Release.Name }}-secrets 中读取 s3endpoint、s3bucketname、s3accesskeyid、s3accesskey 四个键——这正是安装命令里那四个 --set secrets.* 的落点;S3_PATH 则直接取自 extraInit.s3modelpath。此外,若 extraInit.awsEc2MetadataDisabled 有定义(默认 true),还会注入 AWS_EC2_METADATA_DISABLED,避免在 EKS 之外的云环境(如自建集群、GCP/Azure)上 aws-cli 反复查询 EC2 元数据服务导致启动变慢。注意:一旦显式配置了 waitContainer.env 或 downloadJob.env,模板会整体替换默认的 S3 变量组,适合改用 Hugging Face 等其它下载方式。
PVC、Service、HPA 与 PDB
- PVC(templates/pvc.yaml):只要
extraInit有值就渲染,容量取自extraInit.pvcStorage(默认"1Gi",只够 opt-125m 演示模型;部署 7B 级以上模型务必调大,如10Gi),访问模式固定为ReadWriteOnce。 - Service(templates/service.yaml):固定
ClusterIP类型,servicePort(默认 80)映射到容器端口名container-port(即containerPort,默认 8000,与--port 8000对应)。serviceName为空时默认命名{{ .Release.Name }}-service。 - HPA(templates/hpa.yaml):
autoscaling.enabled: true时渲染autoscaling/v2HPA,支持 CPU(targetCPUUtilizationPercentage,默认 80)与内存(targetMemoryUtilizationPercentage,values 中默认注释)两个 Resource 指标,副本数在minReplicas(默认 1)到maxReplicas(默认 100)之间伸缩。 - PDB(templates/poddisruptionbudget.yaml):
maxUnavailable默认 1,保障节点维护等自愿中断期间的服务可用性。 - ConfigMap / Secrets:
configs(默认{})渲染为{{ .Release.Name }}-configs;secrets渲染为 base64 编码的 Opaque Secret。 - 自定义资源:
customObjects列表中的每一项都会以 Helm 模板语法渲染成独立 K8s 对象,方便追加 Ingress、InferenceService 等任意资源。
values 参数完整说明
以下表格完整收录 Helm 文档 中 values.yaml 的可配置参数:
| Key | Type | Default | Description |
|---|---|---|---|
| autoscaling | object | {"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80} | Autoscaling configuration |
| autoscaling.enabled | bool | false | Enable autoscaling |
| autoscaling.maxReplicas | int | 100 | Maximum replicas |
| autoscaling.minReplicas | int | 1 | Minimum replicas |
| autoscaling.targetCPUUtilizationPercentage | int | 80 | Target CPU utilization for autoscaling |
| configs | object | {} | Configmap |
| containerPort | int | 8000 | Container port |
| customObjects | list | [] | Custom Objects configuration |
| deploymentStrategy | object | {} | Deployment strategy configuration |
| externalConfigs | list | [] | External configuration |
| extraContainers | list | [] | Additional containers configuration |
| extraInit | object | {"modelDownload":{"enabled":true},"initContainers":[],"pvcStorage":"1Gi"} | Additional configuration for init containers |
| extraInit.modelDownload | object | {"enabled":true} | Model download functionality configuration |
| extraInit.modelDownload.enabled | bool | true | Enable automatic model download job and wait container |
| extraInit.modelDownload.image | object | {"repository":"amazon/aws-cli","tag":"2.6.4","pullPolicy":"IfNotPresent"} | Image for model download operations |
| extraInit.modelDownload.waitContainer | object | {} | Wait container configuration (command, args, env) |
| extraInit.modelDownload.downloadJob | object | {} | Download job configuration (command, args, env) |
| extraInit.initContainers | list | [] | Custom init containers (appended after model download if enabled) |
| extraInit.pvcStorage | string | "1Gi" | Storage size for the PVC |
| extraInit.s3modelpath | string | "relative_s3_model_path/opt-125m" | (Optional) Path of the model on S3 |
| extraInit.awsEc2MetadataDisabled | bool | true | (Optional) Disable AWS EC2 metadata service |
| extraPorts | list | [] | Additional ports configuration |
| gpuModels | list | ["TYPE_GPU_USED"] | Type of gpu used |
| image | object | {"command":["vllm","serve","/data/","--served-model-name","opt-125m","--host","0.0.0.0","--port","8000"],"repository":"vllm/vllm-openai","tag":"latest"} | Image configuration |
| image.command | list | ["vllm","serve","/data/","--served-model-name","opt-125m","--host","0.0.0.0","--port","8000"] | Container launch command |
| image.repository | string | "vllm/vllm-openai" | Image repository |
| image.tag | string | "latest" | Image tag |
| livenessProbe | object | {"failureThreshold":3,"httpGet":{"path":"/health","port":8000},"initialDelaySeconds":15,"periodSeconds":10} | Liveness probe configuration |
| livenessProbe.failureThreshold | int | 3 | Number of times after which if a probe fails in a row, Kubernetes considers that the overall check has failed: the container is not alive |
| livenessProbe.httpGet | object | {"path":"/health","port":8000} | Configuration of the kubelet http request on the server |
| livenessProbe.httpGet.path | string | "/health" | Path to access on the HTTP server |
| livenessProbe.httpGet.port | int | 8000 | Name or number of the port to access on the container, on which the server is listening |
| livenessProbe.initialDelaySeconds | int | 15 | Number of seconds after the container has started before liveness probe is initiated |
| livenessProbe.periodSeconds | int | 10 | How often (in seconds) to perform the liveness probe |
| maxUnavailablePodDisruptionBudget | string | "" | Disruption Budget Configuration |
| readinessProbe | object | {"failureThreshold":3,"httpGet":{"path":"/health","port":8000},"initialDelaySeconds":5,"periodSeconds":5} | Readiness probe configuration |
| readinessProbe.failureThreshold | int | 3 | Number of times after which if a probe fails in a row, Kubernetes considers that the overall check has failed: the container is not ready |
| readinessProbe.httpGet | object | {"path":"/health","port":8000} | Configuration of the kubelet http request on the server |
| readinessProbe.httpGet.path | string | "/health" | Path to access on the HTTP server |
| readinessProbe.httpGet.port | int | 8000 | Name or number of the port to access on the container, on which the server is listening |
| readinessProbe.initialDelaySeconds | int | 5 | Number of seconds after the container has started before readiness probe is initiated |
| readinessProbe.periodSeconds | int | 5 | How often (in seconds) to perform the readiness probe |
| replicaCount | int | 1 | Number of replicas |
| resources | object | {"limits":{"cpu":4,"memory":"16Gi","nvidia.com/gpu":1},"requests":{"cpu":4,"memory":"16Gi","nvidia.com/gpu":1}} | Resource configuration |
| resources.limits."nvidia.com/gpu" | int | 1 | Number of GPUs used |
| resources.limits.cpu | int | 4 | Number of CPUs |
| resources.limits.memory | string | "16Gi" | CPU memory configuration |
| resources.requests."nvidia.com/gpu" | int | 1 | Number of GPUs used |
| resources.requests.cpu | int | 4 | Number of CPUs |
| resources.requests.memory | string | "16Gi" | CPU memory configuration |
| secrets | object | {} | Secrets configuration |
| serviceName | string | "" | Service name |
| servicePort | int | 80 | Service port |
| labels.environment | string | test | Environment name |
结合仓库源码补充几点文档表格之外的实现事实:
- 两个探针(
readinessProbe/livenessProbe)整体通过 helpers 中的chart.probes以toYaml透传,因此可以按 K8s 标准探针语法追加timeoutSeconds、successThreshold等字段;探针对应的是 vLLM OpenAI 兼容服务的/health端点。 resources.requests.memory、cpu与limits对应字段在 helpers 的chart.resources中均为required,缺失会导致渲染直接报错。- 从 deployment.yaml 的结构看,chart 还支持
nodeSelector、tolerations与image.securityContext/image.runAsUser等透传字段(image未显式给出 securityContext 时,默认runAsNonRoot: false),便于把 vLLM 钉到带 GPU 污点容忍的专用节点池。 - chart 附带 values.schema.json 用于
helm侧的值校验,tests/目录下的 deployment_test.yaml、job_test.yaml 等基于 chart-testing 的测试文件验证了 Deployment/Job/PVC 等模板的输出契约,可参考其断言来确认自己 values 的渲染结果是否符合预期。
配置示例
示例一:S3 模型下载(默认方式)
将模型存放在 S3 并调整存储配额:
extraInit:
modelDownload:
enabled: true
pvcStorage: "10Gi"
s3modelpath: "models/llama-7b"
安装命令中通过 --set secrets.s3endpoint / s3bucketname / s3accesskeyid / s3accesskey 提供凭据后,models/llama-7b 目录会在 Pod 启动前同步进 /data。记得把 image.command 里的 --served-model-name 与模型路径改成你的真实模型,并把 gpuModels 换成节点上 nvidia.com/gpu.product 标签的实际取值。
示例二:仅使用自定义 Init 容器(如 llm-d 场景)
当不需要 chart 内置的 S3 下载、而要注入自定义 sidecar/init 容器时:
extraInit:
modelDownload:
enabled: false
initContainers:
- name: llm-d-routing-proxy
image: ghcr.io/llm-d/llm-d-routing-sidecar:v0.2.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: proxy
securityContext:
runAsUser: 1000
restartPolicy: Always
pvcStorage: "10Gi"
从 deployment.yaml 的条件 {{- if and .Values.extraInit (or .Values.extraInit.modelDownload.enabled .Values.extraInit.initContainers) }} 看,只要 modelDownload.enabled 或 initContainers 任一有值就会渲染 init 容器段;自定义 init 容器追加在 wait-download-model 之后,PVC 依然会创建并挂载,模型可以通过其它方式(镜像自带、节点预置、对象存储挂载卷等)提前写入。
小结
vLLM 仓库自带的 Helm chart 覆盖了 LLM 服务在 K8s 上落地的完整链路:PVC 承载模型权重、aws-cli Job 拉取权重、init 容器轮询等待、GPU 节点亲和调度、健康探针、滚动更新策略与 HPA/PDB。所有可调项都收敛在 values.yaml 中,配合 --set secrets.* 注入凭据即可用一条 helm upgrade --install 完成部署;需要深度定制时,直接阅读 templates 下的对应模板文件就能定位渲染逻辑。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
