1. 从零理解 Helm:为什么它成了 Kubernetes 部署的标配
如果你已经在用 Kubernetes 跑过几个应用,大概率经历过这样的场景:一个简单的 Web 服务,需要写 Deployment、Service、Ingress、ConfigMap、Secret 五六个 YAML 文件,每个文件里还有一堆缩进和字段要反复核对。部署到测试环境跑通了,换到生产环境又要改镜像地址、副本数、资源限制,改完还得重新 apply 一遍。这种重复劳动做多了,人就会开始想:有没有办法把这些配置打包起来,用一个命令就能完成部署和参数替换?
Helm 就是为解决这个问题而生的。你可以把它理解成 Kubernetes 的包管理器,类似 Linux 上的 apt 或 yum,或者 Node.js 生态里的 npm。它把一组相关的 Kubernetes 资源定义打包成一个称为 Chart 的单元,通过模板化和参数化的方式,让同一套配置可以适配不同的环境和需求。你只需要维护一份 Chart,就能在开发、测试、生产等多个环境中复用,改的只是 values 文件里的几个参数。
我最初接触 Helm 的时候,觉得它不过是把 YAML 文件换了个地方放,能有多大价值。但真正在多个项目里用起来之后才发现,它解决的核心问题不是"少写几个文件",而是部署的一致性和可重复性。当你的集群里有几十个微服务,每个服务都有自己的一套资源定义,靠手工维护 YAML 几乎不可能保证不出错。Helm 通过版本化、模板化和回滚机制,把部署这件事变得可控了。
这篇文章会从 Helm 的安装讲起,一步步带你部署一个完整的应用,把 Chart 的结构、模板语法、values 覆盖、版本管理这些关键点都过一遍。不管你是刚接触 Kubernetes 的新手,还是已经用过 kubectl 手动部署过应用的老手,都能从中找到可以直接复用的操作步骤和避坑经验。
2. Helm 3.2.4 的安装路径选择与实操
2.1 为什么选 3.2.4 这个版本
Helm 的版本迭代比较快,3.x 系列和 2.x 系列在架构上有本质区别。2.x 版本依赖集群内的 Tiller 组件,权限模型比较粗糙,而 3.x 彻底去掉了 Tiller,直接使用 kubeconfig 的权限与集群交互,安全性提升了一大截。3.2.4 属于 3.x 早期的一个稳定版本,虽然现在已经有更新的版本,但很多企业内网环境或者特定 Kubernetes 版本(比如 1.16 到 1.18 这个区间)搭配 3.2.4 是比较稳妥的组合。
选择版本时需要考虑两个因素:一是你的 Kubernetes 集群版本,Helm 官方会给出兼容性矩阵,一般来说 Helm 3.2.x 支持 Kubernetes 1.14 以上的版本;二是你的网络环境,如果在内网无法直接拉取官方仓库,就需要提前下载好二进制包。我个人的建议是,如果集群版本不是特别老,尽量用 Helm 3.2.4 以上的版本,因为后续版本在模板函数和错误提示上做了不少改进。
2.2 二进制安装的完整步骤
Helm 的安装方式主要有三种:二进制包直接下载、包管理器安装(如 brew、apt)、源码编译。生产环境最推荐的是二进制安装,因为可控性最强,不依赖额外的包管理工具。
先确认你的操作系统和架构,然后从官方发布页面下载对应的压缩包。以下以 Linux amd64 为例:
# 下载指定版本的二进制包 wget https://get.helm.sh/helm-v3.2.4-linux-amd64.tar.gz # 解压 tar -zxvf helm-v3.2.4-linux-amd64.tar.gz # 将二进制文件移动到系统路径 sudo mv linux-amd64/helm /usr/local/bin/helm # 验证安装 helm version执行helm version后,你应该能看到类似version.BuildInfo{Version:"v3.2.4", GitCommit:"...", GitTreeState:"clean", GoVersion:"go1.13.9"}的输出。如果提示命令找不到,检查/usr/local/bin是否在 PATH 环境变量里。
注意:不要用
sudo helm来执行命令。Helm 3 使用当前用户的 kubeconfig 文件(默认在~/.kube/config),如果用 sudo 切换到了 root 用户,很可能找不到正确的集群配置,导致连接失败。
2.3 配置仓库与网络问题的处理
Helm 3 默认没有添加任何 Chart 仓库,需要手动添加。最常用的是官方的 stable 仓库和 bitnami 仓库:
# 添加官方仓库 helm repo add stable https://charts.helm.sh/stable # 添加 bitnami 仓库(常用中间件 Chart 比较全) helm repo add bitnami https://charts.bitnami.com/bitnami # 更新仓库索引 helm repo update # 查看已添加的仓库 helm repo list这里有个实际中经常遇到的问题:如果网络环境受限,helm repo add和helm repo update可能会超时。这时候有两个处理思路。一是找一台能访问外网的机器,把 Chart 包用helm pull下载成 tgz 文件,再拷贝到内网环境用helm install ./chart.tgz安装。二是搭建一个内部的 Chart 仓库,比如用 ChartMuseum 或者直接放在对象存储上,把常用 Chart 同步进去。我在内网项目里一般会维护一个本地仓库目录,把所有依赖的 Chart 提前拉好,避免部署时临时下载。
另外,helm repo update这个操作要养成习惯,每次安装新 Chart 之前先执行一次,否则可能拉到旧版本的索引,导致找不到某个 Chart 或者版本号对不上。
3. 用 Helm 部署第一个应用:从 Chart 结构到实际运行
3.1 创建一个自定义 Chart 的完整过程
虽然可以直接用现成的 Chart 部署应用,但理解 Chart 的内部结构对后续排查问题至关重要。我们先用helm create生成一个标准骨架:
helm create my-app执行后会生成一个my-app目录,结构如下:
my-app/ ├── Chart.yaml # Chart 的元数据,名称、版本、描述等 ├── values.yaml # 默认配置值 ├── charts/ # 依赖的子 Chart ├── templates/ # 模板文件目录 │ ├── deployment.yaml │ ├── service.yaml │ ├── ingress.yaml │ ├── serviceaccount.yaml │ ├── hpa.yaml │ ├── NOTES.txt # 安装后显示的提示信息 │ └── _helpers.tpl # 可复用的模板片段 └── .helmignore # 打包时忽略的文件这个骨架已经包含了一个完整的 Web 应用所需的基本资源。Chart.yaml里定义了apiVersion、name、version、appVersion等字段,其中version是 Chart 自身的版本,appVersion是应用的版本,两者不要混淆。values.yaml里定义了镜像仓库、标签、副本数、资源限制、服务端口等默认值,模板文件通过{{ .Values.xxx }}的方式引用这些值。
我建议在正式写业务 Chart 之前,先把这个默认骨架部署一遍,看看它生成了哪些资源,这样对模板和 values 的对应关系会有直观感受。
3.2 values.yaml 的参数覆盖逻辑
Helm 的 values 覆盖遵循一套优先级规则,从低到高依次是:Chart 自带的values.yaml、父 Chart 的values.yaml(如果有子 Chart)、用户通过-f指定的 values 文件、通过--set传入的参数。理解这个优先级很重要,因为实际部署时经常需要在不修改 Chart 源码的情况下覆盖某些配置。
举个例子,默认的values.yaml里replicaCount: 1,你想在测试环境用 2 个副本,可以创建一个values-test.yaml:
replicaCount: 2 image: repository: nginx tag: "1.19" resources: limits: cpu: 200m memory: 256Mi requests: cpu: 100m memory: 128Mi然后用helm install my-app ./my-app -f values-test.yaml来安装。如果只是临时改一个参数,用--set replicaCount=3更方便,但要注意--set的值会被解析成字符串,涉及布尔值或数字时可能需要显式转换,比如--set resources.limits.cpu=200m这种带单位的写法是没问题的,但--set enabled=true在某些版本里会被当成字符串 "true" 而不是布尔值 true,这时候用--set-string或者干脆写进 values 文件更稳妥。
提示:
--set的优先级最高,会覆盖 values 文件里的同名配置。调试时可以用helm get values <release-name>查看某个 release 实际生效的 values,用helm get manifest <release-name>查看渲染后的完整 YAML。
3.3 安装、查看与卸载的完整操作链路
假设我们已经准备好了 Chart 和 values 文件,接下来执行安装:
# 安装并指定 release 名称和 namespace helm install my-release ./my-app -f values-test.yaml -n default # 查看 release 列表 helm list # 查看某个 release 的详细状态 helm status my-release # 查看渲染后的 Kubernetes 资源清单 helm get manifest my-release # 查看安装后 NOTES.txt 里的提示信息 helm get notes my-release安装完成后,helm list会显示 release 名称、namespace、修订版本号、状态、Chart 版本和应用版本。状态为deployed表示安装成功。如果状态是failed,可以用helm status看详细错误,或者用kubectl get events查看集群层面的事件。
卸载操作很简单:
helm uninstall my-releaseHelm 3 的卸载默认会删除该 release 创建的所有资源,但有一个细节需要注意:通过helm install创建的 PersistentVolumeClaim 默认也会被删除,如果你的应用有状态数据,卸载前一定要确认数据已经备份或者 PVC 的回收策略是 Retain。
3.4 部署一个带 Ingress 的完整 Web 应用
为了把前面的知识点串起来,我们部署一个稍微完整一点的场景:一个 Nginx 应用,带 Service 和 Ingress,通过域名访问。
先修改values.yaml中的关键配置:
replicaCount: 2 image: repository: nginx tag: "1.19" pullPolicy: IfNotPresent service: type: ClusterIP port: 80 ingress: enabled: true annotations: kubernetes.io/ingress.class: nginx hosts: - host: my-app.example.com paths: - path: / pathType: Prefix然后执行安装:
helm install web-demo ./my-app -f values-test.yaml安装完成后,用kubectl get ingress查看 Ingress 资源是否创建成功,确认 ADDRESS 字段有值。如果 Ingress 控制器配置正确,通过域名就能访问到应用。这里常见的坑是 Ingress 的pathType字段,在 Kubernetes 1.18 之前这个字段是可选的,1.18 之后变成了必填,如果 Chart 模板里没写,渲染出来的 YAML 在 apply 时会被 API Server 拒绝。遇到这种情况,要么升级 Chart 模板,要么在 values 里显式指定。
4. 模板语法与调试:让 Chart 真正为你所用
4.1 模板中的变量、函数与流程控制
Helm 模板基于 Go template 语言,但额外扩展了很多 Kubernetes 相关的函数。最基础的用法是引用 values 中的值:
metadata: name: {{ .Release.Name }}-deployment spec: replicas: {{ .Values.replicaCount }} template: spec: containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"这里用到了几个内置对象:.Release包含 release 的名称、namespace、修订号等信息;.Chart包含 Chart.yaml 里的元数据;.Values就是 values 文件里的内容。除了这些,还有.Capabilities(集群能力信息)、.Template(当前模板文件的信息)等。
流程控制方面,if/else、with、range是最常用的。比如根据ingress.enabled决定是否渲染 Ingress 资源:
{{- if .Values.ingress.enabled }} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ .Release.Name }}-ingress spec: rules: {{- range .Values.ingress.hosts }} - host: {{ .host }} http: paths: {{- range .paths }} - path: {{ .path }} pathType: {{ .pathType }} backend: service: name: {{ $.Release.Name }}-service port: number: {{ $.Values.service.port }} {{- end }} {{- end }} {{- end }}注意range循环里访问外层变量需要用$符号,比如$.Release.Name,因为range会改变上下文。这个细节在写嵌套循环时特别容易出错,我第一次写的时候就在这卡了半天,渲染出来的 YAML 里全是空值。
4.2 用 helm template 和 --dry-run 提前发现问题
模板写完之后,不要急着 install,先用helm template在本地渲染一遍:
helm template my-release ./my-app -f values-test.yaml这个命令会把所有模板渲染成最终的 YAML 输出到终端,不跟集群交互。你可以直接看到生成的资源清单,检查字段是否正确、缩进是否对齐。如果渲染报错,错误信息会指出具体是哪个模板文件的哪一行出了问题。
另一个有用的命令是helm install --dry-run --debug:
helm install my-release ./my-app -f values-test.yaml --dry-run --debug--dry-run会模拟安装过程,--debug会输出详细的调试信息,包括渲染后的 manifest 和计算出的 values。这两个命令配合使用,基本能在安装前发现 90% 的模板问题。
我个人的习惯是,每次修改模板后都先跑一遍helm template,确认输出符合预期再执行真正的安装。这样能避免因为模板错误导致 release 处于 failed 状态,虽然 Helm 支持回滚,但反复失败再回滚会浪费不少时间。
4.3 常见渲染错误与排查思路
模板渲染阶段最常见的错误有三类。第一类是字段引用错误,比如.Values.image.tag写成了.Values.image.version,渲染时不会报错,但生成的值是空的,最终 apply 到集群时才会因为镜像地址不完整而失败。这类问题用helm template输出后肉眼检查最快。
第二类是缩进问题。YAML 对缩进极其敏感,模板里的{{-和-}}用来控制空白字符,用错了会导致生成的 YAML 缩进错乱。比如{{- if ... }}会吃掉前面的换行和空格,如果位置不对,可能把上一行的内容也吃掉了。排查这类问题时,把渲染结果复制到支持 YAML 语法高亮的编辑器里,缩进错误一眼就能看出来。
第三类是类型不匹配。比如某个字段期望是整数,但 values 里写成了字符串,或者反过来。Kubernetes 的 API Server 对类型校验比较严格,这类错误在 apply 阶段会明确报出来,根据错误信息定位到具体字段即可。
提示:如果
helm install失败后 release 处于 failed 状态,直接重新 install 同名 release 会报"already exists"。这时候要么先helm uninstall再重装,要么用helm upgrade来更新。我一般倾向于先 uninstall 清理干净,避免残留资源干扰。
5. 版本管理与回滚:Helm 的杀手锏功能
5.1 release 的修订版本机制
Helm 每次对同一个 release 执行 install、upgrade、rollback 操作,都会生成一个新的修订版本(revision)。这个版本号是递增的,helm history可以查看完整的修订历史:
helm history my-release输出会显示每个修订的版本号、更新时间、状态、Chart 版本、应用版本和描述。状态包括deployed(当前生效)、superseded(被新版本取代)、failed(操作失败)等。这个机制的价值在于,任何时候你都可以回到之前的某个修订版本。
比如你升级了一个新版本,发现有问题,一条命令就能回滚:
helm rollback my-release 1这里的1是修订版本号,表示回滚到第 1 版。回滚操作本身也会生成一个新的修订版本,所以历史记录是线性的,不会丢失任何一次操作。
5.2 upgrade 时的参数传递与注意事项
升级操作使用helm upgrade:
helm upgrade my-release ./my-app -f values-prod.yaml如果只想改一个参数,可以用--set:
helm upgrade my-release ./my-app --set image.tag=1.20这里有一个容易忽略的点:helm upgrade默认会复用上一次的 values,如果你这次只传了-f values-prod.yaml,那么上一次通过--set传入的参数会被保留还是覆盖?答案是:--set的值不会自动保留,每次 upgrade 都需要重新指定,除非你用--reuse-values参数。但--reuse-values也有坑,它会把上一次的所有 values 合并进来,如果 Chart 本身有更新,新的默认值可能被旧值覆盖。我的建议是,把所有需要覆盖的参数都写进 values 文件,用-f传递,避免依赖--set和--reuse-values。
另一个注意事项是--atomic参数。加上这个参数后,如果 upgrade 失败,Helm 会自动回滚到上一个成功的版本:
helm upgrade my-release ./my-app -f values-prod.yaml --atomic --timeout 5m--timeout指定等待资源就绪的超时时间,默认是 5 分钟。对于启动比较慢的应用,比如需要初始化数据库的 Java 服务,可以适当调大这个值。
5.3 用 helm diff 插件预览变更内容
原生的 Helm 没有提供"升级前预览差异"的功能,但有一个社区插件helm-diff可以做到。安装插件:
helm plugin install https://github.com/databus23/helm-diff安装后,用helm diff upgrade就能看到本次升级会改变哪些资源:
helm diff upgrade my-release ./my-app -f values-prod.yaml输出会用颜色标注新增、修改、删除的行,非常直观。这个插件在生产环境升级前特别有用,能避免因为误改 values 导致意外变更。我在团队里推行的一个规范就是:任何生产环境的 upgrade 操作,必须先跑一遍helm diff,确认变更范围符合预期后再执行。
6. 实际部署中积累的经验与避坑清单
6.1 资源命名冲突与 namespace 隔离
Helm 创建的资源名称通常由 release 名称加后缀组成,比如my-release-my-app。如果两个 release 名称相同但部署在不同的 namespace,资源不会冲突。但如果同一个 namespace 里有两个 release 名称相同,就会报冲突。所以给 release 命名时最好带上环境标识,比如web-demo-test、web-demo-prod,这样既避免了冲突,也方便用helm list过滤查看。
另外,有些 Chart 模板里会硬编码资源名称,不包含 release 名称,这种 Chart 在同一 namespace 里只能部署一次。遇到这种情况,要么修改模板,要么用不同的 namespace 隔离。我在部署一些第三方中间件 Chart 时就遇到过这个问题,两个环境想部署在同一个 namespace 下,结果 Service 名称冲突,最后只能拆到不同 namespace。
6.2 镜像拉取失败的排查顺序
部署应用时最常见的失败原因就是镜像拉取失败。排查顺序建议如下:先用kubectl describe pod <pod-name>查看 Events 区域,确认是ImagePullBackOff还是ErrImagePull。如果是私有仓库,检查 Secret 是否创建、ServiceAccount 是否引用了正确的 imagePullSecrets。如果是公开仓库,检查镜像地址和标签是否存在,有时候标签写成了latest但仓库里根本没有这个标签。
还有一个容易被忽略的点:如果 Kubernetes 节点配置了私有仓库的镜像加速或者代理,但 Helm Chart 里的镜像地址是完整的公网地址,可能会绕过加速直接走公网,导致拉取超时。这种情况下需要确认节点的容器运行时配置,或者把镜像地址改成加速器支持的格式。
6.3 Chart 版本与应用版本的区分管理
Chart.yaml里的version和appVersion是两个独立的概念。version是 Chart 本身的版本,每次修改模板或 values 结构都应该递增;appVersion是打包在 Chart 里的应用版本,通常对应镜像的标签。很多团队在 CI/CD 流程里会自动更新这两个字段,但如果不注意区分,可能会出现 Chart 版本没变但应用版本变了的情况,导致helm history里看不出差异。
我的做法是:version遵循语义化版本,每次 Chart 内容有变更就递增;appVersion与镜像标签保持一致,由 CI 流水线在构建镜像后自动写入。这样通过helm list就能同时看到 Chart 版本和应用版本,排查问题时能快速定位是配置变更还是应用变更。
6.4 清理无用 release 与存储后端的选择
Helm 3 默认把 release 的元数据以 Secret 的形式存储在 Kubernetes 集群里(sh.helm.release.v1.<release-name>.v<revision>)。如果频繁安装卸载,这些 Secret 会越积越多。虽然单个 Secret 不大,但数量多了也会占用 etcd 空间。定期用helm list --all查看所有 release(包括 failed 状态的),把不再需要的清理掉。
如果集群规模比较大,可以考虑把 Helm 的存储后端从 Secret 换成 ConfigMap 或者 SQL 数据库。通过设置环境变量HELM_DRIVER来切换,比如export HELM_DRIVER=configmap。不过对于大多数场景,默认的 Secret 存储已经够用了,没必要折腾。
7. 从单机部署到多环境复用的进阶思路
7.1 用 umbrella chart 管理微服务组合
当你的应用拆成了多个微服务,每个服务都有自己的 Chart,逐个安装会很繁琐。这时候可以用一个 umbrella chart(也叫 parent chart)把子 Chart 组合起来。在 umbrella chart 的Chart.yaml里声明依赖:
dependencies: - name: service-a version: "1.0.0" repository: "file://../service-a" - name: service-b version: "1.0.0" repository: "file://../service-b"然后执行helm dependency update把子 Chart 下载到charts/目录,再统一安装。这样一条命令就能部署整套微服务,values 文件里可以分别覆盖每个子 Chart 的配置。这种方式的优势是部署顺序和依赖关系由 Helm 统一管理,不用手工控制先后。
7.2 环境差异的配置分层策略
多环境部署的核心问题是:哪些配置应该共用,哪些应该按环境区分。我的经验是把 values 文件拆成三层:values.yaml放所有环境通用的默认值,values-<env>.yaml放环境特有的配置(如副本数、资源限制、域名),敏感信息(如密码、密钥)单独放在一个不纳入版本控制的文件里,通过 CI/CD 的变量注入。
安装时用多个-f参数叠加:
helm install my-release ./my-app -f values.yaml -f values-prod.yaml -f values-secret.yaml后面的文件会覆盖前面的同名配置。这种分层方式让配置的维护变得清晰,改通用配置只动一个文件,改环境配置也只动对应的文件,不会互相干扰。
7.3 把 Helm 集成到 CI/CD 流水线
在 CI/CD 里使用 Helm,关键是要处理好 kubeconfig 的注入和 release 名称的生成。通常的做法是在流水线里根据分支名或环境变量生成 release 名称,比如my-app-${CI_COMMIT_REF_NAME},然后执行helm upgrade --install。--install参数的作用是:如果 release 不存在就安装,存在就升级,这样一条命令就能覆盖首次部署和后续更新两种场景。
helm upgrade --install my-app-${ENV} ./my-app \ -f values.yaml \ -f values-${ENV}.yaml \ --namespace ${ENV} \ --create-namespace \ --atomic \ --timeout 10m--create-namespace在 namespace 不存在时自动创建,省去了额外步骤。--atomic保证失败自动回滚。这套组合拳在流水线里跑起来很稳,基本不需要人工干预。
7.4 一些不太常见但很有用的 helm 子命令
除了常用的 install、upgrade、rollback、uninstall,Helm 还有几个子命令在实际工作中很有帮助。helm show values <chart>可以查看某个 Chart 的默认 values,不用先下载下来。helm show chart <chart>查看 Chart 的元数据。helm search repo <keyword>在已添加的仓库里搜索 Chart。helm pull <chart> --untar把 Chart 下载并解压到本地,方便查看模板源码。
还有一个helm lint命令,用来检查 Chart 的语法和规范问题:
helm lint ./my-app它会检查 Chart.yaml 是否完整、模板是否有语法错误、values 是否可解析等。在 CI 流水线里加一步helm lint,能在早期发现很多低级错误。
8. 写在最后:一些踩坑之后的真实体会
Helm 这个工具,入门门槛不高,但要用得顺手,需要在实际部署中不断积累经验。我印象比较深的一次是升级一个生产环境的 release,values 文件里漏了一个必填参数,helm upgrade执行后 Pod 一直起不来,因为--atomic没加,Helm 也没有自动回滚,最后手动helm rollback才恢复。从那以后,我在生产环境的 upgrade 命令里一定会加上--atomic和--timeout,宁可多等几分钟,也不让失败的版本留在集群里。
另一个体会是关于 values 文件的管理。早期我习惯用--set传参数,觉得方便,但后来发现一旦参数多了,命令行会变得很长,而且容易漏掉某个参数导致配置不一致。现在我的做法是,所有非敏感的配置都写进 values 文件,用-f传递,--set只用于临时调试或者 CI 里注入动态生成的变量。这样每次部署的配置都有据可查,回滚时也能准确复现之前的配置。
还有一点是关于 Chart 的版本控制。Chart 本身也是代码,应该纳入 Git 管理,每次变更都有 commit 记录。配合helm history和helm diff,基本能做到部署过程的可追溯和可审计。对于团队协作来说,这套流程能减少很多沟通成本,谁改了什么、什么时候改的、影响范围是什么,一目了然。
如果你刚开始用 Helm,建议先从部署一个简单的应用入手,把 install、upgrade、rollback、uninstall 这几个操作跑一遍,感受一下 release 和 revision 的概念。然后再逐步深入模板语法和 values 覆盖,最后再考虑集成到 CI/CD 和 umbrella chart 这些进阶用法。一步一步来,比一上来就啃文档要有效得多。