リクエストがetcdに届くまで — アドミッションという場所
一言でいうと
ポリシーエンジンは魔法ではなく、APIサーバーがリクエストを保存する直前に、他者に問い合わせるようにしたフック1つです。そのフックがどこに挟まっているかを知ってはじめて、ポリシーがなぜ効かないのかも、なぜクラスターを止めてしまうのかも、理解できます。
なぜ必要なのか
RBACは「誰が何をできるか」に答えます。開発者にPodの作成権限を与えれば、Podを作れます。しかし、組織が本当にほしいのは、その次の質問です。「作ってもよいが、こういう形でなければならない」ということです。イメージは社内のレジストリからだけ、リソースリミットは必ず設定、latestタグは禁止、ラベルは標準に従う、といった条件です。RBACのverbとリソースの種類では、この条件を表現できません。権限は「作る」という行為の単位であり、作られるオブジェクトの中身には目がないからです。
そのため、人々は長い間、このギャップを人で埋めてきました。Wikiに標準を書き、PRレビューで指摘し、デプロイ後にスキャナーを回して間違いを見つけ出しました。この3つの方法はどれも、同じ弱点を持っています。強制力がないか、すでに入った後で発見するかのどちらかです。レビューを通らない経路(kubectl applyの直接実行、CIの迂回、ほかのチームのHelmチャート)が1つでもあれば、ルールは漏れ始めます。
アドミッションコントロールは、この問題を構造で解決します。ルールを、人ではなくAPIサーバーが必ず通る関門に置くのです。その関門を通らずにクラスターに入ってくるオブジェクトはないので、経路がいくつあっても、ルールは1か所にあれば済みます。
どう動くのか
kubectl apply 1回が、APIサーバーの中を通る順序は、こうです。
요청 → 인증(Authentication) → 인가(Authorization/RBAC)
→ 뮤테이팅 어드미션(Mutating Admission)
→ 스키마 검증(Object Schema Validation)
→ 밸리데이팅 어드미션(Validating Admission)
→ etcd 저장
このコードブロックの韓国語の語は、順に、リクエスト、認証、認可、ミューテーティングアドミッション、スキーマ検証、バリデーティングアドミッション、etcdへの保存、という意味です。
ここで、必ず押さえておくべき事実が4つあります。
1つ目は、認可が先であることです。権限がなければ、ポリシーはそもそも評価されません。ポリシーはRBACの代わりになるものではなく、RBACを通過したリクエストをもう一度ふるいにかけるものです。両者の順序を逆に考えると、「ポリシーで権限を与える」のような、誤った設計が出てきます。
2つ目は、ミューテーションが検証より先であることです。この順序は偶然ではなく、設計です。デフォルト値を埋めた後で検査するからこそ、「リソースリミットを書かなかったが、ポリシーが埋めてくれたので通過」という流れが成り立ちます。同時に、落とし穴もここから生まれます。検証ルールが見るオブジェクトは、すでにミューテーションルールが手を加えた後のオブジェクトです。サイドカーを注入するミューテーションポリシーと、すべてのコンテナにリミットを要求する検証ポリシーを一緒にかけておいて、注入するスペックにリミットを入れなかった場合、デプロイがブロックされるうえ、ログにはユーザーが書いてもいないコンテナの名前が出力されます。自分のポリシーに自分が引っかかる、典型的な形です。
3つ目は、スキーマ検証がその間にあることです。ミューテーションルールが、スキーマにないフィールドを作り出すと、ここで引っかかります。ポリシーが作った結果も、結局はKubernetesのオブジェクトでなければなりません。
4つ目は、Webhookがネットワーク呼び出しであることです。APIサーバーが外部のPodにHTTPリクエストを送って、答えを待ちます。そのため、Webhookの設定には、2つの安全装置が付いています。
| 設定 | 意味 | 間違って使うと |
|---|---|---|
timeoutSeconds |
応答を待つ時間(デフォルト10秒、最大30秒) | 長くすると、障害時にAPIサーバーがその分だけ拘束される |
failurePolicy: Fail |
Webhookが応答できないと、リクエストを拒否 | ポリシーエンジンが落ちると、クラスターのすべての書き込みがブロックされる |
failurePolicy: Ignore |
Webhookが応答できないと、リクエストを通過 | ポリシーエンジンが落ちている間、検査なしにすべて入ってくる |
namespaceSelector |
特定のネームスペースだけをWebhookに通す | かけないと、kube-systemまでWebhookに依存する |
failurePolicyは、このコースで最も重要な1行です。Failはセキュリティを守りますが、ポリシーエンジンの可用性がそのままクラスターの可用性になるという意味です。アドミッションコントローラーのPodがすべて停止すると、Podも、ConfigMapも、さらにはそのコントローラーを復旧するためのデプロイさえも、ブロックされます。循環に陥るのです。そのため、namespaceSelectorでkube-systemとポリシーエンジン自身のネームスペースをWebhookの対象から外しておくのは、好みではなく、エスケープハッチを残しておく作業です。実際の運用で、Webhookの設定をまるごと削除してクラスターを蘇生させる手順が、ランブックに入っている理由も同じです。
ワイルドカードですべてのリソースをマッチさせるポリシーが危険な理由も、ここから出てきます。そのポリシーのコストは、ポリシー1つの実行時間ではなく、APIサーバーに入ってくるすべてのリクエストに付くレイテンシ税です。マッチ範囲を種類とネームスペースで絞る作業は、パフォーマンスチューニングではなく、可用性の作業です。
現場での姿
1つ目は、「ポリシーが何もブロックしない」という状況です。ポリシーのYAMLをいくら睨んでも、答えは出ません。たいていの原因は、Webhookが登録されていないか、マッチブロックが実際のリクエストと合わず、何もマッチしていないことです。何にもマッチしないポリシーは、黙ってすべてを通過させるので、正常に動いているポリシーと、人の目では区別できません。そのため、ポリシーを作るときは、通過すべきリソースとブロックされるべきリソースの両方を入れてみる習慣が必要です。
2つ目は、Webhookの障害が全面障害に広がる場合です。アドミッションコントローラー3つが同じノードに集中していて、そのノードが抜けると、failurePolicy: Failのもとで、クラスター全体の書き込みが止まります。レプリカを増やし、PodDisruptionBudgetをかけ、ノードの分散を強制するのが、ポリシー導入の必須の手順である理由です。
3つ目は、このラボ環境の正直な限界です。この環境のkwokクラスターでは、本物のetcd・APIサーバー・コントローラーマネージャー・スケジューラーが動いていますが、ポリシーエンジンのコントローラーは起動していません。そのため、誤ったPodをkubectl applyしても、クラスターはブロックしません。バックグラウンドスキャンも動かず、generateルールが作るリソースも、実際には生まれません。その代わり、kyverno CLIでポリシーエンジンをローカルで動かして、同じ判定ロジックを見られます。この文書のWebhookの順序・failurePolicy・タイムアウトは、ラボではなく、読み物とクイズで身につけ、ラボでは、ポリシー自体を書く力を養います。
次の確認で見ること
続くクイズでは、アドミッションの順序、WebhookのタイムアウトとfailurePolicy、Audit・Enforceの適用時点を、まず区別します。その概念を確認した後、次のモジュールで、必須ラベルを要求するClusterPolicyと、通過・拒否のフィクスチャを作成します。