リポジトリ上のチャートは tgz と index.yaml だけ
一言でいうと
チャートリポジトリは、サーバーソフトウェアではなく、index.yaml1つとtgz複数を配る、静的なHTTPディレクトリです。
なぜ必要なのか
チャートをうまく作っても、チームに広める段階で、誰もが一度は滑ります。最も多い方式は、Gitリポジトリのパスをそのまま教えることです。helm install myapp ./charts/myappは、うまく動きます。ところが、この方式にはバージョンがありません。昨日デプロイしたものと今日デプロイしたものが同じチャートかどうかを確認する方法がgit logしかなく、元に戻すにはコミットハッシュを覚えておく必要があります。本番障害のさなかに、「あのとき使ったチャートが正確にどの状態だったのか」という問いに答えられない瞬間が来ます。
パッケージ化されたチャートは、その問いにファイル1つで答えます。catalog-1.1.0.tgzは、名前とバージョンがファイル名に埋め込まれていて、内容が1バイトでも違えば、インデックスのdigestが変わります。デプロイの記録に「catalog 1.1.0」とだけ書かれていても、それが正確に何だったのかを、あとから取り出して確認できます。
versionとappVersion: 別々に動く2つの数字
Chart.yamlには数字が2つあり、この2つを同じものと勘違いすると、デプロイの会話がずっとかみ合いません。
| フィールド | 何のバージョンか | いつ上がるか |
|---|---|---|
version |
チャート自身 | テンプレート・デフォルト値・依存関係が変わるとき |
appVersion |
チャートが運ぶソフトウェア | アプリケーションのイメージタグが変わるとき |
リソース制限のデフォルト値を直しただけでも、versionは上がる必要があります。デプロイされるイメージはそのままなので、appVersionはそのままです。逆に、アプリケーションだけを新しくビルドしてタグを変えればappVersionが上がり、その値を反映するためにテンプレートに手を入れたなら、versionも一緒に上がります。そのため、2つの値はたいてい別の数字で、同じになるほうが、むしろ偶然です。
ファイル名にはversionだけが入ります。helm package --app-versionでappVersionを上書きしても、tgzの名前はそのままです。helm search repo --versionsやhelm listが、2つの値を並べて見せる理由が、ここにあります。
インデックスが自動で更新されないという事実
index.yamlは、リポジトリの目次です。ところが、この目次は、tgzをアップロードしても自然には増えません。helm repo index <디렉터리>(プレースホルダーはディレクトリです)を人がもう一度実行する必要があり、このとき、このコマンドはそのディレクトリに今あるtgzだけを見て、目次を最初から新しく書きます。
helm package catalog --version 1.2.0 -d stage
helm repo index stage --merge repo/index.yaml # 옛 목차를 합친다
ビルドの出力物だけが入っているstageで、--mergeなしでインデックスを作ってアップロードすると、目次には、今作った1つのバージョンだけが残ります。tgzファイルはサーバーにそのままあるのに、helm searchとhelm pullからは消えます。ファイルが消されたのではなく、目次から抜けただけなので、ディスクを見ても原因が見えません。
受け取る側も、目次をキャッシュとして持っています。helm repo addをすると、コピーが~/.cache/helm/repository/<이름>-index.yaml(プレースホルダーは名前です)に降りてきて、helm searchは、サーバーではなくこのコピーを読みます。リポジトリに新しいバージョンがアップロードされたのに、検索に出てこなければ、ほとんどはhelm repo updateを実行していないのです。
HTTPリポジトリ以外の道
チャートを運ぶ方法が、index.yaml+tgzだけではありません。Helm 3は、OCIレジストリをチャートのリポジトリとして使え、helm push <차트.tgz> oci://<레지스트리>/<경로>(プレースホルダーは順にチャートのtgz、レジストリ、パスです)でアップロードします。この方式には目次がありません。レジストリがすでにタグの一覧を持っているからです。そのため、helm repo index --mergeを忘れて古いバージョンを消してしまう事故が、構造的に起きず、コンテナイメージと同じ認証・権限の体系をそのまま使います。逆に、helm search repoで一度に見て回るのが難しく、レジストリの種類によってサポートの程度が違います。
完全性のための仕組みもあります。helm package --sign --key <이름> --keyring <경로>(プレースホルダーは順に名前とパスです)でパッケージ化すると、tgzの隣に.provファイルが一緒に作られ、受け取る側はhelm verifyやhelm install --verifyで署名を確認します。インデックスのdigestは、ファイルが転送中に変わっていないことを確認してくれますが、誰が作ったかは教えてくれないという点で、2つの役割が違います。社内だけで使うリポジトリなら、たいていdigestで十分で、外部に配布するチャートなら、署名を付ける方向に行きます。
現場での姿
社内リポジトリを初めて立てるとき、人々は専用のサーバーを探します。実際には、オブジェクトストレージのバケット1つを静的ウェブとして公開し、CIがhelm packageとhelm repo index --mergeを実行して、結果をアップロードすれば終わりです。認証が必要なら、その前にリバースプロキシを立てます。この単純さが、長所であり、落とし穴でもあります。目次を更新する責任が、もっぱらパイプラインにあるので、一度間違って書いたインデックスが、リポジトリ全体の歴史を消します。
依存関係では、helm dependency buildとupdateを区別しないために起きる問題が多いです。updateは、Chart.yamlのバージョンの範囲を解決し直して、Chart.lockを新しく書きます。範囲を^1.0.0のように開けておくと、昨日と今日のビルドが、異なるチャートを取ってくることがあります。buildは、lockに書かれたバージョンをそのまま取得し、lockがChart.yamlとずれていれば、取得せずに止まります。CIでupdateを使っているなら、そのパイプラインは、再現可能なビルドをしていません。
次のラボですること
チャートを2つのバージョンでパッケージ化してインデックスを作り、そのディレクトリをpython3 -m http.serverで立ち上げて、本物のリポジトリとして登録します。バージョンを選んで取得してみて、3つ目のバージョンを--mergeでマージしてアップロードしたあと、別のチャートの依存関係としてロックします。最後に、宣言とロックがずれた状態をわざと作って、helm dependency buildがどんな言葉で止まるかを、自分で確認します。