前面我們學習了一些 Helm 模板中的一些常用使用方法,但是我們都是操作的一個模板文件,在實際的應用中,很多都是相對比較復雜的,往往會超過一個模板,如果有多個應用模板,我們應該如何進行處理呢?這就需要用到新的概念:命名模板。
**命名模板我們也可以稱為子模板,是限定在一個文件內部的模板,然后給一個名稱。在使用命名模板的時候有一個需要特別注意的是:模板名稱是全局的**,如果我們聲明了兩個相同名稱的模板,最后加載的一個模板會覆蓋掉另外的模板,由于子 chart 中的模板也是和頂層的模板一起編譯的,所以在命名的時候一定要注意,不要重名了。
為了避免重名,有個通用的約定就是為每個定義的模板添加上 chart 名稱:`{{define "mychart.labels"}}`,`define`關鍵字就是用來聲明命名模板的,加上 chart 名稱就可以避免不同 chart 間的模板出現沖突的情況。
## 1. 聲明define和使用命名template
使用`define`關鍵字就可以允許我們在模板文件內部創建一個命名模板,它的語法格式如下:
```bash
{{ define "ChartName.TplName" }}
# 模板內容區域
{{ end }}
```
比如,現在我們可以定義一個模板來封裝一個 `label` 標簽:
```c
{{- define "mychart.labels" }}
labels:
from: helm
date: {{ now | htmlDate }}
{{- end }}
```
然后我們可以將該模板嵌入到現有的 `ConfigMap` 中,然后使用`template`關鍵字在需要的地方包含進來即可:
```bash
{{- define "mychart.labels" }}
labels:
from: helm
date: {{ now | htmlDate }}
{{- end }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
{{- template "mychart.labels" }}
data:
{{- range $key, $value := .Values.course }}
{{ $key }}: {{ $value | quote }}
{{- end }}
```
我們這個模板文件被渲染過后的結果如下所示:
$ helm install --dry-run --debug .
```bash
$ helm install --dry-run --debug .
[debug] Created tunnel using local port: '42058'
......
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: ardent-bunny-configmap
labels:
from: helm
date: 2018-09-22
data:
k8s: "devops"
python: "django"
```
我們可以看到define區域定義的命名模板被嵌入到了`template`所在的區域,但是如果我們將命名模板全都寫入到一個模板文件中的話無疑也會增大模板的復雜性。
還記得我們在創建 chart 包的時候,templates 目錄下面默認會生成一個`_helpers.tpl`文件嗎?我們前面也提到過 `templates` 目錄下面除了`NOTES.txt`文件和以下劃線`_`開頭命令的文件之外,都會被當做 kubernetes 的資源清單文件,而這個下劃線開頭的文件不會被當做資源清單外,還可以被其他 chart 模板中調用,這個就是 `Helm` 中的`partials`文件,所以其實我們完全就可以將命名模板定義在這些`partials`文件中,默認就是`_helpers.tpl`文件了。
現在我們將上面定義的命名模板移動到 `templates/_helpers.tpl` 文件中去:
```bash
{{/* 生成基本的 labels 標簽 */}}
{{- define "mychart.labels" }}
labels:
from: helm
date: {{ now | htmlDate }}
{{- end }}
```
一般情況下面,我們都會在命名模板頭部加一個簡單的文檔塊,用`/**/`包裹起來,用來描述我們這個命名模板的用途的。
現在我們講命名模板從模板文件 `templates/configmap.yaml` 中移除,當然還是需要保留 `template` 來嵌入命名模板內容,名稱還是之前的 `mychart.lables`,這是因為模板名稱是全局的,所以我們可以能夠直接獲取到。我們再用 `DEBUG` 模式來調試下是否符合預期?
### 示例:Creating Image Pull Secrets
假設憑證是在`values.yaml`文件中定義的,如下所示:
```bash
imageCredentials:
registry: quay.io
username: someone
password: sillyness
email: someone@host.com
```
然后我們定義我們的幫助模板如下:
```bash
{{- define "imagePullSecret" }}
{{- with .Values.imageCredentials }}
{{- printf "{\"auths\":{\"%s\":{\"username\":\"%s\",\"password\":\"%s\",\"email\":\"%s\",\"auth\":\"%s\"}}}" .registry .username .password .email (printf "%s:%s" .username .password | b64enc) | b64enc }}
{{- end }}
{{- end }}
```
最后,我們在更大的模板中使用輔助模板來創建 Secret 清單:
```bash
apiVersion: v1
kind: Secret
metadata:
name: myregistrykey
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: {{ template "imagePullSecret" . }}
```
## 2. 模板范圍
上面我們定義的命名模板中,沒有使用任何對象,只是使用了一個簡單的函數,如果我們在里面來使用 chart 對象相關信息呢:
```bash
{{/* 生成基本的 labels 標簽 */}}
{{- define "mychart.labels" }}
labels:
from: helm
date: {{ now | htmlDate }}
chart: {{ .Chart.Name }}
version: {{ .Chart.Version }}
{{- end }}
```
如果這樣的直接進行渲染測試的話,是不會得到我們的預期結果的:
```bash
$ $ helm install --dry-run --debug .
[debug] Created tunnel using local port: '42058'
......
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: peeking-zorse-configmap
labels:
from: helm
date: 2018-09-22
chart:
version:
data:
k8s: "devops"
python: "django"
```
chart 的名稱和版本都沒有正確被渲染,這是因為他們不在我們定義的模板范圍內,當命名模板被渲染時,它會接收由 template 調用時傳入的作用域,由于我們這里并沒有傳入對應的作用域,因此模板中我們無法調用到 .Chart 對象,要解決也非常簡單,我們只需要在 template 后面加上作用域范圍即可:
```bash
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
{{- template "mychart.labels" . }}
data:
{{- range $key, $value := .Values.course }}
{{ $key }}: {{ $value | quote }}
{{- end }}
```
我們在 `template` 末尾傳遞了.,表示當前的最頂層的作用范圍,如果我們想要在命名模板中使用.Values范圍內的數據,當然也是可以的,現在我們再來渲染下我們的模板:
```bash
$ helm install --dry-run --debug .
[debug] Created tunnel using local port: '32768'
......
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: oldfashioned-mule-configmap
labels:
from: helm
date: 2018-09-22
chart: mychart
version: 0.1.0
data:
k8s: "devops"
python: "django"
```
我們可以看到 chart 的名稱和版本號都已經被正常渲染出來了。
## 3. include 函數
假如現在我們將上面的定義的 labels 單獨提取出來放置到 `_helpers.tpl` 文件中:
```bash
{{/* 生成基本的 labels 標簽 */}}
{{- define "mychart.labels" }}
from: helm
date: {{ now | htmlDate }}
chart: {{ .Chart.Name }}
version: {{ .Chart.Version }}
{{- end }}
```
現在我們將該命名模板插入到 `configmap` 模板文件的 `labels` 部分和 `data` 部分:
```bash
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
labels:
{{- template "mychart.labels" . }}
data:
{{- range $key, $value := .Values.course }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- template "mychart.labels" . }}
```
然后同樣的查看下渲染的結果:
```bash
$ helm install --dry-run --debug .
[debug] Created tunnel using local port: '42652'
......
Error: YAML parse error on mychart/templates/configmap.yaml: error converting YAML to JSON: yaml: line 9: mapping values are not allowed in this context
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: altered-wombat-configmap
labels:
from: helm
date: 2018-09-22
chart: mychart
version: 0.1.0
data:
k8s: "devops"
python: "django"
from: helm
date: 2018-09-22
chart: mychart
version: 0.1.0
```
我們可以看到渲染結果是有問題的,不是一個正常的 YAML 文件格式,這是因為template只是表示一個嵌入動作而已,不是一個函數,所以原本命名模板中是怎樣的格式就是怎樣的格式被嵌入進來了,比如我們可以在命名模板中給內容區域都空了兩個空格,再來查看下渲染的結構:
```bash
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mortal-cricket-configmap
labels:
from: helm
date: 2018-09-22
chart: mychart
version: 0.1.0
data:
k8s: "devops"
python: "django"
from: helm
date: 2018-09-22
chart: mychart
version: 0.1.0
```
我們可以看到 data 區域里面的內容是渲染正確的,但是上面 labels 區域是不正常的,因為命名模板里面的內容是屬于 labels 標簽的,是不符合我們的預期的,但是我們又不可能再去把命名模板里面的內容再增加兩個空格,因為這樣的話 data 里面的格式又不符合預期了。
為了解決這個問題,Helm 提供了另外一個方案來代替`template`,那就是使用`include`函數,在需要控制空格的地方使用`indent`管道函數來自己控制,比如上面的例子我們替換成`include`函數:
```bash
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
labels:
{{- include "mychart.labels" . | indent 4 }}
data:
{{- range $key, $value := .Values.course }}
{{ $key }}: {{ $value | quote }}
{{- end }}
{{- include "mychart.labels" . | indent 2 }}
```
在 labels 區域我們需要4個空格,所以在管道函數indent中,傳入參數4就可以,而在 data 區域我們只需要2個空格,所以我們傳入參數2即可以,現在我們來渲染下我們這個模板看看是否符合預期呢:
```bash
$ helm install --dry-run --debug .
[debug] Created tunnel using local port: '38481'
......
---
# Source: mychart/templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: torpid-bobcat-configmap
labels:
from: helm
date: 2018-09-22
chart: mychart
version: 0.1.0
data:
k8s: "devops"
python: "django"
from: helm
date: 2018-09-22
chart: mychart
version: 0.1.0
```
可以看到是符合我們的預期,所以在 Helm 模板中我們使用 include 函數要比 template 更好,可以更好地處理 YAML 文件輸出格式。
## 4. required 函數
該`required`函數允許您根據模板渲染的需要聲明一個特定的值條目。如果該值為空,則模板渲染將失敗并顯示用戶提交的錯誤消息。
以下`required`函數示例聲明了一個條目 `for.Values.who`是必需的,并且在缺少該條目時將打印一條錯誤消息:
```bash
value: {{ required "A valid .Values.who entry required!" .Values.who }}
```
## 5. tpl 函數
該tpl函數允許開發人員將字符串評估為模板內的模板。這對于將模板字符串作為值傳遞給圖表或呈現外部配置文件很有用。句法:`{{ tpl TEMPLATE_STRING VALUES }}`
```bash
# values
template: "{{ .Values.name }}"
name: "Tom"
# template
{{ tpl .Values.template . }}
# output
Tom
```
渲染外部配置文件:
```bash
# external configuration file conf/app.conf
firstName={{ .Values.firstName }}
lastName={{ .Values.lastName }}
# values
firstName: Peter
lastName: Parker
# template
{{ tpl (.Files.Get "conf/app.conf") . }}
# output
firstName=Peter
lastName=Parker
```