K8s 高級 #3 Admission Controller — OPA Gatekeeper / Kyverno

K8s 高級シリーズの 3 番目の記事です。中級 #7 で RBAC が K8s API の権限を、NetworkPolicy が Pod 間トラフィックを、ResourceQuota がリソース合計を統制すると整理しました。この記事のテーマはその上にもう 1 層乗るポリシーの肌理です — マニフェスト自体の形を強制するポリシー です。「limits なしのコンテナは作れない」「イメージは私たちの ECR レジストリからのみ受け取らなければならない」「すべてのワークロードに owner ラベルが必須」のようなルールは RBAC だけでは表現できません。こういうルールが入るところが K8s API サーバの Admission 段階 で、その段階にポリシーエンジンを差し込む 2 つのツールが OPA Gatekeeper と Kyverno です。

このシリーズは K8s 高級 6 編です。

Admission 段階 — マニフェストが etcd に入る直前 #

kubectl apply -f my-pod.yaml を打った瞬間からそのマニフェストが etcd に保存されるまでの流れは単純な 1 行ではありません。K8s API サーバは次の 5 段階を順に通します。

K8s API サーバのリクエスト処理フロー
1. 認証 (Authentication)         — 誰が呼んだか
2. 権限 (Authorization)          — RBAC 検査。呼び出し側がこの動詞・リソースを使えるか
3. Mutating Admission            — マニフェストを変形 (defaulting、サイドカー注入など)
4. Validating Admission          — マニフェストがポリシーを満たすか
5. etcd 保存

3、4 段階がこの記事のテーマである Admission Controller です。認証と RBAC を通過したリクエストでもこの段階で拒否される可能性があり、保存される前にマニフェスト自体が変形されることがあります。

Mutating vs Validating #

2 種類の違いは明確です。

  • Mutating Admission — マニフェストを 変形 します。例: すべての Pod にサイドカーコンテナを自動注入、欠落したラベルの自動補充、デフォルト値の適用。同じオブジェクトに複数の mutating controller が順に適用されることがあります。
  • Validating Admission — マニフェストを 検査 だけします。通過または拒否。変形は起こりません。すべての mutating が終わった後の最終マニフェストを見る点が重要です。

順序は常に mutating → validating です。変形がすべて終わったマニフェストが検査段階に渡されるので、validating ルールは「最終形態がポリシーを満たすか」だけを評価すればよいです。

ビルトイン Admission Controller #

K8s API サーバ内にはすでに複数の admission controller がコンパイルされています。運用クラスタでよく出会うものを押さえておきます。

コントローラ種類役割
NamespaceLifecycleValidating削除中の namespace でのオブジェクト生成を遮断
LimitRangerMutating + ValidatingLimitRange のデフォルト値適用 + 違反拒否
ResourceQuotaValidatingResourceQuota 合計超過時に拒否
ServiceAccountMutatingPod に default ServiceAccount を自動付加
PodSecurityValidatingPod Security Standards 強制 (1.25+ stable)
DefaultStorageClassMutatingPVC にデフォルト SC を自動補充

中級 #7 で扱った ResourceQuota と LimitRange が実際に動作する段階がこの admission 段階です。マニフェストが ResourceQuota の合計を超えると ResourceQuota admission controller が 4 段階で拒否します。ビルトインコントローラは --enable-admission-plugins API サーバフラグで有効化・無効化されます。

Webhook — 外部から admission 段階に差し込む #

ビルトインコントローラは K8s コードに内蔵されているのでユーザーが定義を変えられません。運用チームが自分のポリシーを admission 段階に差し込みたければ Webhook を使います。2 種類あります。

  • MutatingWebhookConfiguration — 外部 HTTP サービスにマニフェストを送り、そのサービスが変形されたマニフェストを返します。
  • ValidatingWebhookConfiguration — 外部 HTTP サービスにマニフェストを送り、そのサービスが allow/deny を返します。

K8s API サーバはこの webhook の呼び出し結果を見てリクエストをそのまま通すか、変形するか、拒否するかを決めます。OPA Gatekeeper と Kyverno は両方ともこの webhook メカニズムの上に乗ったポリシーエンジン です。K8s に新しい admission 種類を追加するのではなく、標準 webhook をうまく使うように抽象化したツールです。

OPA Gatekeeper — Rego で表現するポリシー #

OPA(Open Policy Agent)は K8s 外でも使われる汎用ポリシーエンジンです。Rego という独自言語でポリシーを書き、OPA エンジンがそのポリシーを評価します。Gatekeeper は OPA を K8s admission webhook で包んだツールです。

Gatekeeper の核心オブジェクトは 2 つです。

  • ConstraintTemplate — Rego で書いたポリシーの設計図。「こういう種類のポリシーを定義する」
  • Constraint — ConstraintTemplate のインスタンス。「このポリシーをどのリソースにどんなパラメータで適用する」

この 2 つの分離が Gatekeeper のモデルです。ポリシーの「形」は ConstraintTemplate に一度書いておき、その形にパラメータを入れて何度もインスタンス化する方式です。

ConstraintTemplate 例 — 必須ラベル強制 #

constrainttemplate-required-labels.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredLabels
      validation:
        openAPIV3Schema:
          type: object
          properties:
            labels:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredlabels

        violation[{"msg": msg}] {
          required := input.parameters.labels
          provided := input.review.object.metadata.labels
          missing := required[_]
          not provided[missing]
          msg := sprintf("Missing required label: %v", [missing])
        }

rego ブロック内のコードが本当のポリシーです。input.review.object は admission 段階のマニフェストで、input.parameters は Constraint から渡されたパラメータです。violation[...] が空でなければマニフェストが拒否されます。ConstraintTemplate を適用すると K8s に K8sRequiredLabels という新しい CRD ができます。

Constraint 例 — 上のテンプレートのインスタンス #

constraint-require-owner.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: namespace-must-have-owner
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Namespace"]
  parameters:
    labels: ["owner", "team"]

この Constraint を適用するとその瞬間から新しく作るすべての Namespace に ownerteam ラベルがなければ admission 段階で拒否されます。

ラベルなしの Namespace 生成試行
$ kubectl create ns test
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request:
[namespace-must-have-owner] Missing required label: owner
[namespace-must-have-owner] Missing required label: team

Gatekeeper の付加機能 #

Gatekeeper にはポリシー評価以外に運用親和的な機能がいくつかあります。

  • dry-run / audit モード — Constraint の enforcementAction: dryrun で適用すると拒否せず violation だけ記録します。ポリシーを実サービスに強制適用する前に影響範囲を測定するのに使います。
  • Config オブジェクトで評価対象を制限kube-system のようなシステム namespace を評価から除外できます。
  • 外部データ referrer — Constraint が OPA の data オブジェクトを参照して他の K8s オブジェクトや外部データを見てポリシーを評価できます。

Kyverno — YAML で表現するポリシー #

Kyverno は OPA Gatekeeper と同じカテゴリのツールですがアプローチが違います。新しい言語を学ばずに YAML でポリシーを書く のが Kyverno の最大の差別点です。K8s ユーザーはすでに YAML に慣れているので、ポリシー導入の参入障壁が低いです。

Kyverno の 3 つの動作 #

Kyverno のポリシーは 3 つのうちの 1 つ(またはそれ以上)を行います。

  • validate — マニフェストがルールを満たすか検査 (Validating Admission)
  • mutate — マニフェストを変形 (Mutating Admission)
  • generate — 別のオブジェクトを自動で作り出す (Kyverno 独自の機能)

generate は admission 段階自体の動作ではありませんが、「Namespace が作られたらその中にデフォルト NetworkPolicy を自動生成」のようなパターンを 1 つのポリシーで表現します。

Validate 例 — limits なしのコンテナを拒否 #

policy-require-limits.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-resource-limits
spec:
  validationFailureAction: Enforce
  rules:
    - name: require-cpu-memory-limits
      match:
        any:
          - resources:
              kinds: ["Pod"]
      validate:
        message: "Pod must have CPU and memory limits."
        pattern:
          spec:
            containers:
              - resources:
                  limits:
                    memory: "?*"
                    cpu: "?*"

pattern 内の ?* は「どんな値でもよいが空であってはならない」という意味です。このポリシーが適用されるとすべての新しい Pod のすべてのコンテナに limits.cpulimits.memory がすべて書かれていなければなりません。中級 #4 で扱ったリソースモデルを admission 次元で強制するパターンです。

Mutate 例 — すべての Pod にラベル自動追加 #

policy-add-labels.yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-default-labels
spec:
  rules:
    - name: add-managed-by
      match:
        any:
          - resources:
              kinds: ["Deployment", "StatefulSet"]
      mutate:
        patchStrategicMerge:
          metadata:
            labels:
              managed-by: platform-team

このポリシーが適用されるとマニフェストに managed-by ラベルがなくても admission 段階で自動で追加されます。コード 1 行も変えずにラベル標準を強制する道です。

Gatekeeper vs Kyverno — 2 つのうち何を使うか #

2 つのツールの比較を 1 つの表にまとめます。

次元OPA GatekeeperKyverno
ポリシー言語Rego (新たに学ぶ必要あり)YAML
表現力非常に高い (チューリング完全な Rego)普通 (宣言的パターンマッチ)
学習曲線
ポリシー動作validate, mutate (1.0+)validate, mutate, generate, cleanup
K8s 外ポリシーOPA 自体は K8s 外でも使用可能K8s 専用
ポリシーライブラリ豊富 (gatekeeper-library)豊富 (kyverno/policies)

選択の肌理 は通常次の肌理に従います。

  • Rego の学習負担を受け入れることができ、同じポリシーエンジンを K8s 外でも使いたいなら Gatekeeper が自然です。大きな組織でポリシーの一貫性を複数のシステムにわたって持っていくときに有利です。
  • K8s 運用チームがポリシーを直接書いて維持したいなら、ポリシー作成自体の参入障壁が最大のコストなら Kyverno が早いです。導入初月の学習コストの差が大きいです。

どちらのツールも運用規模での実績が十分あります。表現力が圧倒的に必要なケースでなければ Kyverno を先に検討し、Rego の表現力が本当に必要になった時点で Gatekeeper に移行する流れも自然です。

運用時に押さえておく原則 #

Admission webhook を導入するときに運用面で必ず押さえておくべき原則をいくつか押さえます。

1. failurePolicy の 2 つの選択 — Fail vs Ignore #

webhook を呼べないとき(タイムアウト、ネットワーク断絶、ポリシーエンジン Pod ダウン)に API サーバがどう振る舞うかを定めるフィールドです。

  • failurePolicy: Fail — webhook が応答できなければリクエスト拒否。ポリシーが回避されることはありませんが、ポリシーエンジンの可用性がクラスタ全体の可用性と組み合わさります。ポリシーエンジンが死ぬと新しいワークロードを立てられません。
  • failurePolicy: Ignore — webhook が応答できなければそのまま通過。可用性は良いがポリシーが回避されます。

運用の正攻法は 重要ポリシーは Fail、付随ポリシーは Ignore で分けておくことです。そしてポリシーエンジン自体を多重化(replicas 2 以上)し、PDB で保護するのが基本です。

2. namespaceSelector でシステム namespace 除外 #

kube-systemkube-public のような K8s 自体のワークロードが回る namespace は通常ポリシー評価から除外します。クラスタ起動自体がポリシーに止められる事故を防ぐ安全装置です。

webhook の namespaceSelector 例
namespaceSelector:
  matchExpressions:
    - key: kubernetes.io/metadata.name
      operator: NotIn
      values: ["kube-system", "kube-public", "kube-node-lease"]

3. dry-run で漸進的導入 #

運用クラスタに新しいポリシーをすぐに enforce モードで適用すると既存ワークロードのアップデートが次々と壊れる可能性があります。標準フローは次のとおりです。

ポリシー導入の正攻法
1. dry-run モードで適用 (Gatekeeper の dryrun、Kyverno の Audit)
2. 一定期間 violation ログ収集 → 影響範囲測定
3. 違反ワークロードを優先整理
4. enforce モードに切り替え

このサイクルを省略すると既存マニフェストが次々と拒否されて GitOps 同期が止まる事故につながります。ポリシーの意図が正しくても、導入手順は段階的に進める必要があります。

4. webhook latency モニタリング #

Admission webhook はすべてのマニフェスト変更の critical path にあります。ポリシーエンジンが遅くなると kubectl apply も一緒に遅くなります。Gatekeeper / Kyverno 両方とも独自メトリクスを公開するので、P99 latency と拒否率を #5 で扱うオブザーバビリティスタックに組み込んでおくのが標準です。

締めくくり #

K8s API サーバの admission 段階とその上に乗るポリシーエンジンを整理しました。マニフェストが etcd に保存される直前の 5 段階のうち mutating・validating admission がポリシーの進入点で、K8s にビルトインされているコントローラ以外に webhook で外部ポリシーエンジンを差し込めるというモデルを追いました。その外部エンジンの 2 つの標準が OPA Gatekeeper と Kyverno で、表現力の Gatekeeper 対 参入障壁の Kyverno という肌理を比較しました。最後に failurePolicy / システム namespace 除外 / dry-run 導入 / webhook latency モニタリングまで運用原則を 4 つ押さえました。次の記事では K8s API 自体を拡張する道 — CRD で新しいオブジェクト種を定義し controller-runtime でそのオブジェクトを運用する Operator パターンを扱います。

X