valuesの山をAPIに変える
一言でいうと
CRDは新しい機能を追加する装置ではありません。APIサーバーがすでに持っている能力を、自分が定義した型にそのまま貸し出す装置です。
なぜ必要なのか
社内サービスをデプロイするHelmチャートを想像してみてください。最初はimageとreplicasの2つだったvalues.yamlが、1年後には40行になります。そして、次のようなことが繰り返されます。
- 誰かが
replicas: "3"と引用符を付けてしまいました。テンプレートはそのままレンダリングされ、デプロイは成功し、Podは起動しません。 - 誰かが
tier: prdとタイプミスをしました。どこにも引っかからず、静かに誤ったラベルが付きます。 - 「うちのチャートのvaluesスキーマ」を説明するWikiドキュメントができますが、そのドキュメントはいつもコードより古くなっています。
- 今クラスターにどんな値で載っているかを知るには、releaseごとに
helm get valuesを実行しなければなりません。ラベルで一度に選ぶこともできません。
問題の根は1つです。values.yamlはAPIサーバーから見ると、ただの文字列の塊です。検証してくれる主体がなく、保存場所がreleaseのSecretの中なので参照しにくく、誰がいつ変更したかが監査ログにリソース単位で残らず、値1つだけを変更する権限をRBACで分けることもできません。
CRDはこの問題を、「それなら、それを本物のAPIオブジェクトにしよう」という発想で逆転させます。
どう動くのか
CRDを1つ適用すると、APIサーバーが/apis/<그룹>/<버전>/namespaces/<ns>/<복수형>(プレースホルダーはグループ、バージョン、ネームスペース、複数形です)というエンドポイントを開き、その瞬間から次の能力が無料で付いてきます。
| 能力 | values.yaml | CR |
|---|---|---|
| スキーマ検証 | なし(レンダリング後にようやく発見) | OpenAPI v3でapply時点に拒否 |
| デフォルト値の注入 | テンプレート内のdefault関数 |
スキーマのdefaultをAPIサーバーが埋める |
| 未知のフィールド | 黙って無視 | pruningで切り落とす、または明示的に保存 |
| 保存 | releaseのSecret | etcdにオブジェクトとして |
| 参照 | helm get values |
kubectl get、ラベルセレクター、カスタムカラム |
| 変更の監視 | なし | watchストリーム |
| 権限の分離 | チャート全体の単位 | リソース・サブリソース単位のRBAC |
| 監査 | パイプラインログ | APIの監査ログにオブジェクト単位で |
さらにもう1つ付いてきます。スキーマの中でCELルールを使えます。self.tier != 'prod' || self.replicas >= 2のようなフィールド間の制約を、Webhookサーバーなしで、APIサーバーの中で検査します。以前は検証Webhookを立てなければできなかったことが、今ではスキーマの1行で済みます。
そして、必ず覚えておいてください。CRDだけでは何も起きません。CRを適用すると、検証済みのデータがetcdにきちんと保存されるだけで、Podが起動することもバックアップが走ることもありません。そこに、その型をwatchして調整するコントローラーが加わって、はじめてOperatorになります。
CRD = 새 어휘 (무엇을 원하는지 말하는 언어)
컨트롤러 = 그 어휘를 현실로 만드는 두뇌
오퍼레이터 = CRD + 컨트롤러
このコードブロックの韓国語の3行は、順に、CRDは新しい語彙(何がほしいかを伝える言語)、コントローラーはその語彙を現実にする頭脳、OperatorはCRDとコントローラーの組み合わせ、という意味です。
現場での姿
1つ目は、プラットフォームチームのセルフサービスです。内部開発者プラットフォームをつくるチームの多くは、CRDで抽象化を提供します。開発者はWebServiceを1枚書くだけで、プラットフォームがそれをDeployment・Service・Ingress・HPA・NetworkPolicyに変換します。開発者が学ぶ必要のある表面が、40行のvaluesから5行のCRに減ります。
2つ目は、Prometheus OperatorのServiceMonitorです。巨大なPrometheusの設定ファイルを人が編集する代わりに、各チームが小さなCRを作って「自分のサービスのメトリクスを収集してください」と宣言します。1つの設定ファイルを複数のチームが編集して起きていた衝突が、リソース単位の所有権に置き換わります。
3つ目は、使ってはいけない場面がはっきりあることです。次のうち1つでも当てはまるなら、CRDは過剰です。
- DeploymentとHPAで完結するステートレスなアプリです。抽象化の層が増えるだけで、デバッグは難しくなります。
- 一度インストールしたら、その後の運用作業がほとんどありません。それはHelmとGitOpsの領分です。
- 長期的に保守する人がいません。CRDはコードであり、Kubernetesのバージョンが上がれば追従する必要があります。
- すでによく管理されている公式のOperatorがあります。自分で作る前に、まず探すべきです。
4つ目は、CRDはコードではなくAPI契約であることです。コントローラーのコードはいつでも再デプロイできますが、すでにetcdに溜まった数千個のCRや、ユーザーがGitにコミットしたマニフェストは、気軽には変えられません。フィールドを1つ間違って作ると、v1alpha1からv1まで何年も付いて回ります。だからAPIの表面は小さく始め、進化の道をあらかじめ用意しておく必要があります。
次の確認で見ること
続くクイズでは、CRDを単なるYAMLの形式ではなく、長期的な互換性を持つAPI契約として設計しなければならない理由を確認します。名前・スコープ・スキーマ・バージョン変換の判断基準を点検したうえで、次のモジュールでapps.labhub.ioグループのWebService型を自分で定義します。