- 1. Validateルール
- 2. Mutateルール
- 3. Generateルール
- 4. 変数とコンテキスト
- 5. 高度なパターン
- 6. validationFailureActionからfailureActionへ
- 7. ルールはどこで、どの順序で実行されるのか
- 8. ポリシーをローカルで回してからクラスタに載せる
- 9. 失敗パターンと診断の順序
- 10. 使わないほうがよい場面
- 11. 参考資料
- 12. まとめ
1. Validateルール
1.1 パターンマッチング
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-run-as-non-root
spec:
validationFailureAction: Enforce
rules:
- name: check-security-context
match:
any:
- resources:
kinds:
- Pod
validate:
message: 'Containers must run as non-root'
pattern:
spec:
containers:
- securityContext:
runAsNonRoot: true
演算子: ?*(空でない値)、*(任意の値、nullを含む)、X|Y(または)、!X(否定)、>X、<X、>=X、<=X(数値比較)
1.2 denyルール
条件ベースでリソースを拒否します:
rules:
- name: deny-latest-tag
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Using 'latest' tag is not allowed. Use a specific version tag."
deny:
conditions:
any:
- key: '{{ request.object.spec.containers[].image }}'
operator: AnyIn
value:
- '*:latest'
1.3 CEL式
Kubernetes 1.25以降のCEL(Common Expression Language)を使います:
rules:
- name: check-replica-count
match:
any:
- resources:
kinds:
- Deployment
validate:
cel:
expressions:
- expression: 'object.spec.replicas >= 2'
message: 'Deployment must have at least 2 replicas'
- expression: 'object.spec.replicas <= 100'
message: 'Deployment cannot exceed 100 replicas'
1.4 foreach
コレクションの各要素に対して検証します:
rules:
- name: check-each-container
match:
any:
- resources:
kinds:
- Pod
validate:
message: 'All containers must have resource limits'
foreach:
- list: 'request.object.spec.containers'
deny:
conditions:
any:
- key: '{{ element.resources.limits.memory }}'
operator: Equals
value: ''
2. Mutateルール
2.1 patchStrategicMerge
Kubernetes Strategic Merge Patch方式:
rules:
- name: add-sidecar
match:
any:
- resources:
kinds:
- Deployment
selector:
matchLabels:
inject-sidecar: 'true'
mutate:
patchStrategicMerge:
spec:
template:
spec:
containers:
- name: log-collector
image: fluentbit:latest
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
volumes:
- name: shared-logs
emptyDir: {}
2.2 patchesJson6902
JSON Patch(RFC 6902)方式:
rules:
- name: add-annotation
match:
any:
- resources:
kinds:
- Service
mutate:
patchesJson6902: |-
- op: add
path: /metadata/annotations/modified-by
value: kyverno
- op: replace
path: /spec/type
value: ClusterIP
2.3 foreach mutate
rules:
- name: add-pull-secret-to-all-containers
match:
any:
- resources:
kinds:
- Pod
mutate:
foreach:
- list: 'request.object.spec.containers'
patchStrategicMerge:
spec:
imagePullSecrets:
- name: my-registry-secret
3. Generateルール
3.1 dataベース生成
ポリシーに定義したデータでリソースを生成します:
rules:
- name: generate-default-limitrange
match:
any:
- resources:
kinds:
- Namespace
generate:
apiVersion: v1
kind: LimitRange
name: default-limits
namespace: '{{ request.object.metadata.name }}'
synchronize: true
data:
spec:
limits:
- default:
cpu: 500m
memory: 512Mi
defaultRequest:
cpu: 100m
memory: 128Mi
type: Container
3.2 cloneベース生成
既存のリソースを複製します:
rules:
- name: clone-configmap
match:
any:
- resources:
kinds:
- Namespace
generate:
apiVersion: v1
kind: ConfigMap
name: shared-config
namespace: '{{ request.object.metadata.name }}'
synchronize: true
clone:
namespace: default
name: template-configmap
3.3 synchronizeオプション
synchronize: true のとき:
- ソースリソースの変更が生成されたリソースにも反映される
- Background Controllerが同期を担当する
- 生成されたリソースを手で修正しても自動で復元される
4. 変数とコンテキスト
4.1 JMESPath変数
rules:
- name: add-ns-label
match:
any:
- resources:
kinds:
- Deployment
mutate:
patchStrategicMerge:
metadata:
labels:
namespace: '{{ request.object.metadata.namespace }}'
owner: '{{ request.userInfo.username }}'
4.2 API呼び出しコンテキスト
rules:
- name: check-namespace-labels
match:
any:
- resources:
kinds:
- Pod
context:
- name: namespaceInfo
apiCall:
urlPath: '/api/v1/namespaces/{{ request.namespace }}'
jmesPath: "metadata.labels.environment || 'unknown'"
validate:
message: 'Pods can only run in labeled namespaces'
deny:
conditions:
any:
- key: '{{ namespaceInfo }}'
operator: Equals
value: 'unknown'
4.3 ConfigMapルックアップ
rules:
- name: check-allowed-registries
match:
any:
- resources:
kinds:
- Pod
context:
- name: allowedRegistries
configMap:
name: allowed-registries
namespace: kyverno
validate:
message: 'Image must be from an allowed registry'
foreach:
- list: 'request.object.spec.containers'
deny:
conditions:
all:
- key: '{{ element.image }}'
operator: AnyNotIn
value: '{{ allowedRegistries.data.registries }}'
5. 高度なパターン
5.1 条件付きアンカー
# () アンカー: 条件付き — フィールドが存在する場合のみ検証
validate:
pattern:
spec:
template:
spec:
containers:
- (image): "*/nginx:*" # nginxイメージの場合のみ
resources:
limits:
memory: ">=256Mi"
# X() 否定アンカー: フィールドが存在してはならない
validate:
pattern:
spec:
template:
spec:
containers:
- name: "*"
X(securityContext):
X(privileged): true # privilegedがtrueであってはならない
5.2 等値アンカー
# =() 等値アンカー
validate:
pattern:
spec:
=(replicas): '>=3' # replicasが設定されていれば3以上
6. validationFailureActionからfailureActionへ
この記事の例はすべて spec.validationFailureAction を使っています。いま動いているクラスタのポリシーはたいていその姿なのでそのまま残しましたが、このフィールドはdeprecatedで、ルール単位のフィールドである spec.rules[*].validate[*].failureAction に移ります。値はEnforceとAuditの2つで、指定しなければAuditです。Enforceは違反したリクエストをブロックし、Auditは通したうえで違反をレポートに残します。
なぜ移したのかがこの変更の核心です。ポリシー単位のフィールドだったころは、1つのポリシーの中のすべてのvalidateルールが同じ強制レベルを共有していました。あるルールは確実だからブロックし、別のルールはまだ観察だけにしたい、という区別をするにはポリシーを2つに割るしかなく、マッチブロックが複製されて管理対象が増えました。ルール単位に降りてくると、1つのポリシーの中でルールAはEnforce、ルールBはAuditにできます。新しいルールを既存のポリシーにAuditで載せて数日レポートを見て、そのルールだけEnforceに上げる流れが、ポリシーを割らずに実現できます。
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-run-as-non-root
spec:
rules:
- name: check-security-context
match:
any:
- resources:
kinds:
- Pod
validate:
# ポリシー単位のvalidationFailureActionではなくルール単位で
failureAction: Enforce
message: 'Containers must run as non-root'
pattern:
spec:
containers:
- securityContext:
runAsNonRoot: true
- name: warn-on-missing-limits
match:
any:
- resources:
kinds:
- Pod
validate:
# 同じポリシー内でこのルールだけ観察モード
failureAction: Audit
message: 'Containers should declare resource limits'
pattern:
spec:
containers:
- resources:
limits:
memory: '?*'
一緒に動いたフィールドも知っておくほうがよいです。webhookTimeoutSeconds と failurePolicy も1.13からdeprecatedと表示され、それぞれ webhookConfiguration.timeoutSeconds、webhookConfiguration.failurePolicy に移ります。schemaValidation は1.11からdeprecatedで、ドキュメント上いまは何の効果もありません。逆に使われ続けるポリシー単位のフィールドもあります。background は既存リソースを走査して違反を見つけレポートを生成する動作をオンオフし、既定値はtrueです。admission はadmission controlの段階でルールを適用するかを決め、既定値はtrueで、falseにするとbackground専用のポリシーになります。applyRules はマッチしたリソースにルールをいくつ適用するかで、Oneなら最初のマッチで止まり、Allが既定値です。
spec:
background: true # 既定 true — 既存リソースのスキャンとレポート生成
admission: true # 既定 true — falseならbackground専用
applyRules: All # All(既定)または One(最初のマッチで中断)
webhookConfiguration:
timeoutSeconds: 20 # 1.13からwebhookTimeoutSecondsの代わりに
failurePolicy: Fail # 1.13からspec.failurePolicyの代わりに
バージョンによってどちらのフィールドが実際に読まれるかが異なります。正確なフィールドは使用中のバージョンのドキュメントで確認してください。
7. ルールはどこで、どの順序で実行されるのか
Kyvernoは1つのエンジンのように見えますが、ルールの種類ごとに実行される場所が違います。mutateルールはmutating webhookで、validateルールはvalidating webhookで動きます。Kubernetes APIサーバーはmutating admissionを先に呼び、そのあとvalidating admissionを呼ぶので、validateルールが見るオブジェクトはすでにmutateルールが手を入れたあとのオブジェクトです。この順序を知らないと自分のポリシーに自分が引っかかります。サイドカーを注入するmutateポリシーと、すべてのコンテナにリソース制限を要求するvalidateポリシーを一緒にかけておくと、注入されたサイドカーも制限チェックの対象になり、注入スペックに制限を入れていなければデプロイがブロックされ、ログには利用者が書いてもいないコンテナ名が出ます。サイドカー注入ポリシーを作るときにリソース制限も一緒に入れるのは、好みではなく要件です。
generateルールは場所がまったく違います。admissionでリソースを直接作らずUpdateRequestを残し、実際の生成はbackground controllerがあとから行います。そのためネームスペースを作った直後に生成物を確認するとまだ無いことがありますが、これはバグではなく設計です。生成されないときはポリシーを疑う前に、UpdateRequestが残っているかをまず見ます。リクエスト自体が無ければマッチしていないということで、リクエストはあるのにリソースが無ければbackground controller側の問題です。この一度の分岐で調査範囲が半分になります。
kubectl -n kyverno get updaterequests
kubectl auth can-i create helmrepositories --as system:serviceaccount:kyverno:kyverno-background-controller
synchronize: true もタダではありません。ソースが変われば生成物を追従させ、生成物が手で修正されれば元に戻します。つまり対象ネームスペースの数だけ監視と書き込みが増えるということです。ネームスペースが5つのクラスタでは何も感じませんが、数百のクラスタではbackground controllerの常時負荷になります。そしてこのコントローラは最小限の権限だけを持って導入されます。追加の権限は使う側が付ける必要があるので、標準リソースでないものをgenerateし始めるときは、上のauth can-iをそのリソース名に変えて先に確認します。権限が無いとadmissionは成功し、リソースだけが静かに作られません。もっとも気づきにくい種類の失敗です。
最後に、applyRules がOneならマッチしたリソースに最初のルールだけが適用されて止まります。ルールを順に並べたのに後ろのルールがなぜ動かないのかを探しているなら、このフィールドから確認します。
8. ポリシーをローカルで回してからクラスタに載せる
ポリシーはクラスタに載せる前にローカルで回せますし、この習慣ひとつで事故のほとんどが防げます。kyverno apply にポリシーファイルと、--resource で検査対象のマニフェストを渡します。変数を使うポリシーなら --set で1つずつ注入するか、-f で値ファイルを渡します。出力は -t で表、--detailed-results で詳細、-p でレポート形式を選びます。失敗やエラーがあれば終了コードが1になるので、CIのゲートとしてそのまま使えます。ポリシーを直すたびにこのコマンドを回せば、マッチブロックを書き間違えて何にもマッチしないポリシーを作り、通ったと勘違いする事故も防げます。何にもマッチしないポリシーはクラスタで静かに通すだけなので、人の目には正しく動くポリシーと区別がつきません。
kyverno apply policy.yaml --resource pod.yaml
kyverno apply policy.yaml --resource pod.yaml --set namespace=prod,team=payments
kyverno apply policy.yaml --resource pod.yaml -f values.yaml
kyverno apply policy.yaml --resource pod.yaml -t --detailed-results
kyverno apply policy.yaml --resource pod.yaml --policy-report
# 失敗やエラーがあれば 1
echo $?
ローカルの検証を通ったら、同じコマンドをクラスタに向けて回せます。-c は現在のコンテキストのクラスタに接続して検査し、ポリシーをgitソースから直接取得する形もドキュメントが例示しています。既存のリソースが新しいポリシーにどれだけ引っかかるかを先に見たいときにこの組み合わせが役立ちます。ここまで通ったらポリシーを実際に適用し、ポリシー一覧とレポートでクラスタでの姿を確認します。generateルールがあればUpdateRequestまで一緒に見ます。
kyverno apply policy.yaml --cluster
kyverno apply https://github.com/kyverno/policies/openshift/ --git-branch main --cluster
kubectl apply -f policy.yaml
kubectl get cpol,pol -A
kubectl -n kyverno get updaterequests
9. 失敗パターンと診断の順序
ポリシーが期待どおりに動かないとき、いちばん多い間違いはポリシーのYAMLから覗きはじめることです。ポリシーの文法が原因であることは思ったより少なく、多くはポリシーがそもそも評価されていないか、変数が空の値に置換されたケースです。順序を決めておくとずっと早く終わります。
1つ目、ポリシーは準備できているか。ポリシー一覧のready列がすべてtrueかを見ます。ドキュメントが診断の最初の段階として挙げているのがこれです。2つ目、webhookは登録されているか。Kyvernoは2種類のwebhookとして登録され、登録されていなければポリシーは存在してもどのリクエストも通りません。何もブロックされないのにポリシーは問題なさそうに見える状況の大半がここです。
kubectl -n kyverno get po
kubectl get cpol,pol -A
kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations
3つ目、変数が静かに空の値へ置換されるケースです。apiCallのjmesPathが実際のレスポンス構造と合っていないと、エラーではなく空の値が返り、その空の値が条件に入ると条件が常に真か常に偽になります。ポリシーは正常に見えるのに結果だけがおかしい、という典型的な形です。これを目で見るにはログの詳細度を上げる必要があります。ドキュメントは -v=4 を変数置換が見えるレベル、-v=6 を最大の詳細度として案内しています。それでも掴めなければ dumpPayload=true でAdmissionReviewの全文を出力し、実際に届いたオブジェクトが何かを確認します。
kubectl -n kyverno edit deploy kyverno-admission-controller
kubectl -n kyverno logs <pod_name> -f
# クライアント側のスロットリングが見えたらQPSとバーストを上げる
# --clientRateLimitQPS=500 --clientRateLimitBurst=500
4つ目、foreachの list を波括弧で包んだケースです。listはJMESPath式そのものを受け取るので、変数の表記で包みません。この記事のforeachの例が引用符だけを付けているのはそのためです。5つ目、アンカーの誤用です。条件付き ()、等値 =()、存在 ^()、否定 X() はそれぞれ意味が違い、とくに否定アンカーを値の比較と勘違いすることが多いです。否定アンカーはそのキーが存在してはならないという意味で、値が違えばよいという意味ではありません。
6つ目、background controllerの権限です。generateされないときは7節のauth can-iを対象リソースに変えて確認します。7つ目、admission reportが積み上がるケースです。ドキュメントは、reports controllerが正しく動作していないか、admission reportの集計が追いつかないときにレポートが蓄積されると説明しています。まずreports controllerの状態を見て、クライアント側のスロットリングが見えたらQPSとバーストを上げます。
最後は最悪のケースです。ポリシーがAPIサーバーを塞いで何もデプロイできなくなったら、webhook設定を削除するかadmission controllerを0に縮めてクラスタを先に生き返らせます。この2つはポリシー強制を丸ごと切るものなので、原因を直したあと必ず元に戻さなければなりません。
10. 使わないほうがよい場面
検査したいものがフィールド1つの値で、それ以上何も要らないなら、Kubernetesに組み込みのValidatingAdmissionPolicyとCELで十分です。コンポーネントを1つ減らして運用するということは、アップグレード対象が1つ減り、障害時に疑う場所が1つ減り、webhook証明書の更新を気にする対象が1つ減るということです。KyvernoにもCELベースのvalidateはありますが、それはすでにKyvernoを運用しているときに有用な選択肢であって、Kyvernoを導入する理由にはなりません。Kyvernoを正当化するのはgenerateとmutate、そしてイメージ検証のようにin-treeのポリシーではできない仕事です。
mutateで埋めている値が、実はチャートの既定値であるべきケースもよくあります。Helmのvaluesに入れればgitで見えてレビューされタグでロールバックできますが、mutateで入れるとクラスタでしか見えません。半年後になぜこのアノテーションが付いているのか誰も分からない状態になり、マニフェストと実際のオブジェクトが違うという事実がデプロイツールのドリフト検知とずっと戦うことになります。組織全体に強制すべき値だけをmutateに置き、チームが変えられるべき値はチャートに置くほうがよいです。
ワイルドカードで全リソースにマッチするポリシーはとくに注意が必要です。すべてのリクエストがwebhookを通ることになり、クラスタ全体に遅延税をかけるのと同じで、このコストはポリシー1つの性能ではなくAPIサーバーへ来るすべてのリクエストに付きます。タイムアウト内に応答できなければ failurePolicy の既定値Failによってリクエストは拒否されるので、Kyvernoが遅くなった瞬間にクラスタが遅くなるのではなくクラスタが止まります。マッチを必要な種類とネームスペースに絞る作業は、性能チューニングではなく可用性の作業です。
generateとsynchronizeで数百のネームスペースのリソースを管理するのも考え直す価値があります。その仕事はGitOpsのツールのほうが得意で、何よりgitに残ります。Kyvernoのgenerateは、ネームスペース作成のようにadmissionの時点でしか分からないイベントに反応しなければならないときに値打ちがあります。
11. 参考資料
- Kyverno — Validate Rules —
validationFailureActionのdeprecationとfailureAction、foreachのlistの表記、アンカーの種類、podSecurity/CEL/assertのバージョン適用範囲(2026-08-16 確認) - Kyverno — Policy Settings —
background、admission、applyRules、webhookTimeoutSecondsとfailurePolicyの1.13 deprecation(2026-08-16 確認) - Kyverno CLI — kyverno apply —
--resource、--set、--values-file、-t、--detailed-results、--policy-report、--cluster、失敗時の終了コード1(2026-08-16 確認) - Kyverno — Troubleshooting — ポリシーのready確認、webhook登録の確認、
kubectl -n kyverno get updaterequests、background controllerの権限確認、admission reportの蓄積、-v=4/-v=6/dumpPayload=true(2026-08-16 確認)
12. まとめ
- validate: パターンマッチング、deny条件、CEL式、foreachで多様な検証
- mutate: Strategic Merge Patch、JSON Patchでリソースを自動修正
- generate: data/cloneベースのリソース自動生成、synchronizeで同期
- 変数システム: JMESPath、API呼び出し、ConfigMapルックアップで動的ポリシー
- アンカーシステム: 条件付き、否定、等値アンカーで精密なパターンマッチング
- 強制レベル: ポリシー単位の
validationFailureActionからルール単位のfailureActionへ移行 - 実行場所: mutateはmutating webhook、validateはvalidating webhook、generateはbackground controller
次の記事ではKyvernoのイメージ検証機能とサプライチェーンセキュリティを扱います。