大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
模板与 values
这一课你将能做到
- 用
.Values、if/with/range、include+nindent写出可渲染的 Deployment/Service 模板,关键行能解释为什么这么写 - 用
helm template(不需要集群)对比同一 Chart 在 dev/prod 两份 values 下的输出差异,说清-f与--set的覆盖顺序 - 遇到「模板渲染出无效 YAML」时,用
--debug与缩进规则定位并修复
为什么要学
Chart 的灵魂是 Go template + YAML:Helm 在安装/升级前把默认 values、各层 -f 文件、--set 参数合并成一份 .Values,再驱动模板渲染出最终清单交给 API。写错缩进、漏引号、条件分支吐出半截映射,都会变成「看起来像 Helm 的错,其实是 YAML 坏了」。本课练的是 可读、可调试 的模板——够用且清晰,而不是堆砌过滤器炫技。学完后你应能不看答案写出 backend 的 Deployment/Service,并用 helm template 自检。
先建立图像
values.yaml(默认) + values-dev.yaml(-f) + --set(可选)
\ | /
▼ ▼ ▼
合并后的 .Values(越靠后优先级越高)
+
.Release / .Chart / .Capabilities / .Files
│
▼
templates/*.yaml(Go template 引擎)
│
▼
纯 YAML 清单 → 交给 API,或仅 helm template 预览
| 内置对象 | 人话 | 常用字段 |
|---|---|---|
.Values | 合并后的旋钮表 | .Values.image.tag、.Values.replicaCount |
.Release | 这次安装实例 | .Release.Name、.Release.Namespace |
.Chart | 包元数据 | .Chart.Name、.Chart.Version |
.Capabilities | 集群能力探测 | .Capabilities.KubeVersion(进阶) |
人话:模板里写的是「填空题」;helm template 是交卷前自己先誊一遍答案,不用集群就能检查。
跟着做
步骤 1:准备三份 values
values.yaml(Chart 默认):
replicaCount: 1
image:
repository: registry.example/coderoadmap-backend
tag: "1.0.0"
service:
port: 8080
db:
host: mysql.coderoadmap-dev.svc
passwordSecretName: backend-db-secret
# 空对象 = 可选块;模板里 with 条件渲染
resources: {}
values-dev.yaml:
replicaCount: 1
image:
tag: "1.0.0-dev"
values-prod.yaml:
replicaCount: 2
image:
tag: "1.0.0"
db:
host: mysql.prod.internal
resources:
limits:
cpu: "500m"
memory: 512Mi
步骤 2:写 Deployment 模板
templates/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
# helpers 统一命名,避免手写拼错
name: {{ include "coderoadmap-backend.fullname" . }}
labels:
{{- include "coderoadmap-backend.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "coderoadmap-backend.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "coderoadmap-backend.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: api
# 仓库与 tag 分字段;升级通常只改 tag
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.port }}
env:
- name: SPRING_DATA_REDIS_HOST
value: redis.coderoadmap.svc
- name: DB_HOST
value: {{ .Values.db.host | quote }}
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Values.db.passwordSecretName }}
key: password
{{- with .Values.resources }}
# with:仅当 resources 非空时才渲染整个 resources 块
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
步骤 3:补 Service 模板
templates/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: {{ include "coderoadmap-backend.fullname" . }}
labels:
{{- include "coderoadmap-backend.labels" . | nindent 4 }}
spec:
type: ClusterIP
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.port }}
protocol: TCP
selector:
{{- include "coderoadmap-backend.selectorLabels" . | nindent 4 }}
确认 selector 与 Pod 标签来自 同一个 selectorLabels helper——不一致 Service 就选不中 Pod。
步骤 4:写 helpers 最小集
templates/_helpers.tpl:
{{- define "coderoadmap-backend.fullname" -}}
{{- printf "%s-backend" .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- define "coderoadmap-backend.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
{{- define "coderoadmap-backend.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}
注意 include 必须传上下文 .,否则 helper 里访问不到 .Release/.Chart。
步骤 5:渲染并对比环境
helm template myrel . -f values-dev.yaml > dev.yaml
helm template myrel . -f values-prod.yaml > prod.yaml
打开对比:dev 副本 1、tag 1.0.0-dev、无 resources 块;prod 副本 2、有 resources 限额、db.host 指向内网。再验证覆盖顺序:
helm template myrel . -f values-prod.yaml --set image.tag=9.9.9 | grep "image:"
# 输出 9.9.9 —— --set 优先级高于 -f 文件
结论写进笔记:优先级 默认 values < 先传的 -f < 后传的 -f < --set;多层 -f 时后者覆盖前者。
步骤 6:故意制造错误再修复
把 resources 块的缩进打乱(或删掉 nindent 前的 -),然后:
helm template myrel . -f values-prod.yaml --debug
读报错信息定位行号 → 修复 → 重新渲染通过。体会口诀:「渲染报错先怀疑 YAML 结构与空白,再怀疑函数与数据。」
讲清楚要点
- 因为渲染结果必须是合法 YAML,所以空白控制与模板逻辑同等重要——
{{-与-}}吃掉紧邻的空白/换行;indent/nindent把 helper 输出按层级缩进(nindent先换行再缩进,几乎总是比indent更安全)。 - 因为字符串里可能含
:、{等特殊字符,所以镜像名、env 值、注解值要用引号包起来——image: "{{ ... }}"、| quote都是为此;不加引号时 YAML 会把冒号解析成映射导致结构损坏。 - 因为多环境只是 values 不同,所以禁止在模板里写死
if eq .Release.Name "prod"这类环境魔数——Release 名是命名问题不是配置项;环境差异一律显式放进 values,模板保持环境无关。 - 因为
helm template不碰集群,所以 CI 与本地评审应先 template/lint——在「装进集群」之前就能发现绝大多数结构错误;它发现不了的只有运行时问题(镜像不存在、探针路径错误)。 - 因为 helpers 统一命名与标签,所以改资源名只改 define 一处——fullname/labels/selectorLabels 三件套是骨架里的标准件。
- 因为
--set表达复杂结构很别扭(逗号分隔、索引语法),且优先级最高,所以只用于临时试验——长期配置以 values 文件为准,GitOps 场景更是只信文件。
你可能会懵的地方
-
{{ }}和{{- -}}差在哪? 功能相同;加-会剥掉紧邻的空白与换行。YAML 对缩进和空行敏感,不用-经常渲染出「幽灵空行」导致结构错位,所以实操里几乎总是写{{- ... -}}。 -
if/with/range怎么选?if判断真假(空字符串、0、nil、空 map 都为假);with把.切换到指定对象(块内直接.cpu而不是.Values.resources.cpu),对象为空则整块跳过;range遍历列表或 map。资源可选块的标准写法就是{{- with .Values.resources }} ... {{- end }}。 -
为什么到处是
nindent?include返回的是不带缩进的字符串,直接放进 YAML 会破坏层级。nindent 4= 先输出一个换行再缩进 4 格,让标签块精确落位到labels:下面两级。 -
toYaml是干嘛的? 把 values 里的对象(如resources: {limits: {...}})原样序列化成 YAML 文本。用户在 values 里怎么写结构,模板就怎么输出——比逐字段手拼灵活得多,是透传任意结构(resources、nodeSelector、tolerations)的标准手法。 -
模板里能写多复杂的逻辑? 引擎能力很强,但工程共识是 模板越薄越好:分支与默认值尽量设计进 values 结构里;模板过厚=难 lint、难评审、难在 GitOps 里 diff。看到三层嵌套 if 的 Chart 先怀疑设计。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
image: {{ .Values.image.repository }}:{{ .Values.image.tag }} 不加引号 | tag 含特殊字符或以数字开头时 YAML 解析歧义 | 整串加引号 image: "{{ ... }}" |
| Service selector 手写、Pod 标签用 helper | 两处易漂移,选不中 Pod | 两边引用同一 selectorLabels |
用 .Release.Name 判断环境写死分支 | 难测试、难复用 | values 显式参数 |
| 条件分支只包住值不包住 key | 输出半截 key 导致非法 YAML | 整个键值块放进 if/with |
只 --set 调通、从不固化 values 文件 | 同事与 CI 无法复现 | 结果写回 -f 文件 |
helper 里漏传上下文 . | helper 内取不到 .Release | include "x" . 始终传点 |
卡点急救
- 现象:
error converting YAML to JSON或渲染产物缩进错乱——九成是空白问题:检查该行是否该用{{-、helper 输出是否用了nindent、块缩进格数是否与 YAML 层级一致;用helm template --debug看 Meta 行报的文件与行号。 - 现象:env 值渲染成
map[...]或直接报错——你把对象当字符串用了;用toYaml+nindent整块透传,或| quote处理标量,并检查 values 里该键的真实类型。 - 现象:同一个 Chart dev 渲染正常、prod 报错——差异几乎必在 prod values 的结构(多了 resources、缩进错了);
helm template myrel . -f values-prod.yaml --debug单独渲染 prod 定位,别在模板里瞎找。
面试常问
-
Helm 模板的内置对象有哪些?平时最常用哪几个?
.Values(合并后的用户配置)、.Release(安装实例名/命名空间/修订号)、.Chart(包元数据)、.Capabilities(集群 API 版本探测)、.Files(包内文件访问)、.Template(当前模板信息)。日常 90% 是前三者:镜像与副本读.Values,命名与标签用.Release+.Chart。 -
values 的合并优先级顺序? 从低到高:Chart 默认
values.yaml→ 父 Chart(子 Chart 场景)→ 按顺序传入的每个-f文件(后者覆盖前者)→--set/--set-string/--values等命令行参数。--set优先级最高但表达复杂结构困难,生产以 values 文件为准。 -
如何在不连接集群的情况下验证 Chart?
helm lint(语法与最佳实践)+helm template --debug(渲染预览,可叠加各环境-f对比输出差异)+helm install --dry-run --debug(连集群校验但不落资源,含 admission 校验)。CI 流水线中至少跑前两个。
自测三问
.Values、.Release、.Chart各提供什么?升级镜像 tag 应改哪一层的哪个值?- 为什么 CI 里必须跑
helm template/helm lint?它们发现不了哪类问题? - 写出「仅当
.Values.resources非空才渲染 resources 块」的模板写法,并解释为什么要用with而不是if。
小结一句话
values 驱动 Go template 生成合法 YAML:空白与引号决定合法性,helpers 决定一致性,helm template 多环境对比是渲染正确性的第一道闸——多环境一套 Chart 从这里开始成立。
延伸
- Helm — Chart Template Guide
- Helm — Values Files
- 下一课:把渲染好的 Chart 真正装进集群——install/upgrade/rollback 与 Release 生命周期。