スクエニ ITエンジニア ブログ

[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 }}

という部分があります。これにより、 defineend 間のテンプレートを 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

のように展開されます。

ところで、 includetemplate の違いは何でしょうか。この疑問の答えはドキュメント上に用意されていて、

公式ドキュメント によれば、

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

tpltemplate の略称に見えるので、私は最初 template のエイリアスかと思ったのですが、違いました。 公式ドキュメントによると、「テンプレート文字列を引数にとってテンプレートを描画するもの」です。

ドキュメント中の例が簡潔でよいのですが、

  • テンプレート自体を values.yaml で指定して動的に与える
  • {{ tpl (.Files.Get "foo.conf") . }} のようにすることで、 foo.conf の内容をテンプレートとして処理する

といったことが可能になります。

特に2つ目の内容は テンプレートの参照方法が( _helpers.tpl 内の) define で定義した名前か、ファイル名か、という点で異なっていますが、ファイルに共通テンプレートを分けて読み込む、という点では includetemplate に似ています。

tpl と include

ここで、tplinclude のどちらを採用すればいいのでしょうか。

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 や冒頭のテンプレートで使った ChartFiles 等が属しています。

{{ 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 テンプレートをファイル名で識別したい場合

templateinclude については template にできて include にできないことがないように思われるので、公式ドキュメントにあるように include を使うことが無難にみえます。
一方 template でなければならない場合があるのかどうかは不明で、気になっています。

tplinclude については include にできて tpl にできないことがありますが、特殊な例だと思うのでそれを意識する際はchartの設計を見直すサインの1つだと思います。

そのため、ファイル単位で分けてそのファイル名に意味を持たせたい場合3tpl を、そうでなく、処理に対して名前付けして識別したい場合は include といった指針がよさそうです。


  1. https://helm.sh/docs/howto/charts_tips_and_tricks/#using-partials-and-template-includes より、慣習として _helpers.tpl というファイルに共通テンプレートを書きますが、 templates/ 以下の _ (underscore)から始まるファイルであればマニフェストとして展開されないので、_helpers.tpl というファイル名である必要はなさそうです。 ↩︎

  2. with のように、. のスコープを意図的に変える処理もあります。 ↩︎

  3. include の場合でも _ (underscore) から始まる名前であればファイルを複数に分けることは可能ですが、その場合でも ファイル内で define した識別子を使うことに変わりはなく、 tpl と扱い方が異なっています。 ↩︎

この記事を書いた人

記事一覧
SQUARE ENIXでは一緒に働く仲間を募集しています!
興味をお持ちいただけたら、ぜひ採用情報ページもご覧下さい!