テンプレート関数でvaluesを安全に差し込む
目標
テンプレート関数でvaluesを安全にマニフェストへ差し込み、値がないとき・必須のとき・ブロックのときに、それぞれどんな道具を使うべきかを、手で身につけます。
なぜ重要なのか
Helmテンプレートは、YAMLエディターではなく文字列ジェネレーターです。最終結果が文字列として完成してからはじめて、YAMLパーサーがそれを読みます。そのため、インデントが2スペースずれると、エラーが出る代わりに、ブロックが丸ごと消えたり、見当違いの親の下に入ったりします。toYamlで展開したブロックにnindentを必ず付ける理由が、ここにあります。indentは前の改行を作ってくれないので、最初の行が前のキーにそのままくっつきます。値の設計も同じです。defaultは「なくてもよい値」に、requiredは「なければデプロイしてはいけない値」に使います。この2つを逆にすると、誤ったデフォルト値で静かにデプロイされるか、逆に誰にも使えないチャートになります。最後に、値の優先度は、低いものから、チャートのvalues.yaml、-fで渡したファイル群(左から右)、--setの順であり、マップは深くマージされますが、リストは丸ごと置き換えられるという点を、必ず覚えておいてください。
ステップ
/root/helm/tpl/labhub-apiにチャートを作成してください(helm create labhub-apiから始めても構いません)。values.yamlのimage.repositoryはnginx、image.tagは"1.27"にし、Chart.yamlのappVersionは"1.26"にします。そのあと、helm template labhub-api /root/helm/tpl/labhub-api > /root/helm/tpl/out/base.yamlでレンダリングを保存してください。結果にDeploymentがあり、コンテナイメージが저장소:태그(プレースホルダーはリポジトリとタグです)の形式である必要があり、波括弧や<no value>が残っていてはいけません。- コンテナイメージのタグを
{{ .Values.image.tag | default .Chart.AppVersion }}のように書き、helm template labhub-api /root/helm/tpl/labhub-api --set image.tag="" > /root/helm/tpl/out/default.yamlを保存してください。タグを空にしたのに、イメージにはnginx:1.26のようにタグが付いている必要があり、デフォルト値としてlatestを使ってはいけません。 values.yamlにingress.enabled: trueとingress.host: api.labhub.localを置き、/root/helm/tpl/labhub-api/templates/ingress.yamlで、ホストをrequiredで囲んでください(例:{{ required "ingress.host 를 반드시 지정하세요" .Values.ingress.host }}。韓国語の文は「ingress.hostを必ず指定してください」という意味です)。そのあと、helm template labhub-api /root/helm/tpl/labhub-api --set ingress.host="" > /root/helm/tpl/out/required-error.txt 2>&1でわざと失敗させて、その出力を保存してください。ファイルには、レンダリングの失敗メッセージと一緒に、どのキーが欠けているかが見える必要があります。values.yamlのresourcesを、limits.cpu: 500m、limits.memory: 512Mi、requests.cpu: 100m、requests.memory: 128Miで埋め、Deploymentのコンテナで{{- toYaml .Values.resources | nindent 12 }}の形で、丸ごと渡してください。/root/helm/tpl/out/base.yamlをもう一度レンダリングすると、コンテナのresources.limits.cpuとresources.requests.memoryが見える必要があります。values.yamlにenvをマップとして置き、APP_MODE: server、LOG_LEVEL: info、TZ: Asia/Seoulの3つを定義してください。Deploymentでrange $k, $v := .Values.envを使って、コンテナのenvのリストを作ります。/root/helm/tpl/out/base.yamlのコンテナのenvはちょうど3つで、そのうちの1つの名前がLOG_LEVELである必要があります。/root/helm/tpl/labhub-api/templates/_helpers.tplに、defineでチャートの識別文字列(名前-バージョン)を作り、Deploymentのmetadata.annotationsにlabhub.io/chart: {{ include "labhub-api.chart" . }}を付けてください。そして、/root/helm/tpl/out/include-note.txtに、templateとincludeの違いを1行で書いてください。includeは結果を文字列として返すので、パイプでつないで後処理(インデント)ができる、という内容が入っている必要があります。/root/helm/tpl/values-base.yamlにreplicaCount: 2、env.LOG_LEVEL: info、image.tag: baseを、/root/helm/tpl/values-stage.yamlにreplicaCount: 4、env.LOG_LEVEL: debug、image.tag: stageを置いてください。そのあと、helm template labhub-api /root/helm/tpl/labhub-api -f /root/helm/tpl/values-base.yaml -f /root/helm/tpl/values-stage.yaml --set image.tag=cli > /root/helm/tpl/out/merged.yamlを実行してください。結果のreplicasは4、LOG_LEVELはdebug、イメージタグはcliである必要があります。最後に、/root/helm/tpl/out/precedence.txtに、優先度を低いものから1行ずつ書いてください: チャートのvalues.yaml、-f values-base.yaml、-f values-stage.yaml、--set。- ConfigMapテンプレートを追加して、
values.yamlのconfigマップをdataとして出力し、Podテンプレートのmetadata.annotationsにchecksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}を付けてください。Podレベルのspec.template.spec.securityContext.runAsNonRootは、trueになる必要があります。完成したレンダリングを/root/helm/tpl/out/final.yamlに保存してください(オブジェクトが3つ以上、コンテナのenvが3つ以上、<no value>なし)。そして、helm lint /root/helm/tpl/labhub-api > /root/helm/tpl/out/lint.txtも保存し、[ERROR]がない必要があります。
参考
- ラボのPodはラボごとに新しく起動するので、ほかのラボで作ったチャートは残っていません。このラボのチャートも、
/root/helm/tplの下に最初から作ります。チャートがそのまま再現可能なパッケージであるという事実が、ここで現れます。 helm template ... -s templates/deployment.yamlで1つのファイルだけを見られ、--debugを付けると、失敗したレンダリングの途中結果まで見せてくれます。- ステップ4・5・6でテンプレートを直したあとは、
/root/helm/tpl/out/base.yamlを必ずもう一度レンダリングして上書きしてください。ステップ1・4・5・6の採点は、すべてこの1つのファイルを見ます。 - よくある間違い1:
indentを使って、ブロックの最初の行が前のキーにくっつくことです。前に改行が必要ならnindentです。 - よくある間違い2: 値ファイルを2つ渡せば、リストもマージされると期待することです。マップは深くマージされますが、リストは丸ごと置き換えられます。このラボで
envをリストではなくマップで設計した理由です。
valuesを参照してレンダリングする
/root/helm/tpl/labhub-apiにチャートを作成してください(helm create labhub-apiから始めても構いません)。values.yamlのimage.repositoryはnginx、image.tagは"1.27"にし、Chart.yamlのappVersionは"1.26"にします。そのあと、helm template labhub-api /root/helm/tpl/labhub-api > /root/helm/tpl/out/base.yamlでレンダリングを保存してください。結果にDeploymentがあり、コンテナイメージが저장소:태그(プレースホルダーはリポジトリとタグです)の形式である必要があり、波括弧や<no value>が残っていてはいけません。
イメージは、リポジトリとタグをそれぞれvaluesから取得して組み合わせます。レンダリング結果に波括弧や「」が残っていたら、参照したキーがvaluesにないという意味です。
defaultで空の値を埋める
コンテナイメージのタグを{{ .Values.image.tag | default .Chart.AppVersion }}のように書き、helm template labhub-api /root/helm/tpl/labhub-api --set image.tag="" > /root/helm/tpl/out/default.yamlを保存してください。タグを空にしたのに、イメージにはnginx:1.26のようにタグが付いている必要があり、デフォルト値としてlatestを使ってはいけません。
タグを空にしてレンダリングしても、イメージにタグが付く必要があります。代わりに使う値をChart.yamlから取得すれば、チャートとアプリのバージョンが自然に合います。latestは答えではありません。
requiredで必須の値を強制する
values.yamlにingress.enabled: trueとingress.host: api.labhub.localを置き、/root/helm/tpl/labhub-api/templates/ingress.yamlで、ホストをrequiredで囲んでください(例: {{ required "ingress.host 를 반드시 지정하세요" .Values.ingress.host }}。韓国語の文は「ingress.hostを必ず指定してください」という意味です)。そのあと、helm template labhub-api /root/helm/tpl/labhub-api --set ingress.host="" > /root/helm/tpl/out/required-error.txt 2>&1でわざと失敗させて、その出力を保存してください。ファイルには、レンダリングの失敗メッセージと一緒に、どのキーが欠けているかが見える必要があります。
必須の値が空なら、レンダリング自体が失敗する必要があります。エラーメッセージには、どのキーが欠けているかを書いてください。失敗の出力は標準エラー出力に出るので、保存するときに一緒に受け取る必要があります。
toYamlとnindentでブロックを渡す
values.yamlのresourcesを、limits.cpu: 500m、limits.memory: 512Mi、requests.cpu: 100m、requests.memory: 128Miで埋め、Deploymentのコンテナで{{- toYaml .Values.resources | nindent 12 }}の形で、丸ごと渡してください。/root/helm/tpl/out/base.yamlをもう一度レンダリングすると、コンテナのresources.limits.cpuとresources.requests.memoryが見える必要があります。
リソース制限のように丸ごと渡すブロックは、1行ずつ書きません。前に改行が必要かどうかが、2つのインデント関数の違いです。
rangeで環境変数を展開する
values.yamlにenvをマップとして置き、APP_MODE: server、LOG_LEVEL: info、TZ: Asia/Seoulの3つを定義してください。Deploymentでrange $k, $v := .Values.envを使って、コンテナのenvのリストを作ります。/root/helm/tpl/out/base.yamlのコンテナのenvはちょうど3つで、そのうちの1つの名前がLOG_LEVELである必要があります。
valuesのマップを、キーと値の2つの変数で受け取って繰り返します。値は引用符で囲むのが安全です。ちょうど3つが出力される必要があります。
名前付きテンプレートを定義してincludeする
/root/helm/tpl/labhub-api/templates/_helpers.tplに、defineでチャートの識別文字列(名前-バージョン)を作り、Deploymentのmetadata.annotationsにlabhub.io/chart: {{ include "labhub-api.chart" . }}を付けてください。そして、/root/helm/tpl/out/include-note.txtに、templateとincludeの違いを1行で書いてください。includeは結果を文字列として返すので、パイプでつないで後処理(インデント)ができる、という内容が入っている必要があります。
defineで作った断片を、アノテーションの場所に差し込みます。2つの呼び出し方のうち、パイプでつなげて書けるのはどちらか、その理由は何かを、メモに残してください。
値の優先度を確認する
/root/helm/tpl/values-base.yamlにreplicaCount: 2、env.LOG_LEVEL: info、image.tag: baseを、/root/helm/tpl/values-stage.yamlにreplicaCount: 4、env.LOG_LEVEL: debug、image.tag: stageを置いてください。そのあと、helm template labhub-api /root/helm/tpl/labhub-api -f /root/helm/tpl/values-base.yaml -f /root/helm/tpl/values-stage.yaml --set image.tag=cli > /root/helm/tpl/out/merged.yamlを実行してください。結果のreplicasは4、LOG_LEVELはdebug、イメージタグはcliである必要があります。最後に、/root/helm/tpl/out/precedence.txtに、優先度を低いものから1行ずつ書いてください: チャートのvalues.yaml、-f values-base.yaml、-f values-stage.yaml、--set。
値ファイル2つとコマンドラインのオプションを、一度に掛けてみてください。値ファイルは、渡した順序に意味があります。結果を見て、低いものから順序を書いてください。
設定ハッシュとセキュリティコンテキストまで付ける
ConfigMapテンプレートを追加して、values.yamlのconfigマップをdataとして出力し、Podテンプレートのmetadata.annotationsにchecksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}を付けてください。Podレベルのspec.template.spec.securityContext.runAsNonRootは、trueになる必要があります。完成したレンダリングを/root/helm/tpl/out/final.yamlに保存してください(オブジェクトが3つ以上、コンテナのenvが3つ以上、<no value>なし)。そして、helm lint /root/helm/tpl/labhub-api > /root/helm/tpl/out/lint.txtも保存し、[ERROR]がない必要があります。
ConfigMapの内容が変わってもPodが変わらない問題を防ぐ慣例があります。レンダリングされた設定ファイルのハッシュを、Podのアノテーションに入れてください。Podレベルのセキュリティ設定も一緒に埋めます。