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

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

ライブラリチャート — インストールできないからこそ役に立つ

TT Labで続きを見る

一言でいうと

ライブラリチャートは、defineだけを収めて、自分では何もレンダリングしないチャートで、標準ラベルと共通のワークロードの形を、1か所に固定する場所です。

なぜ必要なのか

チャートを初めて作るとき、_helpers.tplはいつも同じ形で生まれます。helm createが入れてくれる、fullname、labels、selectorLabelsの3つです。問題は、チャートが10個になったときに始まります。その10個の_helpers.tplは、最初はコピーでしたが、あるチームがapp.kubernetes.io/componentを追加し、別のチームがteamラベルを入れるうちに、互いに別の生き物になります。

これがなぜ問題になるのかは、ラベルを使う側を見ると、はっきりします。監視のルールはapp.kubernetes.io/instanceで対象を選び、ネットワークポリシーはapp.kubernetes.io/nameで選び、コストのダッシュボードはteamでまとめます。チャート1つでラベルが1行抜けると、そのワークロードだけが、黙ってルールの外に出ます。障害の状況で「このPodだけメトリクスがない」という報告が上がり、原因は、半年前のコピーで抜けた1行です。

標準ラベルの修正を一度に終えられる必要がある、という要求が、ここから出てきます。そのためには、そのルールがファイル1つにあり、そのファイルを複数のチャートが依存関係として持っていける必要があります。

どう動くのか

Chart.yamlのtypeをlibraryにすると、Helmはこのチャートをインストールの対象から外します。実際にインストールを試みると、次のように拒否されます。

Error: INSTALLATION FAILED: library charts are not installable

helm templateも、同じ理由で止まります。その代わり、このチャートは、ほかのチャートのdependenciesに入れることができ、入った瞬間に、その中のすべてのdefineが、親チャートのテンプレート名の名前空間に合流します。親は、include "platform-lib.deployment" .1行で、Deployment一式をまるごと受け取って使います。

テンプレート名がチャート全体で1つの名前空間であるという点が、要であり、落とし穴でもあります。サブチャートが定義した名前と親が定義した名前が同じなら、あとに読まれたほうが勝ち、親があとに読まれます。誤って重なると、エラーも警告もなく、黙って別のテンプレートが使われます。defineの名前の前にチャート名を付ける慣例は、きれいに見せるためではなく、この衝突を防ぐためです。

values内の文字列を再レンダリングするtpl

ライブラリと組になってよく使われる関数がtplです。valuesに書かれた文字列は、デフォルトではただの文字なので、波括弧が入っていても、そのまま出力されます。

note: "{{ .Release.Name }} in {{ .Release.Namespace }}"

この値を{{ .Values.note }}で差し込むと、波括弧までそのまま出ます。{{ tpl .Values.note . }}で差し込むと、その場でもう一度テンプレートとして解釈されて、release名が入ります。ユーザーがvaluesで自由形式の設定を渡すチャートが、この方式を使います。アノテーションのまとまり、サイドカーの定義、設定ファイルの本文などです。その代わり、valuesがテンプレートを実行できるという意味なので、信頼できない値をtplに渡してはいけません。

いつ使い、いつ使わないか

ライブラリチャートが答えになる条件は、意外に狭いです。複数のチャートが同じルールを守る必要があり、そのルールが今後も変わるときです。標準ラベルがまさにそうです。監視とポリシーがラベルで対象を選ぶので、ルールが1つである必要があり、会社が大きくなるにつれて、チームのラベルやコストセンターのラベルが1つずつ増えます。

逆に、チャートが2–3個しかなく、ルールも固まっているなら、ライブラリを作るコストが、得るものより大きいです。依存関係の宣言が増え、バージョンアップの流れがもう1つ増え、新しく来た人は、_helpers.tplではなくcharts/の中のtgzを開かないと、テンプレートを読めません。この最後のことが、特に過小評価されます。レンダリング結果がおかしいとき、そのテンプレートがどのファイルにあるかを探す作業が、1段階遠くなります。

そのため、導入するかどうかよりも先に決めるべきことは、バージョンアップの流れです。ライブラリのバージョンを上げたとき、利用側のチャートはいつ追随するのか。Chart.yamlのバージョンの範囲を0.1.0のように固定しておくと、利用側のチャートを1つずつ手で上げる必要があり、^0.1.0のように開けておくと、helm dependency updateを実行する時点によって結果が変わります。CIがhelm dependency buildでロックのとおりにだけ取得するようにしておけば、後者のリスクはなくなります。ロックファイルがリポジトリにあるので、どのバージョンでビルドしたかが記録に残ります。

現場での姿

ライブラリチャートを導入するときに最もよくぶつかるのは、更新の時点です。helm dependency updateは、その瞬間のライブラリをtgzとして取り出して、親のcharts/に入れます。そのため、ライブラリでラベルを1行直しても、利用するチャートが依存関係を取得し直すまでは、古いコピーが使われ続けます。社内リポジトリを使うなら、ライブラリのバージョンを上げ、利用側のチャートのChart.yamlのバージョンの範囲を調整し、CIがhelm dependency buildでロックのとおりに取得するようにする流れが必要です。

そして、ライブラリにあまり多く入れないほうがよいです。Deployment一式をまるごと共通化すると、最初はきれいですが、チャートごとに異なる要求が1つずつできるにつれて、ifが積み重なります。実務でよく持ちこたえる境界は、たいていラベル・名前のルール・共通アノテーションまでで、ワークロード本体は、各チャートが持つほうです。共通化の得と分岐のコストがどこで逆転するかは、チームごとに違うので、ライブラリが大きくなり始めたら、一度立ち止まって測る必要があります。

次のラボですること

ライブラリチャートを自分で作って、インストールが拒否されることを確認し、2つのアプリケーションチャートが、同じDeploymentテンプレートとラベルのルールを、値だけを変えて使うようにします。tplでvalues内のテンプレート文字列を生き返らせ、最後に、親が同じ名前を再定義したとき、どちらがレンダリングされるかを、2つのチャートを並べて確認します。