课程目录(第 16 章 / 共 33 章)
课程/综合实战

Helm 入门:把一堆 YAML 变成可复用的 chart

16 章 / 共 33·20 分钟·入门+Helm模板发布kubectl

用 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/ 整个目录
ReleaseChart 在集群里的一次安装实例,有名字和版本号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 的整个工作方式:

text
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-devdemo-prod 用同一份模板、不同的参数,互不干扰。

安装 Helm 与第一个 chart

bash
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 update

helm create kube101-app 会生成一个可以直接安装的骨架:

text
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 里的 versionchart 自己的版本appVersion里面跑的应用的版本,两者互不相干;以 _ 开头的文件不会被渲染成资源;templates/ 下每个 .yaml 文件可以生成一个或多个资源,文件名随便起。

模板语法基础

Helm 模板用的是 Go template 语法,外面套一层 {{ }}。核心就四种写法:

yaml
# 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

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.local

templates/configmap.yaml

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 结构相同,只是镜像和端口不同):

yaml
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_GREETING

templates/redis-pvc.yaml 演示条件渲染:只有把 redis.persistence.enabled 设为 true 才会创建 PVC。

yaml
{{- 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,报错才知道有问题」。养成先本地渲染的习惯:

bash
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 对照检查。

安装、升级与回滚一次真实发布

bash
# 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 demo

helm list -n demo 期望看到 STATUSdeployedhelm 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: 2image.web: nginx:1.27,再加一份生产参数:

yaml
# values-prod.yaml
web:
  replicaCount: 5
image:
  web: nginx:1.27-alpine

三条命令分别升级,生效结果完全不同:

bash
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 猜:

bash
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 installnamespaces "demo" not found命名空间不存在且没加 --create-namespacekubectl 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 definedinclude 的名字与 _helpers.tpl 里的 define 不一致打开 _helpers.tpl 核对 define 全名
helm uninstall 后 PVC 还在PVC 由 volumeClaimTemplates 创建,或带 keep 策略注解kubectl get pvc -n demo
回滚后配置没变回去回滚的是 chart 清单,集群侧可能有人手工改过helm history demo -n demohelm 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-devdemo-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 linthelm template 本地渲染 → helm installhelm upgrade → 出问题 helm rollback
  • 参数覆盖优先级是 values.yaml < -f 文件 < --set,用 helm get values --all 看最终结果。

相关章节:第 9 章 ConfigMap 与 Secret 里的配置,正是被 Helm 模板参数化的对象;第 10 章 存储卷与 PV/PVC 解释 chart 里 PVC 模板背后的存储语义。

练习

  1. 把本章的 chart 补完:给 web 和 redis 各写一份 templates/*.yaml,用 helm template 渲染后和手写的 YAML 对比差异。
  2. 新增一个 values-dev.yaml,把副本数设为 1、镜像换成 nginx:1.27-alpine、Ingress 关闭,用同一个 chart 分别装出 demo-devdemo-prod 两个 Release。
  3. 故意在模板里写错一个 .Values 路径(比如 web.replicas),观察 helm templatehelm install 分别报什么错,理解为什么推荐先渲染再安装。

到这里,工具和技能都齐了。下一章我们不引入任何新概念,只把前面所有东西串起来:用一份完整的清单,从零部署一个能通过域名访问的三层应用。