使用 Helm 在 Kubernetes 上部署 vLLM 推理服务:安装步骤、values 配置详解与模型自动下载原理
【免费下载链接】vllmA high-throughput and memory-efficient inference and serving engine for LLMs项目地址: https://gitcode.com/GitHub_Trending/vl/vllm
本篇基于 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 渲染为一个OpaqueSecret(值经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-modelinit 容器在主容器之前运行,通过轮询"干跑"同步结果来阻塞,直到模型文件就绪:
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 下的对应模板文件就能定位渲染逻辑。
【免费下载链接】vllmA high-throughput and memory-efficient inference and serving engine for LLMs项目地址: https://gitcode.com/GitHub_Trending/vl/vllm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考