TT Lab
はじめる
学ぶ 学習パス コース

Helmチャートの作成とデプロイ

レンダリング結果が YAML ではなかった — スコープと空白

TT Labで続きを見る

目標

with・rangeがドットを変えること、空白のトリムが行をくっつけてしまうこと、引用符を外した値が数値やブール値になることを、それぞれわざと起こしてみて、--debug・required・fail・helm lint --strictで防ぐ方法まで進みます。

なぜ重要なのか

テンプレートで起きる事故の大部分は、関数を知らないからではなく、結果がYAMLでなくなるために起きます。そのため、エラーメッセージが原因を指しません。mapping values are not allowed in this contextは、YAMLパーサーの言葉であって、テンプレートの言葉ではないので、その行をいくら見ても、-}}1つが犯人だということは見えません。同じ理由で、withの中で.Releaseがnilだというメッセージも、「ドットが変わった」という事実を知るまでは、読み解けません。ここに引用符の問題が重なると、レンダリングまではきちんとできて、クラスターが拒否します。デプロイパイプラインの最も遅い地点で爆発するタイプです。この4つを1回ずつ自分で作ってみれば、次からはメッセージ2行を見るだけで、どこを見るかを決められます。

ステップ

  1. /root/hc-scope/frontierチャート(名前frontier、バージョン0.1.0)を作成し、values.yamlに、app(name portal、team core、port 8080)、notes(owner sre、runbook wiki/portal)、envs(STAGE、PROD)、release(tag "1.10"、enabled "no")を置いてください。templates/base.yamlは、名前が.Values.app.nameで、data.portが引用符で囲んだポートのConfigMapです。helm template web /root/hc-scope/frontierの結果を/root/hc-scope/out/base.yamlに保存してください。
  2. /root/hc-scope/frontier/templates/scoped.yamlを作成してください。名前は<app.name>-scopedで、その下を{{- with .Values.app }}ブロックで囲み、その中でdata.releaseを{{ .Release.Name }}、data.teamを{{ .team }}と書きます。レンダリングすると失敗します。その出力を/root/hc-scope/out/with-error.txtに保存してください。
  3. /root/hc-scope/frontier/templates/scoped.yamlで、withブロックはそのままにして、data.releaseだけを直し、release名がレンダリングされるようにしてください。レンダリング結果を/root/hc-scope/out/scoped.yamlに保存します(release名はweb)。dataには、release: webとteam: coreの2行が出力される必要があります。
  4. /root/hc-scope/frontier/templates/envs.yamlを作成してください。名前は<app.name>-envsで、.Values.envsをインデックスと値の2つの変数で受け取って回りながら、dataに<소문자 환경이름>: "<인덱스>-<app.port>"(プレースホルダーは順に、小文字の環境名とインデックスです)を1行ずつ出力します。レンダリング結果に、stage: "0-8080"とprod: "1-8080"が出力される必要があります。結果を/root/hc-scope/out/envs.yamlに保存してください。
  5. /root/hc-scope/brokenチャート(名前broken)を別に作成し、values.yamlにapp(name portal、team core)とnotes(owner sre、runbook wiki/portal)を置いてください。templates/portal.yamlは、annotations:の下の行で{{- toYaml .Values.notes | indent 4 -}}でコメントを差し込み、そのあとにlabels:が来る形です。レンダリングすると失敗します。通常の出力は/root/hc-scope/out/ws-error.txtに、--debugを付けた出力は/root/hc-scope/out/ws-debug.txtに保存してください。このチャートは直さず、そのままにします。
  6. /root/hc-scope/brokenを/root/hc-scope/fixedにコピーし(チャート名はfixedに変えます)、templates/portal.yamlだけを直して、レンダリングが成功するようにしてください。コメントのブロックは、annotations:の下に4スペースインデントして2行で、labels.teamはcoreと出力される必要があります。結果を/root/hc-scope/out/fixed.yamlに保存してください。
  7. /root/hc-scope/frontier/templates/release.yamlを作成してください。名前は<릴리스이름>-release(プレースホルダーはrelease名です)、data.tagは.Values.release.tag、data.enabledは.Values.release.enabledを、引用符なしで差し込みます。このテンプレートだけをレンダリングして、yq -o=jsonを通した結果を/root/hc-scope/out/quote-yaml12.jsonに、そのレンダリングをkubectl apply --dry-run=serverに入れた出力を/root/hc-scope/out/quote-error.txtに保存してください。そのあと、2つの値にquoteを掛けて直し、もう一度サーバー検証を通過した出力を/root/hc-scope/out/quote-ok.txtに保存してください。
  8. /root/hc-scope/frontier/templates/guard.yamlを作成してください。ポートが1024未満ならfailで止め、data.teamはrequiredで値がないときに止めます。正常な値では、名前が<app.name>-guardのConfigMapが出力される必要があります。--set app.port=80でレンダリングした出力を/root/hc-scope/out/guard-fail.txtに、--set app.team=nullでレンダリングした出力を/root/hc-scope/out/guard-required.txtに保存し、オプションなしでレンダリングした全体の結果を/root/hc-scope/out/guard-ok.yamlに保存してください。
  9. /root/hc-scope/lintbadチャート(名前lintbad)を作成し、templates/cm.yamlのConfigMapの名前をBad_Nameにしてください。helm lintの出力と終了コードを/root/hc-scope/out/lint-plain.txtに、helm lint --strictの出力と終了コードを/root/hc-scope/out/lint-strict.txtに保存してください。2つのファイルの最後の行に、exit=<종료 코드>(プレースホルダーは終了コードです)を追記します。同じチャートなのに、通過するかどうかが分かれることが、このステップの答えです。

参考

値の構造を先に敷いて、一度レンダリングする

/root/hc-scope/frontierチャート(名前frontier、バージョン0.1.0)を作成し、values.yamlに、app(name portal、team core、port 8080)、notes(owner sre、runbook wiki/portal)、envs(STAGE、PROD)、release(tag "1.10"、enabled "no")を置いてください。templates/base.yamlは、名前が.Values.app.nameで、data.portが引用符で囲んだポートのConfigMapです。helm template web /root/hc-scope/frontierの結果を/root/hc-scope/out/base.yamlに保存してください。

helm createで作ると、スケルトンのテンプレートが一緒に付いてくるので、このラボでは、ディレクトリとファイルを直接作るほうがすっきりします。必要なのは、Chart.yaml、values.yaml、templates/の3つだけです。release名は、このラボの間ずっとwebを使います。

withの中で.Releaseが見つからない

/root/hc-scope/frontier/templates/scoped.yamlを作成してください。名前は<app.name>-scopedで、その下を{{- with .Values.app }}ブロックで囲み、その中でdata.releaseを{{ .Release.Name }}、data.teamを{{ .team }}と書きます。レンダリングすると失敗します。その出力を/root/hc-scope/out/with-error.txtに保存してください。

withは、条件が真のとき、ドット(.)が指す対象を変えます。ブロックの中でドットは、もはやルートではなく.Values.appです。そのため、.teamは見つかり、.Releaseはありません。エラーは標準エラー出力に出るので、2>&1で一緒に受け取ってください。メッセージの「何がnilなのか」をよく読んでみてください。

ドル記号でルートを取り直す

/root/hc-scope/frontier/templates/scoped.yamlで、withブロックはそのままにして、data.releaseだけを直し、release名がレンダリングされるようにしてください。レンダリング結果を/root/hc-scope/out/scoped.yamlに保存します(release名はweb)。dataには、release: webとteam: coreの2行が出力される必要があります。

テンプレートが始まったときのルートコンテキストは$に結び付けられていて、withやrangeの中でも変わりません。.Values.appに絞り込んだ便利さは維持したまま、ルートのものだけを取り出して使いたいときに使うノブです。

rangeの中でもルートの値を一緒に使う

/root/hc-scope/frontier/templates/envs.yamlを作成してください。名前は<app.name>-envsで、.Values.envsをインデックスと値の2つの変数で受け取って回りながら、dataに<소문자 환경이름>: "<인덱스>-<app.port>"(プレースホルダーは順に、小文字の環境名とインデックスです)を1行ずつ出力します。レンダリング結果に、stage: "0-8080"とprod: "1-8080"が出力される必要があります。結果を/root/hc-scope/out/envs.yamlに保存してください。

range $i, $e := .Values.envsのように変数を2つ受け取れば、インデックスと要素を一緒に使え、ドットが変わっても、$i・$eはそのまま生きています。ルートのポートは$.Values.app.portで取り出します。小文字に変える関数はlowerです。

空白トリム1つがYAMLを壊す

/root/hc-scope/brokenチャート(名前broken)を別に作成し、values.yamlにapp(name portal、team core)とnotes(owner sre、runbook wiki/portal)を置いてください。templates/portal.yamlは、annotations:の下の行で{{- toYaml .Values.notes | indent 4 -}}でコメントを差し込み、そのあとにlabels:が来る形です。レンダリングすると失敗します。通常の出力は/root/hc-scope/out/ws-error.txtに、--debugを付けた出力は/root/hc-scope/out/ws-debug.txtに保存してください。このチャートは直さず、そのままにします。

indent 4は、改行なしで4スペースだけを入れ、末尾の-}}は、続く改行と空白を消します。そのため、コメントのブロックはannotations:と同じ行にくっつき、次の行のlabels:も前の行の末尾にくっつきます。エラーメッセージはYAMLパーサーの言葉なので、原因を指しません。--debugを付けると、Helmが壊れた結果をそのまま見せてくれます。そこで、どの行がくっついたかを目で確認してください。

nindentで改行まで渡す

/root/hc-scope/brokenを/root/hc-scope/fixedにコピーし(チャート名はfixedに変えます)、templates/portal.yamlだけを直して、レンダリングが成功するようにしてください。コメントのブロックは、annotations:の下に4スペースインデントして2行で、labels.teamはcoreと出力される必要があります。結果を/root/hc-scope/out/fixed.yamlに保存してください。

nindent 4は、改行を先に入れて4スペースインデントします。前の{{-は、テンプレートタグの前の空白を消す役割だけをすればよく、末尾には-}}を書きません。ルールとして覚えると楽です。キーのすぐ下の行にブロックを差し込むときは、常にnindentです。

引用符を忘れたら、クラスターが拒否した

/root/hc-scope/frontier/templates/release.yamlを作成してください。名前は<릴리스이름>-release(プレースホルダーはrelease名です)、data.tagは.Values.release.tag、data.enabledは.Values.release.enabledを、引用符なしで差し込みます。このテンプレートだけをレンダリングして、yq -o=jsonを通した結果を/root/hc-scope/out/quote-yaml12.jsonに、そのレンダリングをkubectl apply --dry-run=serverに入れた出力を/root/hc-scope/out/quote-error.txtに保存してください。そのあと、2つの値にquoteを掛けて直し、もう一度サーバー検証を通過した出力を/root/hc-scope/out/quote-ok.txtに保存してください。

helm template <릴리스> <차트> -s templates/release.yaml(プレースホルダーは順にrelease名とチャートです)で、1つのテンプレートだけをレンダリングできます。引用符がないと、1.10は数値になって末尾の0が消え、noはYAML 1.1の規則で偽になります。ConfigMapのdataは文字列しか受け付けないので、APIサーバーが型変換エラーで拒否します。値が人の読む文字列なら、quoteを付けるのが基本だと考えてください。

誤った値ならレンダリングの段階で止める

/root/hc-scope/frontier/templates/guard.yamlを作成してください。ポートが1024未満ならfailで止め、data.teamはrequiredで値がないときに止めます。正常な値では、名前が<app.name>-guardのConfigMapが出力される必要があります。--set app.port=80でレンダリングした出力を/root/hc-scope/out/guard-fail.txtに、--set app.team=nullでレンダリングした出力を/root/hc-scope/out/guard-required.txtに保存し、オプションなしでレンダリングした全体の結果を/root/hc-scope/out/guard-ok.yamlに保存してください。

failは条件を直接書けるので、「値はあるが意味をなさない値」を止めるのに使い、requiredは「値そのものがないとき」を止めます。どちらのメッセージも、人が読んですぐに直せるように書きます。--set app.team=nullは、そのキーを消します。空の文字列とは違います。数値の比較には、lt (int .Values.app.port) 1024のように、intを1回通してください。

警告をエラーとして扱わせる

/root/hc-scope/lintbadチャート(名前lintbad)を作成し、templates/cm.yamlのConfigMapの名前をBad_Nameにしてください。helm lintの出力と終了コードを/root/hc-scope/out/lint-plain.txtに、helm lint --strictの出力と終了コードを/root/hc-scope/out/lint-strict.txtに保存してください。2つのファイルの最後の行に、exit=<종료 코드>(プレースホルダーは終了コードです)を追記します。同じチャートなのに、通過するかどうかが分かれることが、このステップの答えです。

Kubernetesのオブジェクト名は、小文字のRFC 1123の規則に従う必要があるので、大文字とアンダースコアは警告になります。デフォルトのリントは、警告を出しても0で終わりますが、strictモードは警告を失敗として数えます。CIでstrictモードを使えば、こうした名前がデプロイまで行きません。終了コードは、コマンドの直後に$?で読みます。