深色模式
Helm 模板语法与进阶
摘要:本文面向已经了解 Chart 基本结构、需要编写或维护生产级 Helm 模板的工程师。覆盖 Go template 的内置对象、控制流、函数、helper 复用、
values.schema.json校验,以及常见的渲染失败模式与排障。
适用版本与前提
- Helm:3.x(模板引擎为 Go
text/template+Sprig函数库) - Kubernetes:v1.25+
- 前提:已熟悉
Chart.yaml/values.yaml/templates/目录约定(见上一篇)
背景与问题
把"结构"和"数值"分离只是第一步。真实 Chart 要面对:同一段 labels 在 5 个资源里重复、某些字段只在特定开关下出现、镜像地址需要拼接、缺少必填参数要在安装前就报错。如果只用 {{ .Values.x }} 字面替换,模板会迅速失控且难以排错。Helm 模板语法的目标,就是用 Go template 的表达能力,让 Chart 既参数化又可维护。
渲染失败的代价
模板语法错误不会在写文件时暴露,而是在 helm install/helm template 阶段才爆发,且报错信息往往指向渲染后的行号而非源码。生产上必须在 CI 中加入 helm lint 与 helm template --dry-run 门禁,避免把错误 Chart 推到仓库。
内置对象(Built-in Objects)
模板里能直接引用一组 Helm 注入的顶层对象,最常用的是:
| 对象 | 含义 | 示例 |
|---|---|---|
.Values | 合并后的配置值(默认值 + 覆盖) | {{ .Values.replicaCount }} |
.Release | 当前 release 信息 | .Release.Name、.Release.Namespace、.Release.IsInstall |
.Chart | Chart.yaml 内容 | .Chart.Name、.Chart.Version |
.Capabilities | 集群能力 | .Capabilities.KubeVersion.Version、.Capabilities.APIVersions.Has "apps/v1" |
.Template | 当前模板路径 | .Template.Name |
. 表示当前作用域,内置对象都从根作用域取,所以前面带点。例如在 range 循环内 . 会变成迭代项,需要用变量捕获外层值:$rel := .Release.Name。
控制结构
if / else —— 条件渲染
yaml
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Release.Name }}-ingress
spec:
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ .Release.Name }}
port:
number: {{ .Values.service.port }}
{{- end }}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
注意 {{- 与 -}} 的减号用于去除空白与换行,这是生产模板里控制 YAML 缩进与空行最关键的符号,写错会导致渲染出多余空行甚至 YAML 解析失败。
range —— 遍历列表/字典
yaml
{{- range .Values.extraEnv }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}1
2
3
4
2
3
4
with —— 限定作用域
yaml
{{- with .Values.image }}
image: "{{ .repository }}:{{ .tag }}"
{{- end }}1
2
3
2
3
with 进入子对象后,内部 . 指向该子对象,可少写一长串前缀。
常用函数与管道
Helm 继承了 Go template 与 Sprig 库,生产中最实用的几个:
yaml
# default:值为空时给默认值,强烈建议所有可选字段都包一层
replicas: {{ .Values.replicaCount | default 1 }}
# quote:字符串加引号,避免 YAML 把 "1.0" 解析成数字
value: {{ .Values.key | quote }}
# required:缺失则安装前直接报错,用于必填项
image: "{{ required "image.repository is required" .Values.image.repository }}"
# toYaml + nindent:把整个 map 原样展开并缩进,常用于 labels / affinity
{{- toYaml .Values.affinity | nindent 8 }}
# tpl:把字符串当作模板再渲染一次,用于 values 中引用其他 values
annotation: {{ tpl .Values.annotationTemplate . }}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
生产危险
required 会在渲染阶段直接 fail 整个 install,不要在可选功能上滥用;但必填项(如镜像仓库、域名)务必用 required 或 values.schema.json 卡住,否则默认值缺失会导致部署出不可用的资源,而不是快速失败。
helper 复用:_helpers.tpl 与 include
重复出现的 labels、name 应该用 define 抽成 helper,放在 templates/_helpers.tpl(以下划线开头,Helm 不会把它当独立资源渲染)。include 比 template 更好用,因为它能配合管道与 nindent:
yaml
# templates/_helpers.tpl
{{- define "mychart.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}1
2
3
4
5
6
2
3
4
5
6
yaml
# 在任意模板里复用,并指定缩进
metadata:
labels:
{{- include "mychart.labels" . | nindent 4 }}1
2
3
4
2
3
4
values 校验:values.schema.json
Helm 3 支持用 JSON Schema 约束 values.yaml,在 helm install/upgrade 前自动校验,比 required 函数更结构化:
json
{
"$schema": "https://json-schema.org/draft-07/schema",
"type": "object",
"properties": {
"replicaCount": { "type": "integer", "minimum": 1 },
"image": {
"type": "object",
"required": ["repository", "tag"],
"properties": {
"repository": { "type": "string" },
"tag": { "type": "string" }
}
}
},
"required": ["image"]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
经验判断
values.schema.json 适合卡"类型/必填/取值范围"这类结构化约束;业务逻辑相关、需要引用其他 values 的校验才用 required 函数。两者互补,CI 里再加 helm lint 兜底。
渲染流程图
常见失败模式与排障
nil pointer evaluating interface {}.xxx:访问了不存在的嵌套 values(如.Values.image.tag但image未定义)。用default或在with/if里守卫。- 多余空行导致 YAML 解析失败:忘记
{{-/-}}去空白。用helm template输出后肉眼或yamllint检查。 function "xxx" not defined:误用了 Helm 不支持的函数;Helm 仅支持 Go 原生 + Sprig 子集,自造函数需注册插件。--set与values.yaml类型冲突:--set a.b=1是数字,YAML 里写"1"是字符串,合并后类型不一致。生产优先用-f文件覆盖,少用--set。- 循环变量捕获错误:
range内.Release.Name需用$rel := .Release.Name提前捕获,否则.已被改写。
版本相关
Sprig 函数集合在不同 Helm 3 小版本间基本稳定,但个别函数(如 semverCompare)依赖 Helm 内部 Sprig 版本。若用到冷门函数,建议在目标 Helm 版本上实测。
生产实践建议
- CI 门禁三连:
helm lint→helm template --dry-run→ 服务端kubectl apply --dry-run=server,任一失败阻断发布。 - 所有可选字段
default化,必填字段required或 schema 卡死。 - labels / name 集中到
_helpers.tpl,避免散落不一致。 - 少用
--set,多用版本化的-f values-env.yaml,让每次变更都可被审计。 - 复杂逻辑下沉到
tpl/include,保持具体资源模板可读。
FAQ
Q:include 和 template 有什么区别? A:template 是 Go 原生动作,不能直接接管道;include 是 Helm 扩展的函数,返回值可接管道(如 | nindent 4),生产基本都用 include。
Q:模板里能调用 kubectl 或外部命令吗? A:不能。Helm 模板是纯声明式渲染,无法在渲染期访问集群。需要集群信息的动态逻辑应放在 post-install/pre-upgrade hook 或外部控制器里。
参考资料
- Helm Chart Template Guide(官方文档),访问日期:2026-10-08。
- Helm Template Function List / Sprig(官方文档),访问日期:2026-10-08(注:官方页部分版本返回异常,函数清单以 Helm 内置 Sprig 子集为准,建议在目标版本
helm template实测)。 - Helm Charts: values.schema.json(官方文档),访问日期:2026-10-08。