1. 为什么 Helm 是 Kubernetes 应用部署的“包管理器”
1.1 从手工 YAML 到 Helm Chart 的必然选择
如果你接触过 Kubernetes 一段时间,一定经历过这样的场景:一个稍微完整点的应用,需要 Deployment、Service、ConfigMap、Secret、Ingress、HPA 等一堆资源对象,每个对象少则十几行 YAML,多则上百行。手工维护这些文件,改一个镜像版本要翻好几个文件,换个环境要复制一整套目录再逐个改 namespace 和镜像地址,稍不留神就漏改一个字段,Pod 起不来还得从头排查。
Helm 解决的就是这个问题。它把一组相关的 Kubernetes 资源打包成一个Chart,用Values文件做参数化配置,用Release记录每次部署的版本。你可以把它理解成 Linux 下的 apt 或者 yum,只不过安装的不是系统软件,而是一整套 Kubernetes 应用。安装、升级、回滚、卸载,一条命令搞定。
我刚开始用 Kubernetes 的时候也觉得 Helm 是“多此一举”,觉得直接写 YAML 更直观。直到有一次需要把同一套应用部署到三个不同环境,手工改配置改到怀疑人生,才真正体会到 Helm 的价值。从那以后,凡是超过三个资源对象的应用,我一律用 Helm 管理。
1.2 本文适合哪些人阅读
这篇内容面向的是已经对 Kubernetes 有基本了解、知道 Pod、Service、Deployment 是什么,但还没系统用过 Helm 的朋友。我会从 Helm 的安装讲起,一直到一个完整应用的部署、验证、升级和回滚,把中间容易踩的坑都标出来。如果你是完全零基础,建议先花半小时了解一下 kubectl 的基本用法和 YAML 的基本语法,再来看这篇会顺畅很多。
文章里涉及的所有命令和配置,都是我在实际环境里反复验证过的。版本号方面,Helm 3 和 Helm 2 差异很大,本文以 Helm 3 为主,Helm 2 的用法我会在必要的地方提一下区别,但不再展开,因为 Helm 2 已经停止维护了。
2. Helm 安装前的环境准备与版本选择
2.1 确认 Kubernetes 集群状态
装 Helm 之前,第一件事是确认你的 Kubernetes 集群是健康的。Helm 本质上就是一个客户端工具,它通过 kubeconfig 里的配置去连接集群的 API Server,把渲染好的 YAML 提交上去。所以集群本身有问题的话,Helm 也帮不了你。
先用下面这几条命令确认集群状态:
kubectl cluster-info kubectl get nodes kubectl get pods --all-namespacescluster-info会告诉你 API Server 的地址,get nodes看所有节点是不是 Ready 状态,get pods --all-namespaces看系统组件有没有异常。如果节点是 NotReady,或者 kube-system 里有 Pod 一直 CrashLoopBackOff,先把这些问题解决了再装 Helm,不然你会分不清是集群的问题还是 Helm 的问题。
另外确认一下 kubectl 的版本和集群版本是否匹配。一般来说 kubectl 版本和集群版本相差不超过一个小版本比较稳妥。用kubectl version --short可以同时看到客户端和服务端版本。
2.2 Helm 3 与 Helm 2 的核心差异
Helm 3 相比 Helm 2 最大的变化是去掉了 Tiller 组件。Helm 2 的时代,服务端要跑一个叫 Tiller 的 Pod,helm 客户端把请求发给 Tiller,Tiller 再去操作集群。这带来一个很大的安全问题:Tiller 默认拥有集群管理员权限,任何能访问 Tiller 的人都能对集群做任何操作。Helm 3 直接把这个组件砍掉了,客户端直接用 kubeconfig 的权限操作集群,权限模型跟 kubectl 完全一致,安全性和易用性都好了很多。
另一个变化是 Release 的存储方式。Helm 2 把 Release 信息存在 ConfigMap 里,Helm 3 默认存在 Secret 里,而且存在 Release 所在的 namespace 下,不再需要全局的 Tiller namespace。这意味着你在哪个 namespace 部署应用,Release 记录就在哪个 namespace,隔离性更好。
还有一点是 Chart 的依赖管理。Helm 2 用 requirements.yaml,Helm 3 统一用 Chart.yaml 里的 dependencies 字段。如果你在网上找到一些老教程,看到 requirements.yaml,那基本是 Helm 2 时代的产物,照着做可能会报错。
2.3 三种安装方式的选择与对比
Helm 的安装方式主要有三种:包管理器安装、二进制安装、脚本安装。我分别说一下适用场景。
包管理器安装适合本地开发环境。macOS 用 Homebrew,Windows 用 Chocolatey 或 Scoop,Linux 用 snap 或者系统自带的包管理器。优点是升级方便,一条命令搞定。缺点是版本可能不是最新的,而且有些包管理器里的 Helm 版本更新滞后。
二进制安装适合生产环境或者对版本有严格要求的场景。直接从官方发布页面下载对应平台的压缩包,解压后把 helm 可执行文件放到 PATH 里就行。这种方式最干净,不依赖任何包管理器,版本完全可控。
脚本安装适合快速体验。官方提供了一个安装脚本,一行命令就能装好。但我不建议在生产环境用这种方式,因为脚本的行为不够透明,出了问题不好排查。
我个人习惯是:本地开发用 Homebrew,服务器上用二进制安装。下面重点讲二进制安装,因为这是最通用、最可控的方式。
2.4 二进制安装 Helm 的完整步骤
先确定你要安装的 Helm 版本。访问 Helm 的官方发布页面,找到最新的稳定版本号。假设我们要安装的是 v3.14.0(具体版本号请以你实际看到的为准),操作步骤如下。
第一步,下载对应平台的压缩包。Linux amd64 架构:
wget https://get.helm.sh/helm-v3.14.0-linux-amd64.tar.gzmacOS amd64 架构:
wget https://get.helm.sh/helm-v3.14.0-darwin-amd64.tar.gzWindows 用户下载 zip 包,解压后把 helm.exe 放到 PATH 里。
第二步,解压并移动可执行文件:
tar -zxvf helm-v3.14.0-linux-amd64.tar.gz sudo mv linux-amd64/helm /usr/local/bin/helm sudo chmod +x /usr/local/bin/helm第三步,验证安装:
helm version如果输出类似version.BuildInfo{Version:"v3.14.0", ...}的内容,说明安装成功。
注意:如果你之前装过 Helm 2,先把旧的 helm 二进制删掉或者重命名,避免 PATH 里两个 helm 冲突。用
which helm确认当前用的是哪个。
3. Helm 核心概念与工作原理拆解
3.1 Chart、Release、Repository 三者的关系
Helm 里有三个核心概念,理解了它们,后面所有操作都会变得清晰。
Chart是应用的打包格式,就是一个目录,里面包含了一组模板文件和默认配置。你可以把它类比成 Docker 的镜像,只不过镜像打包的是文件系统,Chart 打包的是 Kubernetes 资源定义。一个 Chart 可以包含多个子 Chart,形成依赖关系。
Release是 Chart 在集群里的一次实例化。同一个 Chart 可以用不同的配置部署多次,每次部署就是一个 Release。比如你在 dev 和 prod 两个 namespace 各部署一次,就是两个 Release。Release 有名字,有版本号,有状态,可以升级也可以回滚。
Repository是 Chart 的仓库,用来存放和分发 Chart。你可以自己搭建私有仓库,也可以用公共仓库。最常用的是 Artifact Hub,上面有大量官方和社区维护的 Chart。添加仓库用helm repo add,更新仓库索引用helm repo update。
这三者的关系可以这样理解:Repository 是应用商店,Chart 是安装包,Release 是安装到机器上的那个软件实例。
3.2 Helm 模板渲染机制详解
Helm 最核心的能力是模板渲染。Chart 里的模板文件本质上是 Go template 语法,加上 Sprig 函数库和 Helm 自己的内置对象。渲染的时候,Helm 把 Values 里的值注入到模板里,生成最终的 Kubernetes YAML。
举个例子,模板里写:
replicas: {{ .Values.replicaCount }}Values 文件里写:
replicaCount: 3渲染后就是:
replicas: 3这看起来简单,但实际用起来有很多细节。比如默认值怎么设、怎么处理嵌套结构、怎么做条件判断、怎么循环生成多个资源。这些我都会在后面的实操部分展开。
理解渲染机制有一个很实用的技巧:用helm template命令在本地渲染,不提交到集群,直接看生成的 YAML 长什么样。调试模板的时候这个命令能省很多时间。
3.3 Release 的存储与状态管理
每次helm install或helm upgrade,Helm 都会在对应的 namespace 里创建一个 Secret,名字类似sh.helm.release.v1.<release-name>.v<version>。这个 Secret 里存了这次 Release 的完整信息,包括渲染后的 manifest、Values、Chart 元数据等。
这个设计带来几个好处。第一,回滚的时候 Helm 直接从历史 Secret 里读取上一个版本的 manifest,不需要重新渲染,速度快且结果确定。第二,helm history能看到所有版本记录。第三,helm rollback可以回滚到任意历史版本,不只是上一个版本。
但也要注意,这些 Secret 会占用 etcd 空间。如果一个 Release 频繁升级,历史 Secret 会越积越多。Helm 默认保留最近 10 个版本,可以用--history-max参数调整。生产环境建议根据实际情况设置,太少了回滚选择少,太多了占空间。
4. 从零部署一个完整应用的实操过程
4.1 创建自定义 Chart 的目录结构
虽然可以直接用helm create生成一个标准骨架,但为了讲清楚每个文件的作用,我带你手工创建一个最小可用的 Chart。假设应用名叫 myapp,执行:
helm create myapp生成的目录结构如下:
myapp/ ├── Chart.yaml ├── values.yaml ├── charts/ ├── templates/ │ ├── deployment.yaml │ ├── service.yaml │ ├── ingress.yaml │ ├── serviceaccount.yaml │ ├── hpa.yaml │ ├── NOTES.txt │ ├── _helpers.tpl │ └── tests/ │ └── test-connection.yaml └── .helmignoreChart.yaml是 Chart 的元数据,包含名称、版本、描述、依赖等。values.yaml是默认配置值。templates/放模板文件。charts/放子 Chart。.helmignore类似.gitignore,指定打包时忽略哪些文件。
helm create生成的骨架比较完整,包含了 Deployment、Service、Ingress、HPA、ServiceAccount 等常见资源。对于学习来说,我建议先把这个骨架跑起来,看看默认部署出来是什么样,然后再逐步修改。
4.2 Chart.yaml 与 values.yaml 的关键字段
先看Chart.yaml:
apiVersion: v2 name: myapp description: A Helm chart for Kubernetes type: application version: 0.1.0 appVersion: "1.16.0"apiVersion在 Helm 3 里固定是 v2。version是 Chart 自身的版本,每次修改 Chart 内容都应该递增。appVersion是应用的版本,跟 Chart 版本是两回事。type可以是 application 或 library,library 类型的 Chart 不生成资源,只提供模板给其他 Chart 用。
再看values.yaml的关键部分:
replicaCount: 1 image: repository: nginx pullPolicy: IfNotPresent tag: "" service: type: ClusterIP port: 80 resources: limits: cpu: 100m memory: 128Mi requests: cpu: 100m memory: 128Mi这里定义了副本数、镜像、服务、资源限制等。注意image.tag默认是空字符串,模板里通常会写成{{ .Values.image.tag | default .Chart.AppVersion }},意思是如果没指定 tag,就用 Chart.yaml 里的 appVersion。
实操心得:values.yaml 里的注释非常重要。
helm create生成的默认文件里注释很详细,建议保留并补充自己的说明。团队协作时,别人看你的 values.yaml 就能知道每个参数是干什么的,不用去翻模板。
4.3 模板文件中的常用语法与函数
模板语法是 Helm 的核心,我挑几个最常用的讲。
取值用{{ .Values.xxx }},这是最基本的。如果值可能不存在,用default函数给个兜底:{{ .Values.xxx | default "fallback" }}。
条件判断:
{{- if .Values.ingress.enabled }} apiVersion: networking.k8s.io/v1 kind: Ingress ... {{- end }}循环:
{{- range .Values.env }} - name: {{ .name }} value: {{ .value }} {{- end }}包含其他模板:
{{- include "myapp.labels" . | nindent 4 }}include用来复用_helpers.tpl里定义的模板片段,nindent是缩进函数,n表示先换行再缩进。这个组合在生成 labels 的时候特别常用。
还有toYaml函数,把结构体转成 YAML:
resources: {{- toYaml .Values.resources | nindent 2 }}这个写法比逐字段写要简洁得多,而且 values.yaml 里加字段不用改模板。
4.4 部署应用到集群并验证
假设我们已经把 Chart 准备好了,现在部署到集群。先创建一个独立的 namespace:
kubectl create namespace demo然后用 helm install 部署:
helm install myapp ./myapp -n demomyapp是 Release 名字,./myapp是 Chart 路径,-n demo指定 namespace。执行后会输出一段 NOTES 信息,告诉你部署了什么、怎么访问。
验证部署结果:
helm list -n demo kubectl get all -n demohelm list看 Release 状态,kubectl get all看实际创建的资源。如果 Pod 没起来,用kubectl describe pod和kubectl logs排查。
我习惯在部署后立刻做一次端口转发验证服务是否正常:
kubectl port-forward -n demo svc/myapp 8080:80然后浏览器访问 localhost:8080,能看到 nginx 默认页面就说明整条链路通了。
4.5 升级、回滚与卸载的完整操作
升级用helm upgrade:
helm upgrade myapp ./myapp -n demo --set replicaCount=3--set可以覆盖 values.yaml 里的值。也可以用-f指定额外的 values 文件:
helm upgrade myapp ./myapp -n demo -f prod-values.yaml升级后查看历史:
helm history myapp -n demo回滚到上一个版本:
helm rollback myapp -n demo回滚到指定版本:
helm rollback myapp 2 -n demo卸载:
helm uninstall myapp -n demo卸载会删除这个 Release 创建的所有资源,但 namespace 本身不会删。Release 的历史记录默认也会删掉,如果想保留历史,加--keep-history参数。
注意:
helm uninstall删除的资源范围是这次 Release 创建的资源。如果有些资源是手工创建的,或者被其他 Release 共享,卸载时不会动它们。但如果你用了 CRD,CRD 默认不会被删除,需要手动清理。
5. 常见问题排查与避坑经验实录
5.1 安装和渲染阶段的典型报错
报错一:Error: INSTALLATION FAILED: cannot re-use a name that is still in use
这个错误说明同名的 Release 已经存在。可能是之前部署过没删干净,或者用了--generate-name但名字冲突。解决办法是先helm list -n <namespace>看看有没有同名 Release,有的话先 uninstall,或者换个名字。
报错二:Error: UPGRADE FAILED: another operation (install/upgrade/rollback) is in progress
这个错误通常是因为上一次操作卡住了,Release 状态停留在 pending 状态。用helm list --all -n <namespace>能看到状态是 pending-install 或 pending-upgrade。解决办法是等一会儿,如果一直卡着,可能需要手动清理对应的 Secret,或者用helm rollback强制回滚。
报错三:Error: template: myapp/templates/deployment.yaml:20:18: executing "myapp/templates/deployment.yaml" at <.Values.image.tag>: nil pointer evaluating interface {}.tag
这是模板里引用了不存在的值。比如 values.yaml 里没有 image.tag,模板里直接写.Values.image.tag就会报这个错。解决办法是用default函数兜底,或者确保 values.yaml 里有这个字段。
5.2 部署后 Pod 无法启动的排查思路
Pod 起不来是部署阶段最常见的问题,排查思路可以按下面的顺序来。
第一步,看 Pod 状态:
kubectl get pods -n demo如果是 ImagePullBackOff,说明镜像拉不下来。检查镜像名对不对、tag 存不存在、私有仓库的 secret 配了没有。
如果是 CrashLoopBackOff,说明容器启动了但反复退出。用kubectl logs看日志,通常是应用配置问题。
如果是 Pending,说明调度不上去。用kubectl describe pod看 Events,常见原因是资源不足、节点选择器不匹配、PVC 没绑定。
第二步,看 Events:
kubectl describe pod <pod-name> -n demoEvents 里通常有最直接的错误信息,比如Insufficient cpu、no nodes available、failed to mount等。
第三步,看配置是否正确:
helm get values myapp -n demo helm get manifest myapp -n demoget values看实际生效的配置,get manifest看渲染后的 YAML。对比一下跟你预期的是否一致,很多时候问题就出在 values 覆盖没生效或者模板渲染结果不对。
5.3 版本兼容性与依赖管理注意事项
Helm 版本和 Kubernetes 版本之间有兼容性要求。一般来说,Helm 3.14 支持 Kubernetes 1.26 到 1.29。如果你用的是比较新的 Kubernetes,建议用较新的 Helm。具体兼容性矩阵可以查官方文档。
Chart 的 apiVersion 也有讲究。Helm 3 用 v2,Helm 2 用 v1。如果你拿到一个 v1 的 Chart,在 Helm 3 里可能能装,但会有警告,而且某些功能不支持。建议把 v1 的 Chart 迁移到 v2,主要是把 requirements.yaml 的内容合并到 Chart.yaml 的 dependencies 字段。
依赖管理方面,如果 Chart 有子 Chart 依赖,需要在 Chart.yaml 里声明,然后执行helm dependency update下载依赖到 charts/ 目录。如果依赖的是本地路径,用file://协议。如果依赖的是仓库里的 Chart,先helm repo add再声明依赖。
实操心得:生产环境部署前,一定先用
helm template渲染一遍,把生成的 YAML 存下来做 diff。这样升级前能清楚看到哪些资源会变,避免意外改动。我一般会把渲染结果提交到 Git,作为部署记录的一部分。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 排查命令 | 解决方向 |
|---|---|---|---|
| Release 名冲突 | 同名 Release 已存在 | helm list -n <ns> | 改名或先卸载 |
| 操作卡在 pending | 上次操作未完成 | helm list --all -n <ns> | 等待或清理 Secret |
| 模板渲染报 nil | 引用了不存在的值 | helm template | 加 default 兜底 |
| Pod ImagePullBackOff | 镜像拉取失败 | kubectl describe pod | 检查镜像名和仓库认证 |
| Pod CrashLoopBackOff | 容器启动后退出 | kubectl logs | 检查应用配置和日志 |
| Pod Pending | 调度失败 | kubectl describe pod | 检查资源和节点选择器 |
| values 覆盖不生效 | 优先级搞错 | helm get values | 确认 -f 和 --set 顺序 |
| 回滚后配置不对 | 历史版本选错 | helm history | 指定正确的 revision |
6. 进阶技巧与生产环境实践建议
6.1 多环境配置管理的最佳实践
实际项目里,同一套应用通常要部署到 dev、test、prod 多个环境。最朴素的做法是每个环境一个 values 文件,比如values-dev.yaml、values-prod.yaml,部署时用-f指定。这种方式简单直接,但有个问题:公共配置重复。
更好的做法是分层。先有一个values.yaml放公共默认值,然后每个环境一个覆盖文件,只写差异部分。Helm 会做深度合并,覆盖文件里的值会覆盖默认值,没写的字段保持默认。
helm install myapp ./myapp -n prod -f values.yaml -f values-prod.yaml多个-f按顺序合并,后面的覆盖前面的。--set的优先级最高,但--set不适合写复杂结构,容易出错,建议只用来做临时调试。
如果环境特别多,可以考虑用 Helmfile 或者 ArgoCD 这类工具做更上层的管理。但那是另一个话题了,初期用多 values 文件完全够用。
6.2 Chart 版本管理与私有仓库搭建
Chart 的 version 字段要严格遵守语义化版本规范。修 bug 递增 patch,加功能递增 minor,不兼容变更递增 major。每次修改 Chart 内容都要递增 version,否则推送仓库时会冲突。
私有仓库方面,最简单的方案是用 ChartMuseum,一个用 Go 写的 Chart 仓库服务,部署简单,支持本地存储和对象存储。也可以用云厂商提供的 OCI 仓库,Helm 3 支持把 Chart 推送到 OCI 兼容的仓库里,用helm push命令。
搭建好仓库后,客户端用helm repo add添加,然后就能像用公共仓库一样helm install了。私有仓库的好处是版本可控、访问受控,适合企业内部使用。
6.3 安全加固与权限控制要点
Helm 3 的权限完全依赖 kubeconfig,所以控制好 kubeconfig 的权限就控制好了 Helm 的权限。生产环境不要用 cluster-admin 的 kubeconfig 去执行 helm 命令,应该用限定 namespace 的 ServiceAccount。
具体做法是创建一个 ServiceAccount,绑定 Role 或 ClusterRole,只授予必要的权限,然后生成对应的 kubeconfig。这样即使 kubeconfig 泄露,影响范围也有限。
另外,Chart 里不要硬编码敏感信息。密码、token 这类东西应该通过--set或者外部 Secret 注入,不要写在 values.yaml 里提交到 Git。如果一定要在 values 里放敏感信息,用 helm-secrets 插件加密。
镜像方面,建议用固定 tag 而不是 latest,并且开启镜像签名验证。资源限制一定要设,requests 和 limits 都设上,避免单个应用把节点资源吃光。
6.4 我在实际使用中总结的几条经验
第一条,Chart 的目录结构要规范。templates 下的文件名要能反映资源类型,deployment.yaml、service.yaml、configmap.yaml 这样一目了然。不要把所有资源塞到一个文件里,后期维护很痛苦。
第二条,_helpers.tpl里的公共模板要抽干净。labels、selectorLabels、fullname、chart 这些常用的片段抽出来,所有资源文件统一 include,保证标签一致。标签不一致是导致 Service 选不到 Pod 的常见原因。
第三条,NOTES.txt 要写清楚。部署完成后用户看到的第一条信息就是 NOTES,把访问方式、默认账号密码、后续操作提示写清楚,能省很多沟通成本。
第四条,养成helm lint的习惯。提交前跑一下 lint,能发现很多低级错误,比如 YAML 格式问题、模板语法问题、values 缺失问题。CI 流程里也应该加上这一步。
第五条,升级前先 dry-run。helm upgrade --dry-run会渲染并模拟提交,但不真正执行。配合helm diff插件,能看到具体哪些资源会变化。这个习惯能避免很多生产事故。
7. 写在最后的一点个人体会
Helm 这个东西,刚上手的时候会觉得多了一层抽象,不如直接写 YAML 来得直接。但用久了就会发现,它带来的标准化和可复用性,在应用数量多起来之后是刚需。尤其是团队协作场景,一套 Chart 大家共用,配置通过 values 区分,比每个人维护一套 YAML 要可靠得多。
我踩过最大的坑是在 Helm 2 时代,Tiller 的权限问题导致过一次误操作。Helm 3 去掉 Tiller 之后,这个问题从根上解决了。所以如果你还在用 Helm 2,强烈建议尽快迁移到 Helm 3。
另外一个小技巧:helm template配合kubectl apply --dry-run=server可以在提交前做一次服务端校验,能发现一些客户端校验发现不了的问题,比如 CRD 字段不匹配、准入控制拒绝等。这个组合我在生产环境升级前必用。
Chart 写得好不好,一个简单的判断标准是:换一个人来部署,只看 README 和 values.yaml,能不能顺利部署起来。如果能,说明 Chart 的抽象和文档做到位了。这也是我一直努力的方向。