[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
と扱い方が異なっています。 ↩︎