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

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

Capabilities はどこから来るのか、そして crds がリリースの外にある理由

TT Labで続きを見る

一言でいうと

.Capabilitiesの値は、コマンドごとに取得元が違い、crds/は、テンプレートでもreleaseのマニフェストでもない、別のライフサイクルを持ちます。

なぜ必要なのか

チャート1つで複数のクラスターをサポートし始めると、すぐにこんなコードが入ってきます。

{{- if .Capabilities.APIVersions.Has "policy/v1/PodDisruptionBudget" }}
apiVersion: policy/v1
{{- else }}
apiVersion: policy/v1beta1
{{- end }}

古いクラスターにはpolicy/v1がないので、ベータバージョンを使う、という分岐です。うまく動いているように見えますが、CIでhelm templateでレンダリング検査を実行すると、常にelse側だけが出ます。一方、実際のデプロイでは、if側が出ます。同じチャート、同じ値なのに、結果が違います。

理由は、Capabilitiesの情報の取得元が違うからです。

コマンド クラスターに問い合わせるか KubeVersion
helm template いいえ Helmに埋め込まれたデフォルト値
helm template --validate はい 実際のクラスター
helm install --dry-run はい 実際のクラスター
helm install --dry-run=server はい 実際のクラスター
helm install はい 実際のクラスター

helm templateだけがオフラインです。そして、そのデフォルトのCapabilitiesのリストはとても小さく、実際のクラスターなら当然あるはずのnetworking.k8s.io/v1/Ingressさえ、ないと出ます。これを知らないと、「レンダリングしてみたら、Ingressの分岐が通らない」と言って、チャートを直し始めます。

クラスターなしで分岐をテストするには、Helmに嘘をつかせれば構いません。--kube-version 1.21.0はバージョンを、--api-versions "demo.labhub.io/v1/Widget"はAPIのリストを、付け加えます。付け加えるという点が重要です。デフォルトのリストを置き換えるのではなく、足します。

crdsディレクトリは5つの点で違う

CRDには、鶏と卵の問題があります。CRDが定義するユーザーリソースを、同じチャートが一緒に作ろうとすると、CRDが先にクラスターに入っている必要があります。Helmはこれを、crds/という専用のディレクトリで解決します。そのディレクトリは、普通のテンプレートと、5つの点で違います。

  1. テンプレートエンジンを通りません。波括弧を書いても、ただの文字です。そのため、CRDを値で条件分岐できません。
  2. デフォルトのレンダリング結果には出てきません。helm template --include-crdsを渡す必要があります。
  3. インストールのとき、ほかのすべてより先に入ります。そのため、同じチャートのテンプレートが、そのCRDを使うオブジェクトを一緒に作れ、最初のインストールでも.Capabilities.APIVersions.Hasが真になります。
  4. アップグレードでは手を付けません。チャートのcrds/を直してhelm upgradeを実行しても、クラスターのCRDはそのままです。公式ドキュメントがこれを制限として明記し、CRDの更新は人が直接行うよう案内しています。
  5. releaseを削除しても残ります。CRDを削除すると、それで作られたすべてのユーザーリソースが一緒に消えるので、Helmはこの判断を人に任せます。

helm get manifestでreleaseのマニフェストを取り出してみると、CRDがありません。releaseがCRDを所有していないという意味で、上の4つ目と5つ目が、ここから導かれます。

代案: テンプレートにCRDを置く方式

crds/の制約(アップグレードで更新されない、条件分岐できない)が困るなら、CRDをtemplates/に置くという選択肢があります。そうすれば、普通のオブジェクトになって、アップグレードで更新され、条件も掛けられます。その代わり、releaseがCRDを所有することになり、releaseを削除するとCRDとそのリソースがすべて消えます。そして、複数のreleaseが同じCRDを使うと、所有権が重なって衝突します。

実務でよく使われる境界は、こうです。CRDをオペレーター(operator)チャートとは別の、分離したチャートにして、クラスター管理者が一度だけインストールし、アプリケーションチャートは、.Capabilities.APIVersions.Hasで存在を確認するだけにします。大きなプロジェクトが、<이름>-crds(プレースホルダーは名前です)チャートを別に出す理由が、これです。

現場での姿

最も多く起きる事故は、「CRDを直してアップグレードしたのに、新しいフィールドが効かない」というものです。helm historyにはリビジョンが積まれ、デプロイは成功として表示されるのに、クラスターのCRDは、古いスキーマのままです。新しいフィールドを使ったユーザーリソースは、スキーマにないフィールドとして扱われ、黙って切り落とされます。この組み合わせが特に悪いのは、どこにも失敗が報告されない点です。デプロイパイプラインに、CRDを別にkubectl applyするステップを置くか、CRD専用のチャートを別に置くのが、定石的な対応です。

2つ目によくあるのは、CIのレンダリング検査が、本番と違う結果を出すことです。helm templateだけを実行すると、Capabilitiesの分岐が常に片側に固定されます。CIで実際のクラスターを使えるなら、--validateや--dry-run=serverを使い、使えないなら、--kube-versionと--api-versionsで対象のクラスターを模倣して、両方のケースをレンダリングしてみるほうが、安全です。

次のラボですること

Capabilitiesをそのまま出力するチャートを作って、オフラインのレンダリングの値を確認し、--kube-versionと--api-versionsで、別のクラスターのふりをしてレンダリングします。Capabilitiesに応じて、ユーザーリソースを入れたり外したりする分岐を作り、crds/にCRDを置いたあと、実際のkwokクラスターにインストールして、マニフェストにCRDがないことを確認します。最後に、CRDを直してアップグレードしてもクラスターがそのままであることを自分で確認し、--validateと--dry-run=serverが、オフラインのレンダリングとどう分かれるかを整理します。