K3s/Kubernetesでワイルドカードのポリシーを運用していたチームが、ValidatingAdmissionPolicy(CEL)への移行作業で遭遇した問題がある。移行後しばらく経ってから、特定のリソース種別だけがポリシーの検証をすり抜けていたことに気づいたのだ。原因はCELのランタイムエラーとfailurePolicy設定の組み合わせだった。KyvernoのClusterPolicyからValidatingAdmissionPolicyへ書き換えたポリシーは、本当に「同じ意味」を持っていると言えるのか。この記事では、その具体的な落とし穴と安全な移行手順を整理する。

1. ClusterPolicyが消える。今、移行先を決めなければならない理由
Kyvernoは長らくClusterPolicyというYAMLベースのパターンマッチ方式でKubernetesのポリシー管理を担ってきた。しかしKyverno 1.17でClusterPolicyとCleanupPolicyは非推奨(Deprecated)と位置づけられ、以降のマイナーバージョンでは重大な不具合修正のみが提供される段階に入っている。移行先として用意されているのが、CEL(Common Expression Language)ベースの新しいValidatingPolicyであり、これはKubernetes本体が1.30でGAしたValidatingAdmissionPolicyのスーパーセットとして実装されている。
ValidatingAdmissionPolicyの最大の強みは、CEL式をkube-apiserverのプロセス内で直接評価する設計にある。Kyverno/OPA Gatekeeperのような外部Webhookコントローラーをデプロイ・運用する必要がなく、ネットワークホップも発生しない。実際のベンチマークでは、同じ内容のポリシーをCELとWebhookでそれぞれ実行した場合、追加レイテンシの中央値はCELが約0.3msに対しWebhookは約1.65msという結果が報告されている。Kubernetesが2022年にアルファ版として導入して以来、1.30でのGAに至る設計思想は一貫して「単純な検証はWebhookサーバーの運用コストなしで実現する」という点にある。

パフォーマンスと運用負荷の両面でメリットがあるからこそ、多くのチームがKyvernoのワイルドカードポリシー(複数のリソース種別をまとめて検証する設定)をCELベースへ移行しようとしている。だが、この移行には単なる構文変換では済まない落とし穴がある。
2. ワイルドカードポリシーをそのまま移植したら何が起きたか
Kyvernoには既存のClusterPolicyからValidatingAdmissionPolicyを自動生成する機能が用意されている。Kyverno管理者コントローラーに生成フラグを立てるだけで、match.any.resources.kindsのようなリソース指定がCEL式に変換され、KubernetesのValidatingAdmissionPolicy定義として自動更新され続ける。
しかし、この自動変換には明確な制約がある。ValidatingAdmissionPolicyはKubernetes自身の仕様として検証(validate)専用の仕組みであり、Kyvernoのmutate(書き換え)・generate(自動生成)・image検証といった機能はそもそも対象にできない。複数のルールタイプを1つのClusterPolicyに束ねていた構成ほど、移行時に機能の取りこぼしが発生しやすい。
さらに厄介なのが、ワイルドカードで複数リソース種別(Deployment、DaemonSet、StatefulSetなど)をまとめて1本のCEL式で検証するよう移植した場合の挙動だ。Kyverno自身もCEL式のトラブルシューティングガイドで、オプショナルなフィールドへの未チェックアクセスが典型的な事故原因になると明記している。

3. 原因はCELのフィールド欠落エラーと、failurePolicy: Ignoreの組み合わせ
ここが本記事の核心だ。CEL式の中で参照するobject変数は、リソース種別によって存在するフィールドが異なる。たとえばobject.spec.replicasはDeploymentには存在するが、DaemonSetには存在しない。ワイルドカードでこれらを一括検証するCEL式にhas()によるガードが欠けていた場合、DaemonSetへのリクエストが来た瞬間にランタイムエラーが発生する。
matchConditionsの評価ロジックは次の通りだ。いずれかの条件がFALSEならポリシーはスキップされる。一方、いずれかの条件がエラーを返した場合はfailurePolicyの設定に従う。failurePolicy: Fail(デフォルト)であればリクエストは安全側に倒れて拒否されるが、移行作業中に「既存の挙動を壊したくない」という理由でfailurePolicy: Ignoreに設定しているケースが少なくない。この場合、ポリシーの評価自体が黙ってスキップされ、本来ブロックすべきリクエストがそのまま通過する。
Chainguardの分析も、ValidatingAdmissionPolicyが単純な検証には有効である一方、イメージ署名の検証や外部データ参照のような複雑な要件では従来のアドミッションコントローラーが依然として必要だと指摘しており、「移行すれば全て解決する」わけではないことがうかがえる。実際のベンチマーク記事でも、ラベルに整数値を設定したポリシーがポリシー作成時の型チェックをすり抜け、実行時に全リクエストが拒否される事例が報告されている。型チェックの不完全さとランタイムエラーの扱いは、CELベースの検証設計で最も見落とされやすいポイントだ。

この問題は、以前Kyverno/OPA Gatekeeperのワイルドカードポリシーのオーバーヘッドを比較した記事でも触れた「複数リソース種別の一括検証」というテーマの延長線上にある。ツールを選ぶ段階では見えなかったリスクが、CELへの移行段階で初めて表面化した形だ。
こうした「検証されるはずのものが静かに通過する」という事故は、K3sのPod Security Admissionで起きた設定差分の問題とも構造が似ている。アドミッション制御の仕組みは、正しく機能している限りは目立たないが、設定の境界条件を誤ると沈黙のまま実害を生む。
K8s運用の現場でこうした「見えないリスク」をゼロにするのは難しいが、少なくともポリシーの適用状況とDeny/Auditの実績を可視化できていれば、異常の検知は大きく早まる。KuboのCaptain UIは、こうした運用の透明性を標準で提供することを目指して設計されている。
4. 安全な移行手順 — has()ガード、Audit先行、段階的ロールアウト
実運用でCELベースのポリシーへ移行する際は、以下の手順を踏むことでリスクを抑えられる。
CEL式にガード句を必ず入れる
オプショナルなフィールドを参照する箇所には、必ずhas()や?によるオプショナルチェイニング構文でガードを入れる。「このリソース種別にこのフィールドが存在するか」を前提にせず、明示的に確認する設計にすることが、ランタイムエラーそのものを防ぐ最も基本的な対策だ。
failurePolicyはFailを基本にする
移行初期は「挙動を壊したくない」という理由でfailurePolicy: Ignoreにしたくなるが、これは検証漏れを許容することと同義になる。重要なポリシーほどfailurePolicy: Failを基本にし、意図的にIgnoreを選ぶ場合はその理由とリスクをドキュメント化すべきだ。
いきなりDenyにせず、Auditで先行検証する
validationActionsにはDeny/Warn/Auditの3種類が用意されており、Auditを使えば実際にリクエストをブロックすることなく、ポリシーがどう評価されるかをログで確認できる。本番環境で予期しない挙動がないかを数日〜数週間観察してからDenyへ切り替える、段階的なロールアウトが安全だ。
リソース種別ごとにポリシーを分割する
ワイルドカードで複数リソース種別をまとめる設計は一見効率的に見えるが、CEL式が複雑になるほどガード漏れのリスクが上がる。matchConstraints.resourceRulesをリソース種別ごとに分割し、それぞれに適したCEL式を書く方が、結果的に保守性も検証網羅性も高くなる。

こうした段階的な移行は、インフラチームだけで完結させるのが理想だが、実際には「何をAuditで確認すべきか」「いつDenyに切り替えて良いか」の判断に経験が必要になる。Kubo CloudのCaptain UIなら、ポリシーのAudit結果とDeny実績を同じ画面で確認できるため、移行判断のための材料をチームで共有しやすくなる。
金融・医療など、ポリシー違反が即座にコンプライアンス問題へつながる業種では、ポリシー移行そのものを外部に相談しながら段階的に進めたいというニーズも強い。そうした要件には、自社管理の環境でポリシー運用を完結できるKubo On-Premiseが選択肢になる。
まとめ — CEL移行は「速い」だけでなく「静かに失敗する」リスクも持つ
ValidatingAdmissionPolicyは、外部コントローラー不要・低レイテンシという明確なメリットを持つ一方で、CEL式のランタイムエラーとfailurePolicyの設定次第では「検証されないリソースが静かに生まれる」というリスクを内包している。Kubernetesのアドミッションウェブフック運用ガイドが繰り返し強調しているのも、結局のところ「失敗時にどちらへ倒すか」を設計段階で明示することの重要性だ。KyvernoのClusterPolicyが廃止に向かう今、多くのチームがこの移行に直面するはずだ。
移行時のチェックリストとしては、①has()ガードの徹底、②failurePolicy: Failを基本方針にする、③Auditでの先行検証、④リソース種別ごとのポリシー分割、の4点を押さえておきたい。CELに書き換えたポリシーが「本当に同じ意味」を持っているかどうかは、構文を見るだけでは分からない。実際のリクエストを通して検証する仕組みを、移行プロセスそのものに組み込む必要がある。
こうした地味だが重大な運用上の判断を、属人化させずにチームで共有できる基盤として、KuboはAI-Driven DeploymentとCaptain UIによる可視化を標準搭載している。ポリシー移行のような「成功しても目立たず、失敗すると静かに実害が出る」作業こそ、基盤側の透明性が効いてくる。興味があれば、お問い合わせから相談してみてほしい。