课程目录(第 16 章 / 共 33 章)
Helm 入门:把一堆 YAML 变成可复用的 chart
用 Helm 把 web / api / redis 的一堆清单打包成带参数的 chart,一份模板同时支撑多个环境,并拥有可回滚的版本记录。
学完这一章,你将能够
- ✓说清 Chart、Release、Repository、Values 四个概念的关系
- ✓读懂 helm create 生成的目录结构与模板语法
- ✓把写死的 YAML 改写成带 values 参数的 chart
- ✓会用 helm install / upgrade / rollback / uninstall 管理一次发布
为什么需要 Helm
前面十几章我们一直是「一个资源一个 YAML 文件,kubectl apply -f 提交」。三四个资源时这样最直观,但真实项目很快会变成这样:
- 同一套清单要跑在 dev、staging、prod 三套环境,区别只有镜像 tag、副本数、资源配额和域名;
- 改一个字段(比如统一加
resources.limits),要改十几个几乎一样的文件; - 上线后发现有问题想回退,只能靠 git 翻历史、手动改回去,中间的差异没人说得清。
这三个痛点的共同点:YAML 里写死的东西,本该是参数。
Helm 就是 Kubernetes 的包管理器:把一组 YAML 变成带参数的模板,一次安装产生一个发布记录,升级和回滚都由 Helm 维护版本历史。Helm 3 不需要在集群里装任何服务端组件,它直接用你的 kubeconfig 和 API Server 对话。
四个核心概念
| 概念 | 是什么 | 例子 |
|---|---|---|
| Chart | 一个打包好的模板目录,包含资源定义和默认参数 | kube101-app/ 整个目录 |
| Release | Chart 在集群里的一次安装实例,有名字和版本号 | helm install demo ./kube101-app 里的 demo |
| Repository | 存放和分发 chart 的仓库,本质是一个带 index.yaml 的 HTTP 服务 | https://charts.bitnami.com/bitnami |
| Values | 渲染模板时用的参数,来自 values.yaml 与命令行覆盖 | web.replicaCount: 2 |
记住一句话:Chart 是模板,Values 是填进去的空,Release 是填完之后在集群里跑着的那一份。
四个概念串起来是一条固定的流水线,看懂它就理解了 Helm 的整个工作方式:
Chart(模板目录) Values(参数)
templates/*.yaml values.yaml ← 默认值
Chart.yaml / _helpers.tpl -f values-prod.yaml ← 文件覆盖
│ --set web.replicaCount=3 ← 命令行
│ │
└────────────────┬─────────────────┘
│ helm template(本地渲染,不接触集群)
▼
渲染后的标准 YAML 清单
│ helm install / helm upgrade
▼
Release demo(记录存在集群的 Secret 里,带 revision 号)
│ 提交清单
▼
集群资源:Deployment / Service / ConfigMap / PVC …
│ helm rollback demo 1 → 回到 revision 1 再提交同一个 Chart 可以用不同的 Values 装出多个 Release,比如 demo-dev 和 demo-prod 用同一份模板、不同的参数,互不干扰。
安装 Helm 与第一个 chart
brew install helm # macOS;Linux / WSL 用官方脚本:
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version # 期望 v3.x
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo updatehelm create kube101-app 会生成一个可以直接安装的骨架:
kube101-app/
├── Chart.yaml chart 的身份证:名字、版本、类型
├── values.yaml 默认参数,用户覆盖的就是它
├── charts/ 依赖的子 chart(现在为空)
├── .helmignore 打包时忽略哪些文件(类似 .gitignore)
└── templates/
├── deployment.yaml / service.yaml / hpa.yaml / ingress.yaml
├── NOTES.txt 安装成功后打印给用户的提示
├── _helpers.tpl 可复用的模板片段,不会生成资源
└── tests/test-connection.yaml几个容易搞混的点:Chart.yaml 里的 version 是 chart 自己的版本,appVersion 是里面跑的应用的版本,两者互不相干;以 _ 开头的文件不会被渲染成资源;templates/ 下每个 .yaml 文件可以生成一个或多个资源,文件名随便起。
模板语法基础
Helm 模板用的是 Go template 语法,外面套一层 {{ }}。核心就四种写法:
# 1. 取值:从 values.yaml 或命令行拿参数
replicas: {{ .Values.web.replicaCount }}
image: {{ .Values.image.web }}
name: {{ .Release.Name }}-web # .Release 是本次发布的元信息:Name / Namespace / Revision
# 2. include 复用 _helpers.tpl 里的片段;nindent 负责「换行 + 缩进 4 格」
metadata:
labels:
{{- include "kube101-app.labels" . | nindent 4 }}
# 3. if 条件渲染:条件为假时整段消失
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
{{- end }}
# 4. range 遍历 map,把参数展开成 env 列表
env:
{{- range $key, $value := .Values.web.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}两个必须记住的细节:{{- 会吃掉它前面的空白与换行,-}} 会吃掉后面的换行,这正是模板输出缩进整齐的关键;| quote、| nindent 4 是管道,把左边的值交给右边的函数处理。
动手:把 web / api / redis 改写成 chart
把前几章写死的清单搬进 kube101-app/templates/,逐个把硬编码的值换成 .Values。先写 values.yaml:
namespace: demo
image:
web: nginx:1.27
api: hashicorp/http-echo:1.0
redis: redis:7.2
web:
replicaCount: 2
env:
APP_ENV: production
api:
replicaCount: 2
greeting: hello from api
redis:
persistence:
enabled: true
size: 1Gi
ingress:
enabled: true
host: kube101.localtemplates/configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: {{ .Values.namespace }}
data:
APP_ENV: {{ .Values.web.env.APP_ENV | quote }}
API_GREETING: {{ .Values.api.greeting | quote }}
REDIS_HOST: {{ printf "%s-redis" .Release.Name | quote }}templates/api.yaml 的关键片段(web 与 redis 结构相同,只是镜像和端口不同):
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "kube101-app.fullname" . }}-api
labels:
{{- include "kube101-app.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.api.replicaCount }}
selector:
matchLabels:
app: {{ include "kube101-app.fullname" . }}-api
template:
metadata:
labels:
app: {{ include "kube101-app.fullname" . }}-api
spec:
containers:
- name: api
image: {{ .Values.image.api }}
args:
- -listen=:5678
- -text=$(API_GREETING)
env:
- name: API_GREETING
valueFrom:
configMapKeyRef:
name: app-config
key: API_GREETINGtemplates/redis-pvc.yaml 演示条件渲染:只有把 redis.persistence.enabled 设为 true 才会创建 PVC。
{{- if .Values.redis.persistence.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ .Release.Name }}-redis-data
namespace: {{ .Values.namespace }}
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.redis.persistence.size }}
{{- end }}渲染与发布:template / install / upgrade / rollback
写模板最容易犯的错是「改完直接 install,报错才知道有问题」。养成先本地渲染的习惯:
helm lint ./kube101-app # 语法与规范检查
helm template demo ./kube101-app -n demo # 渲染成最终 YAML 打印到屏幕
helm template demo ./kube101-app -n demo > rendered.yaml # 存下来对比helm template 完全不接触集群,输出就是渲染后的标准清单,可以直接和 kubectl apply --dry-run=client -f rendered.yaml 对照检查。
安装、升级与回滚一次真实发布
# 1. 安装:--create-namespace 会顺手建好命名空间
helm install demo ./kube101-app -n demo --create-namespace
# 2. 确认发布状态
helm list -n demo
kubectl get pods -n demo
# 3. 升级:把 web 副本数改成 3,产生 revision 2
helm upgrade demo ./kube101-app -n demo --set web.replicaCount=3
# 4. 看版本历史
helm history demo -n demo
# 5. 回滚到 revision 1,确认副本数回到 2
helm rollback demo 1 -n demo
kubectl get pods -n demo -l app=demo-kube101-app-web
# 6. 卸载
helm uninstall demo -n demohelm list -n demo 期望看到 STATUS 为 deployed;helm history 期望看到每一行对应一个 revision,回滚后还会多出一条 rolled back to 1 的记录。helm uninstall 之后 kubectl get all -n demo 应该只剩空列表。
values 的覆盖层级:默认 → -f → --set
参数覆盖是 Helm 最容易踩坑的地方,先记住优先级(从低到高):
| 来源 | 写法 | 说明 |
|---|---|---|
| Chart 自带的 values.yaml | 无需写法 | 兜底默认值 |
| 父 chart 传入的 values | 子 chart 依赖时 | 多 chart 场景才会遇到 |
| 用户 values 文件 | -f values-prod.yaml | 多个 -f 时后一个覆盖前一个 |
| 命令行 | --set web.replicaCount=3 | 优先级最高 |
假设默认 values.yaml 里是 web.replicaCount: 2、image.web: nginx:1.27,再加一份生产参数:
# values-prod.yaml
web:
replicaCount: 5
image:
web: nginx:1.27-alpine三条命令分别升级,生效结果完全不同:
helm upgrade demo ./kube101-app -n demo # 副本 2,镜像 nginx:1.27
helm upgrade demo ./kube101-app -n demo -f values-prod.yaml # 副本 5,镜像 nginx:1.27-alpine
helm upgrade demo ./kube101-app -n demo -f values-prod.yaml --set web.replicaCount=10三条命令依次执行后,集群里的副本数分别是 2、5、10,而镜像只有后两条变成 nginx:1.27-alpine:--set 只覆盖它点名的那个键,同层级的其他值保持 -f 文件里的内容。
想确认某次发布实际生效的值,别对着 values.yaml 猜:
helm get values demo -n demo --all # 合并后的完整参数(不带 --all 只看被覆盖的部分)
helm get manifest demo -n demo # 这次发布真正提交到集群的清单常见坑与速查表
Helm 的几个典型陷阱
- 覆盖层级搞混:改了
values.yaml里的默认值,但 upgrade 时又带了-f,文件里的值会盖掉你的修改。用helm get values --all确认。 - `helm upgrade` 与 `kubectl apply` 混用:同一个资源被两个工具管理,会出现「kubectl 改完被 upgrade 覆盖」「Helm 记录与集群实际不一致」的状态漂移。要么全交给 Helm,要么全交给 kubectl。
- 卸载后 PVC 残留:
helm uninstall会删除模板里定义的 PVC,但 StatefulSet 的volumeClaimTemplates创建的 PVC、以及带helm.sh/resource-policy: keep注解的资源不会被删除,需要手动kubectl delete pvc。
| 现象 | 原因 | 怎么确认 |
|---|---|---|
helm install 报 namespaces "demo" not found | 命名空间不存在且没加 --create-namespace | kubectl get ns demo |
| 改了 values.yaml 但集群没变化 | 只改了本地文件没 upgrade,或被 -f / --set 覆盖 | helm get values demo -n demo --all |
| 渲染结果缩进错乱、字段跑到同一行 | {{- 与 nindent 用法不对 | helm template demo ./kube101-app -n demo 看输出 |
报 template "xxx" not defined | include 的名字与 _helpers.tpl 里的 define 不一致 | 打开 _helpers.tpl 核对 define 全名 |
helm uninstall 后 PVC 还在 | PVC 由 volumeClaimTemplates 创建,或带 keep 策略注解 | kubectl get pvc -n demo |
| 回滚后配置没变回去 | 回滚的是 chart 清单,集群侧可能有人手工改过 | helm history demo -n demo 加 helm get manifest demo -n demo |
自测题
自测:为什么 `helm template` 的输出和集群里的清单不一样?(点击展开答案)
因为两者根本不是同一个时间点上的东西。helm template 用的是你当前磁盘上的模板和 values,完全不接触集群;集群里跑着的是某一次 install / upgrade 时渲染并提交的清单,可能来自更早的模板版本,也可能被 -f、--set 覆盖过。想看到「集群里那份」,要用 helm get manifest demo -n demo——它返回的正是那个 revision 提交的原始 YAML。这也是排查「我明明改了模板却没生效」的第一步:先确认你到底在看哪一份。
自测:为什么同一份 chart 能装出多个互不干扰的 Release?(点击展开答案)
因为 Helm 把「模板」和「安装实例」彻底分开了。资源名里通常带 .Release.Name(比如 {{ include "kube101-app.fullname" . }} 会拼上 release 名),标签里也带 release 信息,于是 demo-dev 和 demo-prod 生成的是名字不同的两组资源;再加上 -n 指定的命名空间本身也是一层隔离。Helm 自己的发布记录也以「release 名 + 命名空间」为键存在集群的 Secret 里。所以同一个 chart 可以同时装几十次,升级其中一个不会动到另一个。
自测:为什么 `--set` 的值会覆盖 `-f` 文件里的同名项?(点击展开答案)
因为 Helm 的合并顺序是写死的:先 chart 自带的 values.yaml,再父 chart 传入的 values,再按顺序合并各个 -f 文件(后面的覆盖前面的),最后才合并 --set 的键值对。它是深合并:只覆盖你点名的那个键,同一层级里的其他键保持不变。这解释了那个常见困惑——--set web.replicaCount=10 只改了副本数,-f 文件里的镜像仍然生效。排查参数问题时,用 helm get values demo --all 看合并后的最终结果,比对着三个文件来回猜快得多。
小结
- Helm 解决的是「YAML 里写死的东西本该是参数」这个问题:多环境差异、重复清单、版本回滚。
- Chart 是模板,Values 是参数,Release 是集群里的一次安装,Repository 是分发渠道;四者串成「模板 + 参数 → 渲染 → 提交 → 回滚」的流水线。
- 模板语法核心就四个:
{{ .Values.x }}取值、{{ include }}复用片段、if条件渲染、range循环。 - 工作流固定为:
helm lint→helm template本地渲染 →helm install→helm upgrade→ 出问题helm rollback。 - 参数覆盖优先级是 values.yaml <
-f文件 <--set,用helm get values --all看最终结果。
相关章节:第 9 章 ConfigMap 与 Secret 里的配置,正是被 Helm 模板参数化的对象;第 10 章 存储卷与 PV/PVC 解释 chart 里 PVC 模板背后的存储语义。
练习
- 把本章的 chart 补完:给 web 和 redis 各写一份
templates/*.yaml,用helm template渲染后和手写的 YAML 对比差异。 - 新增一个
values-dev.yaml,把副本数设为 1、镜像换成nginx:1.27-alpine、Ingress 关闭,用同一个 chart 分别装出demo-dev和demo-prod两个 Release。 - 故意在模板里写错一个
.Values路径(比如web.replicas),观察helm template与helm install分别报什么错,理解为什么推荐先渲染再安装。
到这里,工具和技能都齐了。下一章我们不引入任何新概念,只把前面所有东西串起来:用一份完整的清单,从零部署一个能通过域名访问的三层应用。