[Helm] template, include, tplの使い分け
Helmにおけるテンプレートを別ファイルに分ける仕組み
Helmで0とdefaultを区別するに引き続きHelmの話題です。 Helmでテンプレートを記述するとき、基本的には以前の記事のようにマニフェストのYAMLファイル内に直接記載しますが、時にはテンプレートを別のファイルに分けたい場合があります。
例えば、複数マニフェスト間で共通のテンプレートを使いたい、ConfigMap/Secretを構成する設定内容を別のファイルで管理したいといった動機があります。
このとき、Helmではいくつか選択肢があり、使いわけに躓いたのでまとめます。
要約すると、
| 方法 | 使い分け |
|---|---|
| template | テンプレートを処理の内容で識別したい場合。ただし、基本的にincludeで良い |
| include | template に加えてテンプレート描画結果に対して追加の処理を行いたい場合 |
| tpl | テンプレートをファイル名で識別したい場合 |
と考えています。 以下に詳しく違いをまとめていきます。
template と include
helm create でchartを作成した際に生成されるテンプレートに例があるので見てみましょう。
helm create template_test として作成したchartにある templates/_helpers.tpl 1 を見ると、
{{- define "template_test.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
という部分があります。これにより、 define ~ end 間のテンプレートを template_test.chart という名前でinclude、または template で参照可能になります。このテンプレートでは、
.Chart.Nameでchart名(今回はtemplate_test).Chart.Versionでchartのバージョン(Chart.yamlに記載の値。初期値は0.1.0)
を展開します。 適当なConfigMapを作って試してみると、
apiVersion: v1
kind: ConfigMap
data:
chart_name: |
{{ include "template_test.chart" . }}
{{ template "template_test.chart" . }}
$ helm template ./template_test/ -s templates/sample_cm.yaml # -sでsample_cm.yamlのみ展開する
---
# Source: template_test/templates/sample_cm.yaml
apiVersion: v1
kind: ConfigMap
data:
chart_name: |
template_test-0.1.0
template_test-0.1.0
のように展開されます。
ところで、 includeと template の違いは何でしょうか。この疑問の答えはドキュメント上に用意されていて、
公式ドキュメント によれば、
Because template is an action, and not a function, there is no way to pass the output of a template call to other functions; the data is simply inserted inline.
template は関数とは別で、いわば直接そのテンプレートを埋め込むような動作をするので | で他の関数をつなげることができないが、include だとそれが可能、ということのようです。
例えば、ドキュメントでも触れられていますが、
It is considered preferable to use include over template in Helm templates simply so that the output formatting can be handled better for YAML documents.
{{ include "test_chart.chart" . | indent 2 }} としてインデントを調整する、といったことが可能なので、template より include を使う方が無難と思います。
tpl
tpl は template の略称に見えるので、私は最初 template のエイリアスかと思ったのですが、違いました。
公式ドキュメントによると、「テンプレート文字列を引数にとってテンプレートを描画するもの」です。
ドキュメント中の例が簡潔でよいのですが、
- テンプレート自体を
values.yamlで指定して動的に与える {{ tpl (.Files.Get "foo.conf") . }}のようにすることで、foo.confの内容をテンプレートとして処理する
といったことが可能になります。
特に2つ目の内容は
テンプレートの参照方法が( _helpers.tpl 内の) define で定義した名前か、ファイル名か、という点で異なっていますが、ファイルに共通テンプレートを分けて読み込む、という点では include や template に似ています。
tpl と include
ここで、tpl と include のどちらを採用すればいいのでしょうか。
tpl で読み込む単位はファイル単位で、 include で読み込む場合は define で定義した単位で参照するので、
ConfigMap/Secretを構成する設定ファイルを別ファイルに切り出したい場合には、 tpl を使うとchart内でどこにアプリケーション設定が管理されているかわかりやすそうです。
一方、複数テンプレートで共通の処理に対して名前をつけて識別したいといった場合は、_helpers.tpl に記載しておけば汎用ライブラリファイルとして扱えてよさそうです。
とはいえ他方の方法でも実現可能で、あくまでファイル運用の違いなので、結局のところあくまで好みの問題なのでしょうか。
ところが、実は仕様上 include にできるが tpl にできないことがあります。
(細かいユースケースなので、多くの場合はむしろchartの構成を見直した方がよさそうですが)
include にできるが tpl にできないこと
例えば、次のような values.yaml で、teachers, students問わず各々nameとroomを出力したいということを考えます。
teachers:
- name: "alice"
room: "A"
- name: "bob"
room: "B"
students:
- name: "for"
room: "A"
- name: "bar"
room: "B"
これは、例えば次のようなテンプレートを使えば得られます。
{{- define "template_test.school.people_info" -}}
{{- range . }}
{{- printf "name=%s, room=%s" .name .room }}
{{ end }}
{{- end }}
apiVersion: v1
kind: ConfigMap
data:
teachers: |
{{ include "template_test.school.people_info" .Values.teachers | indent 4 }}
students: |
{{ include "template_test.school.people_info" .Values.students | indent 4 }}
$ helm template ./template_test/ -s templates/sample_cm.yaml # -sでsample_cm.yamlのみ展開する
---
# Source: template_test/templates/sample_cm.yaml
apiVersion: v1
kind: ConfigMap
data:
teachers: |
name=alice, room=A
name=bob, room=B
students: |
name=for, room=A
name=bar, room=B
include の第2引数に使う . はテンプレートに渡す変数を表しているので、 .Values.teachers のように部分的に別テンプレートに渡すことが可能です。
一見これは tpl でも同じようなことができそうに思われたのですが、それはできません。
同じテンプレートを用意して実行してみると、
{{- range . }}
{{- printf "name=%s, room=%s" .name .room }}
{{- end }}
apiVersion: v1
kind: ConfigMap
data:
teachers: |
{{- tpl (.Files.Get "school_people_info.conf") .Values.teachers | indent 4 }}
エラーになります。
$ helm template ./template_test/
Error: template: template_test/templates/sample_cm.yaml:20:55: executing "template_test/templates/sample_cm.yaml" at <.Values.teachers>: wrong type for value; expected chartutil.Values; got []interface {}
Use --debug flag to render out invalid YAML
これは(エラー文は違いますが) https://github.com/helm/helm/issues/5979 にあるように、tpl が実は $ を渡すことを想定しているからです。
$ というのは
(ドキュメントより)
there is one variable that is always global - $ - this variable will always point to the root context.
変数構造の最上位を表すものです。この下には Values や冒頭のテンプレートで使った Chart 、Files 等が属しています。
{{ tpl (.Files.Get "foo.conf") . }}
はissueで触れられている range のように . が指すものを $ から変えるものの中では成り立ちません。 2
そのため、tpl で今回のようにuser, roomの単位でstudents, teachersを同一視するような、 values.yaml 内の部分的な構造に応じて共通化することはできないようです。
そもそも、tpl は
テンプレート自体を
values.yamlで指定して動的に与える
機能も持っているため、tpl の仕様としてはむしろ $ 前提の方が都合がよさそうです。
まとめ
Helmでテンプレートを分けるための構文の使い分けについて、再掲すると以下のようになります。
| 方法 | 使い分け |
|---|---|
| template | テンプレートを処理の内容で識別したい場合。ただし、基本的にincludeで良い |
| include | template に加えてテンプレート描画結果に対して追加の処理を行いたい場合 |
| tpl | テンプレートをファイル名で識別したい場合 |
template と include については template にできて include にできないことがないように思われるので、公式ドキュメントにあるように include を使うことが無難にみえます。
一方 template でなければならない場合があるのかどうかは不明で、気になっています。
tpl と include については include にできて tpl にできないことがありますが、特殊な例だと思うのでそれを意識する際はchartの設計を見直すサインの1つだと思います。
そのため、ファイル単位で分けてそのファイル名に意味を持たせたい場合3は tpl を、そうでなく、処理に対して名前付けして識別したい場合は include といった指針がよさそうです。
-
https://helm.sh/docs/howto/charts_tips_and_tricks/#using-partials-and-template-includes より、慣習として
_helpers.tplというファイルに共通テンプレートを書きますが、templates/以下の_(underscore)から始まるファイルであればマニフェストとして展開されないので、_helpers.tplというファイル名である必要はなさそうです。 ↩︎ -
includeの場合でも_(underscore) から始まる名前であればファイルを複数に分けることは可能ですが、その場合でも ファイル内でdefineした識別子を使うことに変わりはなく、tplと扱い方が異なっています。 ↩︎