Helm 与应用打包——从 YAML 到可复用制品
在 Kubernetes 上部署一个生产级应用,往往需要几十个甚至上百个 YAML 文件:Deployment、Service、ConfigMap、Ingress、RBAC 规则、HorizontalPodAutoscaler……这些文件之间存在依赖关系,不同环境(开发、预发、生产)需要不同参数,手工管理极易出错。Helm 正是为了解决这个问题而生:把一组相关的 Kubernetes 资源打包成一个可版本化、可参数化、可共享的制品(Chart),并提供安装、升级、回滚、卸载的生命周期管理。
Helm 的本质是"带版本历史的 YAML 打包器"。它不追踪资源的真实运行状态(那是 Operator 的职责),只记录"这次 install/upgrade 渲染出了哪些 manifest,交给 Kubernetes 执行"。Release 状态存储在集群内的 Secret 对象中(key 为 helm.sh/release.v1),每次升级都新增一个版本,rollback 本质上是重新 apply 旧版本的 manifest 集合。
理解 Helm 的价值,需要同时理解它的边界。Helm 擅长初始部署和简单升级,不擅长复杂的 Day-2 运维(数据迁移、主从切换、证书续签)——那类场景更适合 Operator。Helm 和 Operator 不是竞争关系:很多 Operator 本身就是通过 Helm Chart 来安装的。
数据流全景
1 | |
Chart 结构解析
目录布局
一个典型的 Chart 目录包含:
1 | |
Chart.yaml 中的 version 是 Chart 版本(打包制品的版本),appVersion 是被打包应用的版本(如 nginx:1.25.0),两者独立演化。dependencies 字段列出依赖的其他 Chart(如 mysql、redis),执行 helm dependency update 后下载到 charts/ 目录。
values.yaml 与参数覆盖
values.yaml 定义参数的默认值和类型结构,是 Chart 的"接口文档"。Helm 的参数合并优先级从低到高:
- Chart 内嵌的 values.yaml(默认值)
- 父 Chart 的 values.yaml(subchart 覆盖)
- 用户提供的
-f custom-values.yaml(可多个,后面的优先) - 命令行
--set key=value(最高优先级)
需要注意的是,Helm 的 values 合并是浅合并(shallow merge),不是深度合并(deep merge)。如果用 --set 覆盖一个嵌套对象中的某个字段,该对象的其他字段不会自动保留——必须显式传递所有需要的字段,或用 -f 传递完整的 values 文件。
Go template + Sprig 函数库
Helm 使用 Go 的 text/template 引擎,并集成了 Sprig 函数库(提供 100+ 实用函数)。常见用法:
1 | |
nindent 和 indent 是处理多行 YAML 缩进的核心函数。quote 确保字符串值被正确引用。{{- 和 -}} 中的横线控制空白符裁剪,避免渲染结果产生多余的空行。
Release 生命周期
install → upgrade → rollback → uninstall
1 | |
Release 存储机制
Helm 3 把 Release 状态存储在 Secret 中(Helm 2 用 Tiller 进程存储,Helm 3 去掉了 Tiller)。每个 Secret 的 label 为 owner=helm,name 格式为 sh.helm.release.v1.<release-name>.v<revision>,data 字段包含 gzip 压缩后的 Release 信息(包括完整的渲染后 manifest)。
这意味着:rollback 不依赖外部状态,只需读取对应版本的 Secret,重新 apply 其中记录的 manifest 集合。同时也意味着:Helm 的 Secret 会随版本积累占用 etcd 空间,需要配置 --history-max 限制保留的历史版本数。
Hooks:在生命周期节点执行 Job
Helm Hooks 允许在 install/upgrade/delete 等操作的前后执行额外的 Kubernetes Job:
pre-install:在 manifest 渲染完成后、资源安装前执行(常用于数据库初始化)post-install:资源安装后执行(常用于冒烟测试)pre-upgrade:升级前执行(常用于数据库 schema 迁移)pre-delete:卸载前执行(常用于数据备份)
Hook 资源本身也是 Kubernetes 对象(通常是 Job),通过 annotation helm.sh/hook 标记,Helm 在对应时间点 apply 并等待 Job 完成。
OCI Registry 与 Chart Museum
Helm 3 支持把 Chart 作为 OCI artifact 存储到标准的 OCI 兼容 Registry(如 Docker Hub、ECR、GCR、Harbor):
1 | |
Chart Museum 是专为 Helm Chart 设计的 HTTP 服务器,实现了 Helm repository 协议,适合私有部署场景。OCI registry 是更现代的选择,与镜像仓库基础设施复用,不需要额外维护 Chart Museum。
可运行实验
1 | |
实验结果映射到 K8s 对象
helm history mynginx 中的每一行对应一个 Secret 对象,格式为 sh.helm.release.v1.mynginx.v<N>。kubectl get secret sh.helm.release.v1.mynginx.v1 -o jsonpath='{.data.release}' | base64 -d | gunzip | jq . 可以看到完整的 Release 元数据,包括渲染后的所有 manifest。
helm template 的输出就是 Helm 渲染后、kubectl apply 前的 manifest 集合,可以用于 GitOps(把渲染结果提交到 Git,由 Argo CD 或 Flux 负责 apply),规避了 Helm 在集群内运行的权限问题。
helm rollback mynginx 1 实际上是找到 sh.helm.release.v1.mynginx.v1 中记录的 manifest 集合,重新 apply,并创建新的 sh.helm.release.v1.mynginx.v3(版本号继续递增)。rollback 不会"撤销"历史,而是创建新的前进版本。
Chart 设计的工程实践
命名模板与代码复用
_helpers.tpl 文件中定义的命名模板是 Chart 代码复用的核心机制。kubebuilder 生成的 Chart 脚手架通常包含 chart.fullname、chart.labels、chart.selectorLabels 等标准命名模板,在所有资源的 metadata 中统一引用:
这套标准 label 方案(app.kubernetes.io/*)是 Kubernetes 推荐的应用标识规范,kubectl、Helm、Argo CD 等工具都能识别这些 label 做资源关联。
values schema 验证
Helm 3.1+ 支持通过 values.schema.json(JSON Schema 格式)对 values.yaml 做类型和约束验证。用户传入类型错误的参数(如把整数值传给字符串字段)时,helm install 在客户端就报错,不会到达集群。
1 | |
values.schema.json 是 Chart 接口文档的机器可读版本,IDE 可以基于它提供自动补全和错误提示。
Subchart 与全局 values
当一个 Chart 依赖多个 subchart(在 charts/ 目录下)时,父 Chart 的 values.yaml 可以通过 subchart 名称作为 key 来覆盖 subchart 的默认值:
1 | |
global 是特殊的顶层 key,父 Chart 设置的 global.* 值会自动传递到所有 subchart,无需逐个覆盖。常用于传递镜像仓库前缀、imagePullSecrets、环境标识等跨 chart 共享的配置。
Helm 与 GitOps 的结合
在 GitOps 工作流中,Helm 通常作为模板引擎,而不是直接控制 apply 的工具:
Argo CD 的 Helm 模式:Argo CD 读取 Git 仓库中的 Chart 和 values 文件,在集群侧执行 helm template 渲染,然后把渲染结果与集群实际状态对比(diff),由 Argo CD 负责 apply。Helm 的 Release Secret 不由 Argo CD 管理,也不需要 helm install 命令。
这种模式的优势是:Argo CD 能检测到 Git 中的 values 变更和集群中手动修改之间的 drift,自动或手动 sync 回 Git 定义的状态。Helm 直接操作时,如果有人在 Helm 之外修改了资源,Helm 不会检测到这个 drift。
Helm diff 插件
helm-diff 是社区插件,在执行 helm upgrade 前展示本次升级会产生哪些 Kubernetes 资源变更,类似于 terraform plan:
1 | |
输出格式与 kubectl diff 类似,逐字段显示新旧 manifest 的差异。在生产环境做变更前用 helm diff 预览,可以发现意外的副作用——例如修改了一个 values 参数,却导致多个不相关的资源被重建(常见于 ConfigMap hash 作为 Deployment annotation 的场景)。
Helm 测试与 CI 集成
helm lint 与 helm test
helm lint 在本地检查 Chart 的语法和结构问题:模板渲染错误、必填字段缺失、values.yaml 格式问题。这是 CI 流水线中 Chart 变更后的第一道检查,在 push 到 Registry 前执行。
helm test 运行 Chart 中标注了 helm.sh/hook: test 的 Pod,验证 Release 安装后的基本功能。测试 Pod 运行完毕后,Helm 检查退出码:0 表示通过,非 0 表示失败。这类似于 smoke test,例如 nginx Chart 的 test Pod 发起一次 HTTP 请求验证 nginx 正常响应。测试结果存储在 Pod 对象中,helm test 命令结束后 Pod 保留(用于检查日志),下次运行前需手动清理。
多环境 values 管理
实际项目中,通常为不同环境维护单独的 values 文件:
1 | |
通过 -f values-prod.yaml 在安装时选择环境,Chart 本身不包含环境差异。这种模式让 Chart 版本(制品)与部署配置(环境参数)分离,Chart 版本升级不影响环境参数,环境参数变更不产生新的 Chart 版本。
模式提炼
Helm 是"带版本历史的 YAML 打包器",而不是配置管理系统,也不是 Operator。它的能力边界:能做参数化渲染、版本历史、rollback;不能做持续调谐、状态感知、Day-2 运维自动化。
values.yaml 是 Chart 的公开接口,应该只暴露真正需要用户关心的参数,隐藏实现细节。过度暴露参数(把所有 Kubernetes 字段都参数化)会导致 values.yaml 比原始 YAML 更难维护。
helm template + GitOps 是 Helm 在大规模场景的常见用法:Helm 负责渲染,Argo CD/Flux 负责 apply 和状态对比。这种模式把 Helm 的模板能力和 GitOps 的审计能力结合,规避了 Helm 直接 apply 时权限过大的问题。
工程迁移表
| 传统模式 | Helm 等价 | 关键差异 |
|---|---|---|
| Maven POM + 依赖管理 | Chart.yaml + dependencies | Chart 打包的是 K8s manifest,而非代码 |
| npm package + semver | Chart version + appVersion | 两个独立版本号(制品版本 vs 应用版本) |
| Ansible playbook | Helm Chart + hooks | Helm 无 idempotent 保证,Ansible 有 |
| Terraform module | Helm Chart | Terraform 追踪真实状态,Helm 不追踪 |
| Docker Compose file | Helm Chart | Helm 面向 K8s 集群,Compose 面向单机 |
常见误解
误解一:Helm 会跟踪资源的真实状态
Helm 只记录"这次 apply 了哪些 manifest",不监控实际运行状态。如果有人在 Helm 之外直接修改了 Deployment 的副本数,helm status 看到的仍然是 release 的版本信息,不会反映实际状态的偏差。helm upgrade 会用新渲染的 manifest 覆盖手动修改,但中间状态 Helm 并不知晓。这个"drift detection"是 GitOps 工具(Argo CD、Flux)的职责,不是 Helm 的。
误解二:values.yaml 支持深度合并
Helm 的 values 合并是浅合并。如果 values.yaml 中定义了:
1 | |
然后用 --set image.tag=1.25 覆盖,结果是 image.tag=1.25,其他字段正常保留——这是因为 --set 只修改指定的叶子节点。但如果用 -f custom.yaml 传入:
1 | |
结果是整个 image 对象被替换为 {tag: 1.25},repository 和 pullPolicy 丢失。这是 Helm values 合并最常见的踩坑点。
误解三:Helm 3 还需要 Tiller
Helm 2 的架构依赖 Tiller(一个运行在集群内的服务端组件),有严重的安全问题(Tiller 通常拥有 cluster-admin 权限)。Helm 3 于 2019 年彻底移除了 Tiller,改为客户端直接用当前用户的 kubeconfig 权限与 API Server 通信。升级到 Helm 3 后,Tiller 相关的安全顾虑已不存在,权限粒度由 kubeconfig 用户的 RBAC 规则决定。
练习
-
用
helm create mychart初始化一个 Chart 骨架,修改values.yaml添加一个自定义参数(如greeting: hello),在templates/configmap.yaml中引用这个参数,用helm template验证渲染结果,再用helm install安装到本地集群,查看生成的 ConfigMap。 -
在练习 1 的基础上,添加一个
pre-installhook(一个输出"database initialized"的 Job),安装时观察 Job 执行情况,理解 Helm 如何等待 hook Job 完成后才继续安装其他资源。 -
用
helm install安装 bitnami/wordpress,然后用helm get manifest myrelease查看完整的渲染后 manifest,再用kubectl get secrets -l owner=helm找到对应的 Release Secret,用 base64 解码并解压,查看 Release 元数据的完整结构。最后执行helm uninstall,确认所有资源被清理。
系列导航
- 00 核心概念导读
- 01 API Server 与声明式 API
- 02 etcd 与持久化
- 03 控制器模式与 Informer 机制
- 04 Pod 生命周期
- 05 调度器深入
- 06 kubelet 与容器运行时
- 07 网络模型与 CNI
- 08 Service 与 kube-proxy
- 09 Ingress 与 Gateway API
- 10 存储体系
- 11 配置与密钥管理
- 12 RBAC 与安全模型
- 13 资源管理与自动伸缩
- 14 CRD 与 Operator 模式
- 15 Helm 与应用打包(本篇)
- 16 可观测性
- 17 生产集群运维
