mutateとgenerate — 拒否の代わりに埋めてやる
一言でいうと
検証は間違ったものを防ぎ、ミューテーションは抜けているものを埋め、生成はあるべきものを作ります。この3つのうち、人の仕事を実際に減らすのは、後ろの2つです。
なぜ必要なのか
リソースリミットのないPodをすべてブロックするポリシーをかけたと仮定します。ルールは守られますが、代償があります。10個のチームがそれぞれマニフェストを直す必要があり、その過程で、「いくつ入れればよいですか」という問い合わせが10回来ます。組織がほしかったのは、「リミットのないPodがない状態」であって、「10個のチームがそれぞれ悩む状態」ではありませんでした。
ミューテーションルールは、この仕事を逆転させます。値がなければ組織のデフォルト値を入れ、すでに値があれば手を触れません。そうすれば、ルールは守られながら、誰もブロックされません。生成ルールは、さらに一歩先へ進みます。新しいネームスペースが作られた瞬間に、デフォルトのNetworkPolicy・ResourceQuota・LimitRangeを一緒に作ってくれます。人がオンボーディングのチェックリストをたどりながら、3回kubectl applyしていた作業がなくなります。
ただし、力が強い分、副作用も大きいです。ミューテーションは、ユーザーが書いたマニフェストとクラスター上の実際のオブジェクトを違うものにします。半年後に「このアノテーションは誰が付けたのか」という質問が出て、デプロイツールのドリフト検知とも戦い続けることになります。そのため、判断基準が必要です。組織全体に強制すべき値だけをミューテーションで入れ、チームが変更できるべき値は、チャートやマニフェストのデフォルト値に置きます。
どう動くのか
ミューテーションには、3つの方式があります。
| 方式 | いつ | 特徴 |
|---|---|---|
patchStrategicMerge |
マップにフィールドを追加したり、埋めたりするとき | オブジェクトと同じ形で書くため、読みやすい |
patchesJson6902 |
配列の特定の位置を扱うとき | op・path・valueで位置をピンポイントで指す |
foreach |
コンテナのように、数が決まっていないリスト | listでリストを受け取り、elementで現在の項目を指す |
配列が、この3つを分ける基準です。Strategic Mergeはマップには自然ですが、配列では「何番目の項目」を指定しにくいです。JSON Patchは、/spec/containers/0/imagePullPolicyのようにインデックスを使えますが、インデックスが固定であるという前提が必要です。コンテナがいくつあるかわからないときは、結局foreachでリストを回る必要があります。
ミューテーションで最も重要な構文は、追加アンカー(+())です。+(imagePullPolicy): IfNotPresentは、「値がなければ入れ、すでにあれば触らない」という意味です。このアンカーがないと、ポリシーがユーザーの明示した値を、黙って上書きします。チームがcpu: 500mと書いたのに、ポリシーが200mに変えてしまうと、そのチームは自分のマニフェストを信じられなくなります。条件アンカー()は、「この条件が合っているときだけ、下を適用する」という別の働きをするので、混同してはいけません。
生成ルールには、2つの方式があります。dataは、作る内容をポリシーの中に直接書く方式で、cloneは、既存のリソースを元にしてコピーする方式です。Secretのように、内容をポリシーファイルに書いてはいけないものは、必ずcloneを使います。ポリシーは普通、gitに入るからです。2つは一緒には使えません。
synchronize: trueは、作った後も見守り続けるという意味です。元のものが変わればコピーも追従して変え、誰かがコピーを手で直せば元に戻します。オフにすると、作るときに一度だけ関与して、その後は触りません。オンのほうが安全そうに見えますが、無料ではありません。対象のネームスペースの数だけ、監視と書き込みが増えます。
権限も忘れてはいけません。バックグラウンドコントローラーは最小権限でインストールされるため、作ろうとするリソースの種類に対するcreate・update権限を、ClusterRoleで別に付ける必要があります。権限がないと、リクエストは成功するのに、リソースだけが黙って作られません。最も気づきにくい種類の失敗です。
現場での姿
1つ目は、ミューテーションと検証が互いに噛み合う事故です。サイドカーを注入しながらリミットを入れないと、リミットを要求する検証ポリシーに、そのサイドカーが引っかかります。注入するスペックにリミットも一緒に入れるのは、好みではなく、要件です。
2つ目は、既存のリソースを修正するポリシーです。mutate.targetsを使うと、トリガーとなったリソースではなく、別のリソースを修正できます。アドミッションではなくバックグラウンドで動く作業なので追加の権限が必要で、mutateExistingOnPolicyUpdateをオンにすると、ポリシーを修正するたびに、既存のリソースが一度に手直しされます。力が強い分、範囲を絞って使います。
3つ目は、GitOpsとの境界です。数百個のネームスペースのリソースを、生成ルールと同期で管理するのは、GitOpsツールのほうが得意です。何よりも、gitに残ります。生成ルールが価値を持つ場面は、ネームスペースの作成のように、その瞬間にしか知りえないイベントに反応する必要があるときです。
4つ目は、この環境の限界です。ここでは、バックグラウンドコントローラーが動かないため、mutate.targetsや生成ルールが実際のリソースを作りません。その代わり、kyverno CLIが同じルールをローカルで評価して、ミューテーション後のマニフェストと、生成されるリソースを見せてくれます。採点は、ポリシーのYAMLの構造とその実行結果を見ます。
次のラボですること
まず、ミューテーションのラボで、ラベルを追加するポリシー、追加アンカーでリソースのデフォルト値を埋めるポリシー、JSON Patchで配列の項目を修正するルール、コンテナのリストを回るforeachルールを書きます。そして、値がすでにあるPodに同じポリシーを実行して上書きしないことを目で確認した後、ミューテーションの前後を照合したレポートを作ります。続く生成のラボでは、新しいテナントのネームスペースに、NetworkPolicy・ResourceQuota・LimitRangeを一度に作ってくれるオンボーディングポリシーのセットと、そのポリシーが実際に動作するために必要なRBACを書きます。