跳转至

命名模板

前面我们都是只操作的一个模板,现在我们来尝试使用多个模板文件。在本节中,我们可以了解到如何在一个文件中定义命名模板,然后在其他地方使用它们。命名模板(有时也叫子模板)只是在文件内部定义的有名称的模板。主要有两种创建方式以及几种不同的使用方式。

当使用命名模板的时候有几个重要细节:模板名称是全局的,如果声明两个具有相同名称的模板,则会使用最后被加载的模板。由于子 chart 中的模板是与顶级模板一起编译的,所以需要谨慎命名。

一种流行的命名约定是在每个定义的模板前添加 chart 名称:{{ define "mychart.labels" }},通过使用特定的 chart 名作为前缀,我们可以避免由于两个不同的 chart 实现了相同名称的模板而引起的冲突。

partials 和 _ 文件

到目前为止,我们只使用了一个模板文件,但是 Helm 的模板语言允许我们创建命名的嵌入式模板,可以在其他位置进行访问。在编写这些模板之前,有一些值得一提的命名约定:

  • templates/ 中的大多数文件都被视为 Kubernetes 资源清单文件(NOTES.txt 除外)
  • _ 开头命名的文件也不会被当做 Kubernetes 资源清单文件
  • 下划线开头的文件不会被当做资源清单之外,还可以被其他 chart 模板调用

_ 开头的这些文件其实就是 Helm 中的 partials 文件,所以其实我们完全可以将命名模板定义在这些 partials 文件中,默认就是 _helpers.tpl 文件,其实在前面我们创建的 mychart 包中也可以找到这个文件。

definetemplate

define 关键字可以让我们在模板文件中创建命名模板,它的语法如下所示:

{{ define "MY.NAME" }}
  # 模板内容区域
{{ end }}

比如我们可以定义一个模板来封装下 Kubernetes 的 labels 标签:

{{- define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
{{- end }}

现在我们可以将该模板嵌入到前面的 ConfigMap 模板中,然后将其包含在模板中:

{{- define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
{{- end }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  {{- template "mychart.labels" }}
data:
  myvalue: "Hello World"
  {{- range $key, $val := .Values.favorite }}
  {{ $key }}: {{ $val | quote }}
  {{- end }}

当模板引擎读取这个文件的时候,它会存储 mychart.labels 的引用,直到该模板被调用,然后会内联渲染该模板。我们渲染这个模板可以都到如下所示的结果(记得先删掉默认生成的 _helpers.tpl 文件):

# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mychart-1576034036-configmap
  labels:
    generator: helm
    date: 2019-12-11
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "pizza"

一般来说,Helm 中约定将这些模板统一放到一个 partials 文件中,通常就是 _helpers.tpl 文件中,我们将上面的命名模板移动到该文件(templates/_helpers.tpl)中去:

{{/* 生成基本的 Label 标签 */}}
{{- define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
{{- end }}

一般来说,我们也会用一个简单的块({{/*...*/}})来注释这个命名模板的作用。

现在虽然我们把命名模板放到了 _helpers.tpl 文件中,但是我们在 configmap.yaml 模板中还是可以访问,因为命名模板是全局的:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  {{- template "mychart.labels" }}
data:
  myvalue: "Hello World"
  {{- range $key, $val := .Values.favorite }}
  {{ $key }}: {{ $val | quote }}
  {{- end }}

因为上面我们提到过命名模板是全局的,我们可以再渲染下上面的模板可以得到正确的结果。

设置模板范围

上面我们定义的模板中,还没有使用到任何对象,只使用了函数,现在我们来修改下定义的命名模板,包含 chart 的名称和版本:

{{/* 生成基本的 Label 标签 */}}
{{- define "mychart.labels" }}
  labels:
    generator: helm
    date: {{ now | htmlDate }}
    chart: {{ .Chart.Name }}
    version: {{ .Chart.Version }}
{{- end }}

现在我们来渲染下模板,会出现下面的错误:

$ helm install --generate-name --dry-run --debug ./my
chart
install.go:148: [debug] Original chart version: ""
install.go:165: [debug] CHART PATH: /Users/ych/devs/workspace/yidianzhishi/cour
se/k8strain/content/helm/manifests/mychart

Error: unable to build kubernetes objects from release manifest: error validati
ng "": error validating data: [unknown object type "nil" in ConfigMap.metadata.
labels.chart, unknown object type "nil" in ConfigMap.metadata.labels.version]
helm.go:76: [debug] error validating "": error validating data: [unknown object
 type "nil" in ConfigMap.metadata.labels.chart, unknown object type "nil" in Co
nfigMap.metadata.labels.version]
......

我们可以看到提示 labels.chartnil,这是因为我们使用的 .Chart.Name 不在定义的这个模板的作用域范围内,当渲染命名模板(使用 define 定义)的时候,它将接收模板调用传递的作用域。在我们这个示例中,我们是这样引用这个模板的:

{{- template "mychart.labels" }}

没有传入任何作用域,所以在模板内我们无法访问 . 中的任何内容,当然要解决很简单,我们只需要把作用域范围传递给模板即可:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  {{- template "mychart.labels" . }}
......

我们这里在使用 template 调用模板的时候传递了 .,我们可以很容易传递 .Values 或者 .Values.favorite 或者我们想要的任何范围,但是这里我们想要的是顶级作用域,所以我们传递的是 .

现在我们再来重新渲染我们的模板,可以得到如下所示的结果:

# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mychart-1576035668-configmap
  labels:
    generator: helm
    date: 2019-12-11
    chart: mychart
    version: 0.1.0
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "pizza"

现在 {{ .Chart.Name }} 解析为了 mychart,而 {{ .Chart.Version }} 解析为了 0.1.0

include 函数

假设我们定义了一个如下所示的简单模板:

{{- define "mychart.app" -}}
app_name: {{ .Chart.Name }}
app_version: "{{ .Chart.Version }}"
{{- end -}}

现在我们想把上面的内容插入到模板的 labels 部分,在 data 部分也想要这个内容:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  labels:
    {{ template "mychart.app" . }}
data:
  myvalue: "Hello World"
  {{- range $key, $val := .Values.favorite }}
  {{ $key }}: {{ $val | quote }}
  {{- end }}
{{ template "mychart.app" . }}

但是我们直接渲染上面的模板还是会有错误:

$ helm install --generate-name --dry-run --debug ./my
chart
install.go:148: [debug] Original chart version: ""
install.go:165: [debug] CHART PATH: /Users/ych/devs/workspace/yidianzhishi/cour
se/k8strain/content/helm/manifests/mychart

Error: unable to build kubernetes objects from release manifest: error validati
ng "": error validating data: [ValidationError(ConfigMap): unknown field "app_n
ame" in io.k8s.api.core.v1.ConfigMap, ValidationError(ConfigMap): unknown field
 "app_version" in io.k8s.api.core.v1.ConfigMap]
helm.go:76: [debug] error validating "": error validating data: [ValidationErro
r(ConfigMap): unknown field "app_name" in io.k8s.api.core.v1.ConfigMap, Validat
ionError(ConfigMap): unknown field "app_version" in io.k8s.api.core.v1.ConfigMap]
......

因为 template 只是一个动作,而不是一个函数,所以无法将模板调用的输出传递给其他函数,只是内联插入,相当于渲染的结果是这样的:

apiVersion: v1
kind: ConfigMap
metadata:
  name: measly-whippet-configmap
  labels:
    app_name: mychart
app_version: "0.1.0+1478129847"
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "pizza"
  app_name: mychart
app_version: "0.1.0+1478129847"

很明显上面的 YAML 文件是不符合 ConfigMap 资源对象的格式要求的,所以报错了。为解决这个问题,Helm 提供了代替 template 的函数 include,可以将模板的内容导入到当前的管道中,这样就可以在管道中传递给其他函数进行处理了,如下所示:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}-configmap
  labels:
{{ include "mychart.app" . | indent 4 }}
data:
  myvalue: "Hello World"
  {{- range $key, $val := .Values.favorite }}
  {{ $key }}: {{ $val | quote }}
  {{- end }}
{{ include "mychart.app" . | indent 2 }}

现在我们重新渲染就可以得到正确的结果了,这是因为我们用 include 函数得到模板内容后通过管道传给了后面的 indent 函数来保证了缩进:

 Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: mychart-1576036671-configmap
  labels:
    app_name: mychart
    app_version: "0.1.0"
data:
  myvalue: "Hello World"
  drink: "coffee"
  food: "pizza"
  app_name: mychart
  app_version: "0.1.0"

建议

在 Helm 模板中最好使用 include 而不是 template,这样可以更好地处理 YAML 文档的输出格式。

有时候如果我们只想导入内容而不是模板,这个时候我们可以通过下面描述的 .Files 对象来访问文件实现。