命名模板
现在是时候超越单个模板,开始创建其他模板了。在本节中,我们将了解如何在单个文件中定义命名模板,然后在其他地方使用它们。命名模板(有时称为部分或子模板)只是一个在文件中定义并被赋予名称的模板。我们将看到两种创建它们的方法,以及一些不同的使用它们的方法。
在流程控制部分,我们介绍了三种用于声明和管理模板的操作:define
、template
和 block
。在本节中,我们将介绍这三个操作,并介绍一个专门的include
函数,它的功能类似于template
操作。
在命名模板时需要注意一个重要细节:模板名称是全局的。如果你声明了两个具有相同名称的模板,那么最后加载的模板将是使用的模板。因为子图表中的模板与顶层模板一起编译,所以你应该谨慎地使用图表特定名称来命名模板。
一个流行的命名约定是在每个定义的模板前面加上图表的名称:{{ define "mychart.labels" }}
。通过使用图表名称作为前缀,我们可以避免由于两个不同的图表实现相同名称的模板而产生的任何冲突。
此行为也适用于图表的不同版本。如果你有定义了一个模板的mychart
版本1.0.0
,以及修改了现有命名模板的mychart
版本2.0.0
,它将使用最后加载的模板。你可以通过在图表名称中添加版本来解决此问题:{{ define "mychart.v1.labels" }}
和 {{ define "mychart.v2.labels" }}
。
部分和_
文件
到目前为止,我们只使用了一个文件,而且这个文件只包含一个模板。但是 Helm 的模板语言允许你创建命名嵌入模板,这些模板可以通过名称在其他地方访问。
在我们开始编写这些模板之前,有一个文件命名约定值得一提
templates/
中的大多数文件都被视为包含 Kubernetes 清单NOTES.txt
是一个例外- 但是,以下划线 (
_
) 开头的文件被假定为不包含清单。这些文件不会被渲染到 Kubernetes 对象定义中,但可以在其他图表模板中使用。
这些文件用于存储部分和辅助程序。实际上,当我们第一次创建mychart
时,我们看到一个名为_helpers.tpl
的文件。该文件是模板部分的默认位置。
使用define
和template
声明和使用模板
define
操作允许我们在模板文件中创建一个命名模板。它的语法如下
{{- define "MY.NAME" }}
# body of template here
{{- end }}
例如,我们可以定义一个模板来封装 Kubernetes 的标签块
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
{{- end }}
现在,我们可以将此模板嵌入到我们现有的 ConfigMap 中,然后使用template
操作包含它
{{- 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
的引用,直到调用template "mychart.labels"
。然后它将内联渲染该模板。所以结果看起来像这样
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: running-panda-configmap
labels:
generator: helm
date: 2016-11-02
data:
myvalue: "Hello World"
drink: "coffee"
food: "pizza"
注意:define
不会生成输出,除非它像本例一样被模板调用。
按照惯例,Helm 图表将这些模板放在一个部分文件中,通常是_helpers.tpl
。让我们将此函数移到那里
{{/* Generate basic labels */}}
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
{{- end }}
按照惯例,define
函数应该有一个简单的文档块({{/* ... */}}
)来描述它们的功能。
即使此定义位于_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 }}
如上所述,模板名称是全局的。因此,如果两个模板声明了相同的名称,则最后一次出现的模板将是使用的模板。由于子图表中的模板与顶层模板一起编译,因此最好使用图表特定名称来命名模板。一个流行的命名约定是在每个定义的模板前面加上图表的名称:{{ define "mychart.labels" }}
。
设置模板的范围
在上面定义的模板中,我们没有使用任何对象。我们只使用了函数。让我们修改我们定义的模板以包含图表名称和图表版本
{{/* Generate basic labels */}}
{{- define "mychart.labels" }}
labels:
generator: helm
date: {{ now | htmlDate }}
chart: {{ .Chart.Name }}
version: {{ .Chart.Version }}
{{- end }}
如果我们渲染它,我们将得到一个类似这样的错误
$ helm install --dry-run moldy-jaguar ./mychart
Error: unable to build kubernetes objects from release manifest: error validating "": error validating data: [unknown object type "nil" in ConfigMap.metadata.labels.chart, unknown object type "nil" in ConfigMap.metadata.labels.version]
要查看渲染的内容,请使用--disable-openapi-validation
重新运行:helm install --dry-run --disable-openapi-validation moldy-jaguar ./mychart
。结果将不是我们期望的
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: moldy-jaguar-configmap
labels:
generator: helm
date: 2021-03-06
chart:
version:
名称和版本发生了什么?它们不在我们定义的模板的范围内。当使用define
创建的命名模板被渲染时,它将接收由template
调用传递的范围。在我们的示例中,我们像这样包含了模板
{{- template "mychart.labels" }}
没有范围被传递,因此在模板中我们无法访问.
中的任何内容。不过,这很容易解决。我们只需将一个范围传递给模板
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
{{- template "mychart.labels" . }}
注意,我们在template
调用的末尾传递了.
。我们可以轻松地传递.Values
或.Values.favorite
,或者任何我们想要的范围。但是我们想要的是顶层范围。
现在,当我们使用helm install --dry-run --debug plinking-anaco ./mychart
执行此模板时,我们将得到以下结果
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: plinking-anaco-configmap
labels:
generator: helm
date: 2021-03-06
chart: mychart
version: 0.1.0
现在{{ .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 --dry-run measly-whippet ./mychart
Error: unable to build kubernetes objects from release manifest: error validating "": error validating data: [ValidationError(ConfigMap): unknown field "app_name" in io.k8s.api.core.v1.ConfigMap, ValidationError(ConfigMap): unknown field "app_version" in io.k8s.api.core.v1.ConfigMap]
要查看渲染的内容,请使用--disable-openapi-validation
重新运行:helm install --dry-run --disable-openapi-validation measly-whippet ./mychart
。输出将不是我们期望的
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: measly-whippet-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"
注意,app_version
的缩进在两个地方都是错误的。为什么?因为被替换的模板中的文本与左侧对齐。因为template
是一个操作,而不是一个函数,所以无法将template
调用的输出传递给其他函数;数据只是内联插入。
为了解决这个问题,Helm 提供了template
的替代方案,它将导入模板的内容到当前管道中,在那里可以将其传递给管道中的其他函数。
以下是使用indent
正确缩进mychart.app
模板的上述示例
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 }}
现在,生成的 YAML 针对每个部分进行了正确的缩进
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: edgy-mole-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
对象来实现这一点。