Helm通过Chart模板将Kubernetes应用配置参数化,实现一键部署与版本化管理的包管理体验。Chart支持依赖管理、值覆盖与模板函数,适合管理复杂的AI推理服务栈。文章从Chart结构设计、values分层配置到私有仓库搭建,给出Helm在AI工程中的落地实践。

岗位关联

需要学习Helm包管理,数据管道与模型训练的衔接需要MLOps能力。本篇涵盖MLOps/LLMOps、模型部署、CI/CD、版本管理,帮助你实现AI工程化交付。

目录
  1. 可视化演示
  2. Helm Chart 目录结构与核心文件组织
  3. values.yaml 分层配置与值覆盖机制
  4. Go Template 模板函数与条件渲染逻辑
  5. Chart 依赖管理与子 Chart 组合复用
  6. Helm Release 版本管理与回滚机制
  7. 私有 Chart 仓库搭建与 OCI 注册表集成
  8. AI 推理服务栈的 Helm 一体化部署实践
  9. AI 应用场景
  10. Python 演示
  11. 常见问答
  12. 常见误区
  13. 参考来源

可视化演示

展示Helm Chart目录结构到YAML渲染流程、values分层配置覆盖合并机制、以及Release版本管理与回滚操作。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Chart结构渲染 Chart.yaml声明元信息,values.yaml定义默认配置参数 templates目录存放Go template渲染的K8s资源模板 helm install → 模板渲染 → YAML输出 Chart结构将K8s应用配置参数化打包

Helm Chart 目录结构与核心文件组织

Helm Chart 是 Kubernetes 应用的打包格式,由固定目录结构组成。一个典型的 Chart 包含:Chart.yaml(声明 chart 名称、版本、appVersion 等元信息)、templates/ 目录(存放 Deployment、Service 等 K8s 资源的 Go 模板文件)、values.yaml(提供模板渲染的默认参数值)、charts/ 目录(存放依赖的子 Chart)、_helpers.tpl(定义可复用的命名模板函数)。

Chart.yaml 是 Chart 的元数据文件,其中 apiVersion 指定 Chart API 版本(v2 用于 Helm 3),name 定义 Chart 名称,version 是 Chart 本身的版本号(遵循语义化版本),appVersion 是所打包应用的版本号。type 字段可以是 application(可安装的完整应用)或 library(仅提供模板复用的库 Chart)。

templates 目录中的每个文件都是一个 Go template,Helm 渲染时将其转换为有效的 Kubernetes YAML。模板文件可以使用 .Values 引用 values.yaml 中的参数,使用条件判断、循环等逻辑控制生成的资源。_helpers.tpl 文件以下划线开头,不会被渲染为独立资源,专门用于定义可复用的模板片段,如标准化的标签生成器、名称生成器等。

以 LLM 推理服务为例,一个完整的 Chart 可能包含:templates/deployment.yaml(推理容器)、templates/service.yaml(API 服务)、templates/hpa.yaml(自动扩缩)、templates/configmap.yaml(模型配置)、templates/redis.yaml(缓存子 Chart)。values.yaml 定义 GPU 副本数、模型镜像版本、推理端口等默认参数。

yaml12 行
# Chart.yamlapiVersion: v2name: llm-servingversion: 1.2.0appVersion: '2.1.0'type: applicationdescription: LLM inference serving chartdependencies:  - name: redis    version: '17.x.x'    repository: https://charts.bitnami.com/bitnami    condition: redis.enabled

values.yaml 分层配置与值覆盖机制

Helm 的 values 机制支持多层级配置覆盖。默认值在 values.yaml 中定义,部署时可通过 -f values-prod.yaml 加载环境特定配置覆盖默认值,也可用 --set image.tag=v1.2.3 临时覆盖单个参数。多个 -f 文件按顺序加载,后面的覆盖前面的同名键。

覆盖规则是浅层合并(shallow merge):同级键被整体替换而非深度合并。例如子 Chart 定义了 config.data: {a: 1, b: 2},父 Chart 的 values 中设置 config.data: {c: 3},结果是 config.data 整体变为 {c: 3},而非 {a: 1, b: 2, c: 3}。这一点与 Kustomize 的 strategic merge 不同,需要特别注意。

values 优先级从低到高:Chart 自带 values.yaml < 第一个 -f 文件 < 第二个 -f 文件 < --set 参数。这种分层机制使得同一套 Chart 模板可以适配不同环境:values-dev.yaml 设置 1 个副本无 GPU,values-prod.yaml 设置 3 个 GPU 副本并启用 HPA,切换环境只需更换 values 文件。

子 Chart 的 values 通过父 Chart 的 values 中子 Chart 名称作为键传入。例如父 Chart 依赖 redis 子 Chart,可以在父 Chart 的 values.yaml 中通过 redis: {architecture: replication, auth: {enabled: true}} 传递参数给子 Chart。global 键下的值对所有子 Chart 可见,适合传递镜像仓库地址等全局配置。

yaml28 行
# values-dev.yamlreplicaCount: 1gpu:  enabled: false  resources:    limits: {}image:  tag: '2.1.0-dev'redis:  enabled: true  architecture: standalone # values-prod.yamlreplicaCount: 3gpu:  enabled: true  resources:    limits:      nvidia.com/gpu: 1image:  tag: '2.1.0'hpa:  enabled: true  minReplicas: 3  maxReplicas: 10redis:  enabled: true  architecture: replication

Go Template 模板函数与条件渲染逻辑

Helm 模板使用 Go template 语法,通过 {{ .Values.xxx }} 引用 values 参数渲染到 YAML 中。支持 if/else 条件判断(如根据 .Values.hpa.enabled 决定是否生成 HPA 资源)、range 循环(如遍历 .Values.ports 生成多端口 Service 配置)。

内置函数增强了模板表达力:include 复用 _helpers.tpl 中定义的命名模板(如 include "llm-serving.fullname" . 生成标准化的资源名称);required 标记必填参数,缺失时终止渲染并报错;toYaml 将值转为 YAML 格式(常用于 labels 和 annotations);tpl 将字符串作为模板再次渲染(用于动态模板场景)。

条件渲染是 Helm 模板的核心能力。典型用法包括:根据 .Values.gpu.enabled 决定是否添加 GPU 资源限制;根据 .Values.hpa.enabled 决定是否渲染 HPA 资源文件;根据 .Values.ingress.enabled 决定是否创建 Ingress 资源。每个条件分支确保只生成当前环境需要的资源。

range 循环用于处理列表型配置。例如遍历 .Values.extraEnv 生成多个环境变量、遍历 .Values.service.ports 生成多个端口定义、遍历 .Values.volumes 生成多个卷挂载。循环内部可以嵌套条件判断,实现灵活的模板逻辑。

yaml22 行
# templates/deployment.yamlapiVersion: apps/v1kind: Deploymentmetadata:  name: {{ include "llm-serving.fullname" . }}  labels:    {{- include "llm-serving.labels" . | nindent 4 }}spec:  replicas: {{ .Values.replicaCount }}  template:    spec:      containers:      - name: model-server        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"        resources:          limits:            {{- if .Values.gpu.enabled }}            nvidia.com/gpu: {{ .Values.gpu.count }}            {{- end }}            memory: {{ .Values.resources.memory | quote }}          requests:            cpu: {{ .Values.resources.cpu | quote }}

Chart 依赖管理与子 Chart 组合复用

复杂应用通常拆分为多个子 Chart 通过依赖管理组合。在 Chart.yaml 的 dependencies 字段声明依赖的子 Chart(如 redis、prometheus),每个依赖指定 name、version 和 repository。执行 helm dependency update 会下载子 Chart 到 charts/ 目录,或执行 helm dependency build 根据 Chart.lock 精确构建依赖。

父 Chart 通过 values.yaml 中与子 Chart 同名的键传递参数。例如依赖 redis 子 Chart 时,在父 Chart 的 values.yaml 中通过 redis: {architecture: replication} 传递参数。子 Chart 接收到的 values 是父 Chart 中 redis 键下的内容,而非整个 values。

global 键是特殊的全局配置通道,其下的值对所有子 Chart 可见。适合传递镜像仓库地址、公共标签、全局环境变量等需要跨 Chart 共享的配置。子 Chart 通过 .Values.global.imageRegistry 引用全局值。

Chart.lock 文件锁定依赖的精确版本(类似 package-lock.json),确保团队成员和 CI 环境使用完全一致的依赖版本。使用 helm dependency update 更新依赖时会更新 Chart.lock,建议将其提交到版本控制中。

Helm Release 版本管理与回滚机制

Helm 跟踪每次部署为一个 release,每次 install 或 upgrade 都会生成递增的 revision 版本号。helm history 命令查看 release 的所有版本历史,包括每个 revision 的状态(deployed/superseded/failed)、更新时间、Chart 版本和 app 版本。

helm rollback 命令将 release 回退到指定 revision。例如 helm rollback my-release 3 将 my-release 回退到第 3 个 revision。回退操作本身也会生成新的 revision 记录。如果不指定 revision 号,默认回退到上一个版本。

release 信息(包括渲染后的 manifests 和 values)存储在 K8s 集群内的 Secret(Helm 3 默认)或 ConfigMap 中。这意味着即使本地配置丢失,也能从集群恢复 release 状态。每个 release 的数据存储在 kube-system 命名空间的 sh.helm.release.v1.<release-name>.v<revision> Secret 中。

helm upgrade 执行的是三方合并(3-way strategic merge):对比上次部署的 manifest、当前集群实际状态和新 manifest。但已删除的 K8s 资源(如手动删除的 Service)Helm 不会自动重建,除非使用 --force 强制重建所有资源或 helm uninstall 后重新 install。

python47 行
# 模拟 Helm release 版本管理import jsonfrom datetime import datetime class HelmRelease:    def __init__(self, name, namespace='default'):        self.name = name        self.namespace = namespace        self.revisions = []        self.current_revision = 0     def install(self, chart, values):        self.current_revision += 1        self.revisions.append({            'revision': self.current_revision,            'chart': chart,            'values': values,            'status': 'deployed',            'timestamp': datetime.now().isoformat()        })        return self.current_revision     def upgrade(self, chart, values):        if self.revisions:            self.revisions[-1]['status'] = 'superseded'        return self.install(chart, values)     def rollback(self, target_revision=None):        if target_revision is None:            for rev in reversed(self.revisions[:-1]):                if rev['status'] == 'superseded':                    target_revision = rev['revision']                    break        target = next(r for r in self.revisions if r['revision'] == target_revision)        self.revisions[-1]['status'] = 'superseded'        return self.install(target['chart'], target['values'])     def history(self):        return [(r['revision'], r['status'], r['chart']) for r in self.revisions] # Demorelease = HelmRelease('llm-serving')print(f"Install v1: revision {release.install('llm-serving-1.0.0', {'replicas': 1})}")print(f"Upgrade v2: revision {release.upgrade('llm-serving-1.1.0', {'replicas': 3})}")print(f"Upgrade v3: revision {release.upgrade('llm-serving-1.2.0', {'replicas': 5})}")print(f"Rollback:   revision {release.rollback(2)}")print(f"History:    {release.history()}")

私有 Chart 仓库搭建与 OCI 注册表集成

团队共享 Chart 需要搭建私有仓库。传统方式是搭建 HTTP 服务器(如 ChartMuseum)作为 Chart 仓库,通过 helm repo add 添加地址。Chart 打包为 .tgz 文件上传到仓库,helm repo update 刷新本地索引后即可安装。

Helm 3 引入 OCI 协议支持,可以将 Chart 推送到 Harbor、AWS ECR、GitHub Container Registry 等支持 OCI 的镜像仓库,与容器镜像统一管理。使用 helm package 打包 Chart,helm push 推送到 OCI registry,helm pull 按需拉取。这种方式无需额外搭建 Chart 仓库基础设施。

私有仓库支持 RBAC 权限控制,可以限制哪些团队/用户可以推送和拉取特定 Chart。Chart 签名验证(helm package --sign)使用 GPG 密钥对 Chart 进行签名,安装时验证签名确保 Chart 未被篡改。

在 AI 平台场景中,可以将所有推理服务的 Helm Chart 推送到组织私有 OCI registry,配合 CI/CD 流水线自动打包和推送。模型团队只需在 values 中指定 Chart 版本,即可从私有仓库拉取经过验证的部署模板。

AI 推理服务栈的 Helm 一体化部署实践

AI 推理服务栈通常包含模型推理容器、API 网关、Redis 缓存、Prometheus 监控等多个服务。通过 Helm 将这些服务打包为一个 Chart:主 Chart 管理核心推理资源(Deployment、Service、HPA),Redis 和 Prometheus 作为子 Chart 依赖。

values 分层管理环境差异化配置:values.yaml 定义所有参数的默认值,values-dev.yaml 设置开发环境参数(1 副本、无 GPU、单节点 Redis),values-prod.yaml 设置生产环境参数(3 GPU 副本、HPA、Redis 集群)。部署时通过 -f 指定环境文件即可。

部署前使用 helm template 命令本地预览渲染结果,将模板渲染为纯 YAML 输出到终端,无需连接集群即可检查配置正确性。这对于 CI/CD 流水线中的配置验证特别有用:在部署前执行 helm template 并对比预期输出,确保参数变更不会导致配置错误。

集成到 CI/CD 流水线:模型训练完成后触发 CI 流水线,构建推理镜像并推送到镜像仓库,然后执行 helm upgrade --install model-serving ./chart --set image.tag=$CI_COMMIT_SHA --wait。通过 Chart 的 appVersion 和 image.tag 参数化管理镜像版本,每次代码提交自动更新镜像版本并滚动发布。部署失败时执行 helm rollback 自动回退。

yaml16 行
# CI/CD 流水线中的 Helm 部署步骤# 1. 本地预览验证# helm template llm-serving ./chart -f values-prod.yaml # 2. 部署到集群# helm upgrade --install llm-serving ./chart \#   -f values-prod.yaml \#   --set image.tag=$CI_COMMIT_SHORT_SHA \#   --wait --timeout 300s # 3. 验证部署状态# helm status llm-serving# helm history llm-serving # 4. 回滚(如果部署失败)# helm rollback llm-serving

AI 应用场景

AI推理服务栈的参数化部署与环境隔离

阶段:模型部署

解决的问题:AI推理服务需要在不同环境(开发/测试/生产)部署且配置参数不同,手工编写多个YAML文件容易出错且难以维护。

具体案例:为LLM推理服务编写Helm Chart,包含Deployment、Service、HPA、ConfigMap等资源模板。values.yaml定义GPU副本数、模型镜像版本、推理端口等参数。dev环境values-dev.yaml设置1个副本且不启用GPU,prod环境values-prod.yaml设置3个GPU副本并启用HPA。通过helm install llm-serving ./chart -f values-prod.yaml一键部署,切换环境只需更换values文件无需修改模板。

模型版本迭代的Helm CI/CD自动化发布

阶段:模型部署

解决的问题:AI模型从训练完成到上线部署需要自动化且可回滚的交付流程,手动部署无法保证一致性和可追溯性。

具体案例:在GitLab CI中配置Helm部署阶段:模型训练完成后触发CI流水线,构建推理镜像并推送到镜像仓库,然后执行helm upgrade --install model-serving ./chart --set image.tag=$CI_COMMIT_SHORT_SHA --wait。通过Chart的appVersion和image.tag参数化管理镜像版本,每次代码提交自动更新镜像版本并滚动发布。如果部署后健康检查失败,执行helm rollback model-serving自动回退到上一个成功的release版本。

Python 演示

python76 行
import yamlimport jsonfrom dataclasses import dataclass, fieldfrom typing import Dict, List, Optional @dataclassclass HelmValues:    def __init__(self, data: Dict = None):        self._data = data or {}     def get(self, key: str, default=None):        keys = key.split('.')        val = self._data        for k in keys:            if isinstance(val, dict) and k in val:                val = val[k]            else:                return default        return val     def merge(self, other: 'HelmValues') -> 'HelmValues':        result = dict(self._data)        for key, value in other._data.items():            result[key] = value        return HelmValues(result) def render_template(template_str: str, values: HelmValues) -> str:    result = template_str    import re    placeholders = re.findall(r'\{\{\s*\.Values\.(\S+?)\s*\}\}', template_str)    for ph in placeholders:        val = values.get(ph, '')        result = result.replace('{{{{ .Values.{0} }}}}'.format(ph), str(val))    return result def simulate_helm_install(chart_name: str, values_files: List[Dict]) -> Dict:    base_values = HelmValues()    for vf in values_files:        base_values = base_values.merge(HelmValues(vf))     deployment_template = '''apiVersion: apps/v1kind: Deploymentmetadata:  name: {name}spec:  replicas: {{ .Values.replicaCount }}  template:    spec:      containers:      - name: model-server        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"'''     rendered = deployment_template.replace('{name}', chart_name)    rendered = render_template(rendered, base_values)     return {        'release': chart_name,        'values': base_values._data,        'manifest': rendered    } # Demo: multi-environment deploymentbase = {'replicaCount': 1, 'image': {'repository': 'llm-model', 'tag': 'latest'}}dev = {'replicaCount': 1, 'gpu': {'enabled': False}}prod = {'replicaCount': 3, 'gpu': {'enabled': True}, 'image': {'tag': 'v2.1.0'}} print('=== Dev Environment ===')dev_result = simulate_helm_install('llm-serving', [base, dev])print(f"Replicas: {dev_result['values']['replicaCount']}")print(f"Image tag: {dev_result['values']['image']['tag']}") print('\n=== Prod Environment ===')prod_result = simulate_helm_install('llm-serving', [base, prod])print(f"Replicas: {prod_result['values']['replicaCount']}")print(f"Image tag: {prod_result['values']['image']['tag']}")print(f"GPU enabled: {prod_result['values']['gpu']['enabled']}')
目的:模拟 Helm 的 values 合并与模板渲染机制,演示多环境部署时配置覆盖的工作原理
  1. class HelmValues:定义 HelmValues 类封装 values 数据,提供 get 和 merge 方法
  2. def get(self, key: str, default=None):支持点号分隔的路径访问,如 'image.tag' 获取嵌套值
  3. def merge(self, other: 'HelmValues') -> 'HelmValues':实现浅层合并:同级键整体替换,模拟 Helm 的 values 覆盖行为
  4. def render_template(template_str, values):模拟 Go template 渲染:正则匹配 {{ .Values.xxx }} 占位符并替换为实际值
  5. def simulate_helm_install(chart_name, values_files):模拟 helm install:按顺序加载多个 values 文件并合并,然后渲染模板
  6. base_values = base_values.merge(HelmValues(vf))依次合并每个 values 文件,后者覆盖前者的同名键
  7. dev_result = simulate_helm_install('llm-serving', [base, dev])开发环境:base + dev values 合并,1 副本无 GPU
  8. prod_result = simulate_helm_install('llm-serving', [base, prod])生产环境:base + prod values 合并,3 副本启用 GPU,镜像 tag 为 v2.1.0

常见问答

Helm 3 和 Helm 2 的主要区别是什么?
Helm 3 移除了 Tiller 服务端组件,客户端直接与 K8s API Server 交互,简化了架构并提升了安全性。此外 Helm 3 改进了 release 存储(使用 Secret 替代 ConfigMap)、支持 OCI registry、引入了 Chart API v2 等。
helm upgrade 和 helm rollback 的区别是什么?
helm upgrade 使用新的 Chart 或 values 更新现有 release,生成新的 revision;helm rollback 将 release 回退到指定的历史 revision,本质是用历史版本的配置执行一次新的 upgrade。两者都会生成新的 revision 记录。
如何在 Helm 模板中引用子 Chart 的值?
在父 Chart 的 values.yaml 中,以子 Chart 名称作为键传递参数。例如依赖 redis 子 Chart 时,通过 redis: {architecture: replication} 传递。模板中通过 .Values.redis.architecture 引用。global 键下的值对所有子 Chart 可见。

常见误区

误区认为Helm只是一个YAML模板渲染工具,与envsubst或jinja2没有本质区别

正解Helm不仅是模板引擎,更是release生命周期管理器。它维护每次部署的revision版本历史,支持helm rollback回滚到任意历史版本,还管理Chart依赖关系和私有仓库。模板渲染只是其功能的一部分。

为何常见:学习者第一次接触Helm时主要看到模板渲染功能,容易忽略其release管理和版本历史能力。

误区认为values.yaml的合并是深度合并,子键级别的覆盖会保留未覆盖的兄弟键

正解Helm的values合并是浅层合并:父chart覆盖子chart同名的顶级键是整体替换而非深度合并。如果子chart有一个config.data对象,父chart的config.data只设了一个子键,结果是子chart的config.data整个被父chart的替换,而非两个对象的字段合并。

为何常见:其他配置工具(如Kustomize的strategic merge)支持深度合并,学习者把这种预期带到Helm中导致配置被意外覆盖。

误区认为helm upgrade只更新发生变化的K8s资源,不影响未修改的资源

正解helm upgrade执行的是三方合并(3-way strategic merge):对比上次部署的manifest、当前集群实际状态和新manifest。但已删除的K8s资源(如手动删除的Service)Helm不会自动重建,除非使用--force强制重建所有资源或helm uninstall后重新install。

为何常见:学习者期望helm upgrade像kubectl apply一样是声明式的幂等操作,但Helm的三方合并在处理已删除资源时行为与预期不同。

参考来源