Chapter 05

_helpers.tpl 与命名模板

深入理解 Chart 中代码复用的核心机制:命名模板的定义与调用、标准 helper 函数的设计思路,以及 toYaml/nindent 的正确用法。

_helpers.tpl 约定

在 Helm Chart 中,以 下划线开头的模板文件(如 _helpers.tpl)有特殊含义:

不会渲染为 K8s 资源

_ 开头的文件 Helm 不会将其渲染为 K8s 资源提交给 API Server。它们仅作为命名模板的容器。

全局可访问

templates/ 目录下所有模板文件中定义的命名模板({{- define "xxx" -}})对其他所有模板文件都可见。

约定命名

命名模板名称约定为 "chartname.funcname" 格式,如 "myapp.fullname""myapp.labels",避免与其他 Chart 的模板冲突。

define / include / template 的差别

关键字来源返回值支持管道推荐场景
define Go text/template 定义模板,无返回值 定义命名模板
template Go text/template 直接输出(void) 不需要缩进控制时
include Helm 扩展 字符串(可管道) 绝大多数情况(推荐)
# ❌ template:无法控制缩进,输出可能错位
labels:
  {{ template "myapp.labels" . }}  # 无法添加 | nindent 4

# ✅ include:返回字符串,可通过管道控制缩进
labels:
  {{- include "myapp.labels" . | nindent 4 }}  # 正确缩进

标准 helpers:helm create 生成的 _helpers.tpl

{{/*
_helpers.tpl — helm create 生成的标准模板
*/}}

{{/*
myapp.name:Chart 名称,截断到 63 字符(K8s 资源名限制)
*/}}
{{- define "myapp.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
myapp.fullname:Release 名称 + Chart 名称的组合
避免同一集群多个 Release 的资源名冲突
*/}}
{{- define "myapp.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
myapp.chart:Chart 名称 + 版本,用于 helm.sh/chart label
*/}}
{{- define "myapp.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
myapp.labels:标准 K8s 推荐标签集
app.kubernetes.io/* 是官方推荐的标签前缀
*/}}
{{- define "myapp.labels" -}}
helm.sh/chart: {{ include "myapp.chart" . }}
{{ include "myapp.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
myapp.selectorLabels:Selector 用标签(不含版本信息)
Selector 一旦创建不可修改,所以不包含经常变化的版本标签
*/}}
{{- define "myapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
myapp.serviceAccountName:Service Account 名称
*/}}
{{- define "myapp.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "myapp.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

toYaml | nindent 的正确用法

toYaml 将 Go 结构体(map、slice)转为 YAML 字符串,配合 nindent 控制缩进层级。

# values.yaml 中定义复杂结构
resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 100m
    memory: 128Mi

tolerations:
  - key: node-role.kubernetes.io/spot
    operator: Equal
    value: "true"
    effect: NoSchedule
# 模板中使用 toYaml | nindent
spec:
  containers:
  - name: app
    {{- with .Values.resources }}       # 只有 resources 非空才输出
    resources:
      {{- toYaml . | nindent 6 }}      # . 在 with 块内指向 .Values.resources
    {{- end }}
  {{- with .Values.tolerations }}
  tolerations:
    {{- toYaml . | nindent 4 }}
  {{- end }}

自定义 Helper 函数

{{/*
myapp.imagePullPolicy:根据 tag 自动推断 pullPolicy
tag 为 latest 时用 Always,否则用 IfNotPresent
*/}}
{{- define "myapp.imagePullPolicy" -}}
{{- if eq .Values.image.tag "latest" }}
Always
{{- else }}
IfNotPresent
{{- end }}
{{- end }}

{{/*
myapp.env:合并全局环境变量和局部环境变量
*/}}
{{- define "myapp.env" -}}
{{- $env := merge (.Values.env | default dict) (.Values.global.env | default dict) }}
{{- range $key, $value := $env }}
- name: {{ $key | quote }}
  value: {{ $value | quote }}
{{- end }}
{{- end }}

{{/*
myapp.configChecksum:配置 ConfigMap 变更时触发 Pod 滚动重启
在 Deployment 的 annotations 中加入 configmap 的 hash 值
*/}}
{{- define "myapp.configChecksum" -}}
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- end }}

实战:统一的资源标签模板

将所有资源的标签统一管理,确保 K8s 推荐标签(app.kubernetes.io/*)一致性。

# _helpers.tpl — 完整的标签模板体系

{{/* 完整标签(用于 metadata.labels,含版本信息)*/}}
{{- define "myapp.labels" -}}
helm.sh/chart: {{ include "myapp.chart" . }}
{{- include "myapp.selectorLabels" . | nindent 0 }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- if .Values.global.env }}
environment: {{ .Values.global.env | quote }}
{{- end }}
{{- end }}

{{/* Selector 标签(不含版本,不可变)*/}}
{{- define "myapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/component: {{ .Values.component | default "web" | quote }}
{{- end }}
# deployment.yaml 中统一调用
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "myapp.fullname" . }}
  labels:                              # 完整标签
    {{- include "myapp.labels" . | nindent 4 }}
spec:
  selector:
    matchLabels:                       # Selector 标签(不含版本)
      {{- include "myapp.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:                          # Pod 标签
        {{- include "myapp.selectorLabels" . | nindent 8 }}
      annotations:                    # 配置变更自动重启
        {{- include "myapp.configChecksum" . | nindent 8 }}
为什么 selectorLabels 不含版本?

Deployment 的 spec.selector 一旦创建就不可修改(K8s 限制)。如果 selectorLabels 包含版本号,每次升级版本号变化都会导致 Selector 变更,从而使 helm upgrade 失败。因此 selectorLabels 只包含稳定不变的标签(名称 + 实例名)。

本章小结

_helpers.tpl 是 Chart 代码复用的核心。命名约定 "chartname.funcname" 避免冲突。始终使用 include 而非 template,因为 include 支持管道。标准 helpers 包括 fullname(资源名称)、labels(完整标签)、selectorLabels(选择器标签,不含版本)。toYaml | nindent N 是处理复杂 values 结构的标准写法。配置 checksum 注解是实现配置变更触发 Pod 滚动重启的优雅方案。