範囲と障害モード — ポリシーを有効にする日の順番
一言でいうと
ポリシーのリスクは、ルールの内容ではなく、範囲と故障モードから生まれます。そのため、ポリシーをオンにする日の順序は、常に、狭い範囲 → 警告 → 広げる → ブロックです。
なぜ必要なのか
ポリシー導入の事故の記録を集めてみると、ルールが間違っていて起きた事故は珍しいです。ほとんどは、2つです。広すぎる範囲にかけたか、故障したときの動作を決めていなかったかです。
広くかけることがなぜ危険かは、コストの性質を見ればわかります。どんなリソースにもマッチするポリシー1つのコストは、ポリシー1回の実行時間ではありません。APIサーバーに入ってくるすべての書き込みリクエストに付く税です。クラスターには、人が送るリクエストよりも、コントローラーが送るリクエストのほうが、ずっと多いです。Lease・イベント・EndpointSlice・Podの状態の更新が、絶え間なく流れています。ワイルドカードのポリシー1つが、そのすべてを、もう一度ずつつかまえます。
2つ目のほうが、もっと恐ろしいです。ポリシーがkube-systemまで見るようにかけておいて、ポリシーエンジンが落ちると、failurePolicy: Failのもとで、クラスターは自分自身を復旧できなくなります。エンジンを蘇生させるためのデプロイも、そのポリシーを通る必要があるからです。循環です。
どう動くのか
範囲は3つの層で絞ります。
| 層 | 何を決めるか | 例 |
|---|---|---|
| リソース | どのグループ・バージョン・種類・verbを見るか | apps/v1 deploymentsのCREATE・UPDATEだけ |
| ネームスペース | どのネームスペースに適用するか | namespaceSelectorで、特定のラベルが付いた場所だけ |
| リクエスト | そのうち、どのリクエストを見るか | objectSelector、matchConditionsのCEL条件 |
3つの層は、コストを減らす順序でもあります。リソースの層でふるい落とされたリクエストは、APIサーバーがポリシーを思い出しもしません。ネームスペースの層はその次で、リクエストの層は最後です。そのため、ワイルドカードで受けておいて、CEL条件でふるい分けるポリシーは、3つの層のうち最もコストの高い場所で働いていることになります。同じ結果を出しながら、コストだけが大きいのです。
kube-systemを除外するのは、好みではなく、エスケープハッチを残す作業です。ポリシーエンジンが住むネームスペースも同じです。ランブックに「Webhookの設定を削除して、クラスターを蘇生させる」という手順が入っている組織が多いのですが、そもそも範囲から外しておけば、その手順を使う場面が減ります。
故障モード。failurePolicyが引き換えにするものです。
| 値 | ポリシーサーバーが応答できないとき | そのとき失うもの |
|---|---|---|
Fail |
リクエストを拒否する | 可用性。エンジンが落ちると、その範囲の書き込みが止まる |
Ignore |
リクエストを通過させる | 保証。エンジンが落ちている間、検査なしに入ってくる |
どちらも、正しい答えではありません。同じクラスターの中でも、ルールごとに、合う値が違います。セキュリティ境界を守るルールはFailに、衛生ルールはIgnoreにします(ラベルの標準、推奨設定などが衛生ルールです)。そして、Failを選んだルールほど、範囲を絞ることが重要になります。範囲が狭ければ、エンジンが落ちても、止まるのがその範囲だけだからです。範囲と故障モードは、別々に選ぶ値ではなく、セットで選ぶ値です。
timeoutSecondsは、3つ目のつまみです。Webhookの応答を待つ時間で、長くすると、障害のときにAPIサーバーがその分だけ拘束されます。短くすると、遅い応答が失敗として処理されて、failurePolicyに渡されます。そのため、タイムアウトは「余裕を持たせて」ではなく、「このルールが正常なときにかかる時間より、少し長く」で決めます。
組み込みポリシーとWebhookポリシーは、故障モードが違います。この違いが、設計の選択を変えます。
- VAP: APIサーバーの中で評価されます。ネットワークがないのでタイムアウトがなく、エンジンのPodが落ちて止まることもありません。故障は、「式の評価エラー」という狭い形でしか来ません。
failurePolicyは、その場合を扱います。 - PSA: 組み込みのアドミッションプラグインなので、やはり外部への依存がありません。誤ってオンにしたときの故障は、「このネームスペースでPodが起動しない」という局所的な形です。
- Webhookエンジン: 外部のPod・ネットワーク・証明書に依存します。故障モードが最も広く、
failurePolicyと範囲の設計が、その分重要です。
そのため、実務の配置は、たいてい次のように分かれます。エンジンなしで表現できる検証はVAP・PSAに移して、故障の表面積を減らし、本当に必要なものだけをWebhookに残します。
ポリシーをオンにする日の順序です。
1) 좁은 범위로 시작한다 (한 네임스페이스, 한 종류, 한 동사)
2) 경고·감사로만 켜고 며칠 센다
3) 걸린 것을 고치거나 좁은 예외로 남긴다
4) 범위를 한 단계 넓히고 2..3 을 반복한다
5) 마지막에 차단으로 올린다 (정책 본문이 아니라 켜는 쪽만 고친다)
6) 되돌리는 절차를 미리 적어 둔다
このコードブロックの韓国語は、6つの手順を順に、狭い範囲で始める(1つのネームスペース、1つの種類、1つのverb)、警告・監査だけでオンにして数日数える、引っかかったものを直すか、狭い例外として残す、範囲を1段階広げて手順2と手順3を繰り返す、最後にブロックに上げる(ポリシーの本文ではなく、オンにする側だけを直す)、元に戻す手順をあらかじめ書いておく、と述べています。
手順4と手順5の順序を入れ替えるチームが多いです。狭い範囲からすぐにブロックに上げて、そのあとで範囲を広げるのです。そうすると、範囲を広げる日が、そのまま事故の日になります。新しく入ってきたネームスペースが、観察なしにいきなりブロックに出会うからです。広げる作業とブロックに上げる作業を、同じ日に行わないことが、この順序の核心です。
現場での姿
1つ目は、ワイルドカードポリシーのレイテンシ税です。すべてのリソースにマッチするWebhook1つのせいで、APIサーバーのレイテンシ分布のテールがまるごと上がった事例が、よくあります。原因の把握が難しい理由は、ポリシー自体は速いからです。遅いのはリクエスト1つではなく、リクエストのすべてです。
2つ目は、自分自身をブロックしたポリシーです。エンジンのネームスペースを範囲から外さなかったため、エンジンを再デプロイしようとするリクエストが、死んだエンジンに尋ねてブロックされます。蘇生させる唯一の道が、Webhookの設定を削除することだけなので、その手順を知る人を探している間に、障害が長引きます。
3つ目は、Ignoreでオンにしたまま忘れることです。事故を避けるために、すべてIgnoreでオンにしておくと、エンジンが数時間落ちている間に検査なしに入ってきたものを、誰も知りません。Ignoreを選んだなら、エンジンの可用性アラートがポリシーの一部である必要があります。
4つ目は、1つのノードに集中したエンジンです。レプリカが3つでも、同じノードにあれば、そのノードが抜けるときに一緒に消えます。Failのもとでは、それがそのまま書き込みの停止です。トポロジースプレッドとPodDisruptionBudgetは、ポリシー導入の選択肢ではなく、必須の準備です。
5つ目は、この環境で見られることです。kwokクラスターでは、届かないアドレスを持つWebhookの設定を作って、FailとIgnoreの違いを実際に見られます。同じリクエストが、一方ではブロックされ、もう一方では通過します。
参考ドキュメント
- 動的アドミッション制御(Webhookの設定): https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/
- アドミッションコントローラーの一覧: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/
- ValidatingAdmissionPolicy: https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/
- Pod Security Admission: https://kubernetes.io/docs/concepts/security/pod-security-admission/
次のラボですること
ポリシーをわざと壊して、その故障を観察します。届かないアドレスを持つWebhookの設定を作って、failurePolicy: Failでリクエストがブロックされることと、Ignoreで通過することを、同じリクエストで確認し、namespaceSelectorでシステムのネームスペースを範囲から外して、エスケープハッチができるのを見ます。範囲を、リソース・ネームスペース・リクエストの3つの層でそれぞれ絞りながら、どのリクエストがポリシーを通り、どのリクエストが通らないかを数えてみて、最後に、狭い範囲 → 警告 → 広げる → ブロックの順序を一周しながら、元に戻す手順まで作ります。