Helm Chart 入门实战:把一坨 K8s YAML 收敛成可传参的可复用模板

在 K8s 上部署过几个服务后,你大概率遇到过这个场景:测试环境和生产环境的 Deployment 几乎一模一样,只有镜像 tag、副本数、域名不同。于是你复制了一份 YAML,改几个字段,结果两份文件慢慢就漂移了——生产上加了个环境变量忘了同步到测试,某次故障排查半天才发现两边配置根本对不上。

Helm 就是来治这个病的:把 K8s manifest 变成带变量的模板,不同环境只维护一份「差异值」文件。这篇从一个裸 YAML 出发,一步步把它 Helm 化,讲清 Chart 的目录结构、模板语法和最容易踩的坑。

从一份重复的 YAML 说起

假设我们有个 web 服务,原始 Deployment 长这样:

# deployment.yaml —— 每个环境复制一份,改镜像、副本、域名
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 2
  selector:
    matchLabels: { app: web }
  template:
    metadata:
      labels: { app: web }
    spec:
      containers:
        - name: web
          image: myrepo/web:1.4.0
          ports:
            - containerPort: 8080

生产要 4 个副本、镜像是 1.4.0,测试要 1 个副本、镜像是 1.5.0-rc1。用复制大法就会有两份几乎相同的文件。Helm 的思路是:结构只写一遍,变的部分抽成变量。

第一步:建一个 Chart 骨架

helm create mychart

生成的目录里,核心就三样(其余可以先删掉):

mychart/
├── Chart.yaml        # Chart 的元信息:名字、版本
├── values.yaml       # 默认值(变量的默认取值)
└── templates/        # 带变量的 K8s manifest 模板
    └── deployment.yaml
  • Chart.yaml 是这个包的身份证。
  • values.yaml 存所有可配置项的默认值。
  • templates/ 里是模板,用 {{ }} 引用 values。

Chart.yaml 最小内容:

apiVersion: v2
name: mychart
version: 0.1.0        # Chart 自身的版本
appVersion: "1.4.0"   # 应用默认版本,仅作展示用途

第二步:把变量抽进 values.yaml

# values.yaml —— 默认值,可被 -f 或 --set 覆盖
replicaCount: 2

image:
  repository: myrepo/web
  tag: "1.4.0"

service:
  port: 8080

然后把 templates/deployment.yaml 里会变的字段换成模板引用:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-web   # .Release.Name 是安装时指定的实例名
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels: { app: {{ .Release.Name }}-web }
  template:
    metadata:
      labels: { app: {{ .Release.Name }}-web }
    spec:
      containers:
        - name: web
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - containerPort: {{ .Values.service.port }}

几个内置对象要认识:

  • .Values.xxx:读 values.yaml(或命令行覆盖)里的值。
  • .Release.Name:helm install <name> 时的实例名,用它拼资源名,同一个 Chart 就能装多份而不撞名。
  • .Chart.Name:Chart 名字本身。

第三步:先渲染再安装,别盲发

Helm 最实用的习惯是装之前先看渲染结果helm template 把模板 + values 算出最终 YAML 打到屏幕,不碰集群:

helm template myapp ./mychart

你会看到 {{ .Release.Name }} 被替换成 myapp,replicas 变成 2。确认没问题再真正安装:

helm install myapp ./mychart

想模拟安装但不真的提交给集群,用 --dry-run:

helm install myapp ./mychart --dry-run --debug

第四步:多环境靠「差异值」文件,而不是复制 Chart

这才是 Helm 的价值所在。生产和测试共用同一个 Chart,各自只维护一个覆盖文件:

# values-prod.yaml —— 只写和默认值不同的部分
replicaCount: 4
image:
  tag: "1.4.0"
# values-test.yaml
replicaCount: 1
image:
  tag: "1.5.0-rc1"

安装时用 -f 叠加,后面的覆盖前面的:

# 生产
helm install web-prod ./mychart -f values-prod.yaml
# 测试
helm install web-test ./mychart -f values-test.yaml

临时改一两个值不想建文件,用 --set:

helm upgrade web-prod ./mychart -f values-prod.yaml --set image.tag=1.4.1

覆盖优先级从低到高是:values.yaml 默认值 < -f 文件 < --set。到这一步,「两份 YAML 漂移」的问题就根治了:结构只有一份,差异一目了然。

升级与回滚:Helm 记得每一版

改完值用 upgrade 而不是重新 install:

helm upgrade web-prod ./mychart -f values-prod.yaml

Helm 会把每次 upgrade 记成一个 revision。发现新版本有问题,一条命令滚回上一版:

helm history web-prod          # 看历史版本
helm rollback web-prod 1       # 回到第 1 版

这比手动 kubectl apply 旧文件靠谱得多——你不用自己保管「上一版长啥样」,Helm 替你存了。

两个新手高频坑

坑一:缩进用了 Tab。 Helm 模板本质是 YAML,YAML 不认 Tab。模板里 {{ }} 前后的缩进必须是空格,否则 helm template 直接报 error converting YAML

坑二:字符串数字没加引号。 像镜像 tag "1.40" 这种,如果 values 里写成 tag: 1.40,YAML 会解析成浮点数 1.4,渲染出来镜像就变成 web:1.4,拉取失败。凡是可能被误判成数字/布尔的值,一律加引号:tag: "1.40"。模板里也建议 image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 整体套引号。

小结

  • Helm 治的是「YAML 复制漂移」:结构写一遍进 templates/,变的部分抽进 values.yaml,多环境只维护差异文件。
  • 三个核心目录:Chart.yaml(身份)、values.yaml(默认值)、templates/(带 {{ }} 的模板)。
  • 装前先渲染:helm template / --dry-run --debug 看最终 YAML,别盲发。
  • 覆盖优先级:默认值 < -f 文件 < --set;多环境用多个 values 文件叠加。
  • 升级用 upgrade,出事 rollback:Helm 记录每个 revision,回滚不用自己存旧文件。
  • 两个必踩坑:模板缩进只能空格不能 Tab;像 tag 这种值一律加引号防被解析成数字。

一句话记忆:Helm = K8s YAML 的模板引擎,结构一份、差异分环境,装前先渲染、出事能回滚。

更多推荐