Kyverno's v1.17 release announcement carried a four-row deprecation table: ClusterPolicy and CleanupPolicy deprecated at v1.17 (January 2026), critical fixes only through v1.18 (April 2026) and v1.19 (July 2026), removal planned in v1.20 (October 2026). As of 16 October 2026 the newest published tag on the kyverno/kyverno releases page is v1.19.1 from 10 September, with v1.19.2-rc.1 cut on 15 October, so the removal release has not landed. Removal releases do execute on schedule, as anyone who met Kubernetes 1.37 dropping 18 kubelet flags already knows.
Two things make this worse than a rename. Kyverno's migration guide lists validate.podSecurity as not supported in the CEL policy types, and that field is how most fleets enforce Pod Security Standards. The converted upstream library also disagrees with Pod Security Admission today: a drift issue filed against kyverno/policies on 14 September 2026 is still open, and it documents a restricted-profile Pod that the new policy admits and PSA denies. Each tip below names the artifact and the command.
The tips
- Read the running image tag, not the Helm chart version. A chart pin and a container tag drift apart quietly, and the version that decides whether your policies still load is the one in the pod spec. Same failure shape as containerd 1.x against a 1.38 kubelet, where the pinned manifest and the installed binary disagreed. v1.18 and v1.19 accept legacy policies with warnings; v1.20 is where that stops.
kubectl -n kyverno get deploy -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.template.spec.containers[0].image}{"\n"}{end}'
- Qualify every inventory query with the API group, because the names collide. The legacy kinds are
ClusterPolicyandPolicyin groupkyverno.io. The replacements live in a group namedpolicies.kyverno.io, so a barekubectl get policyis ambiguous. Build the full list before you plan anything, the same discipline that keeps a kube-proxy IPVS to nftables move from turning into a discovery exercise mid-cutover.
kubectl get clusterpolicy.kyverno.io -o name
kubectl get policy.kyverno.io -A -o name
kubectl api-resources --api-group=policies.kyverno.io
- Count your
validate.podSecurityrules first: that set cannot be converted mechanically. The migration guide's answer for the field is "Not supported; usespec.validationsfor each pod security control," which turns one rule into one hand-written CEL expression per control. The count below is the real size of the project.
kubectl get clusterpolicy.kyverno.io -o json \
| jq -r '[.items[] | select(any(.spec.rules[]?; .validate.podSecurity != null)) | .metadata.name] | "podSecurity policies: \(length)\n\(.[])"'
- Grade converted policies against PSA, not against the policy they replaced. Two Kyverno policies agreeing with each other proves nothing about the standard you attest to. Label a staging namespace so the in-tree controller reports its own verdict without blocking:
kubectl label ns kyverno-migration-test \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
Per the Pod Security Admission docs, warn returns a warning on the API response and audit writes a pod-security.kubernetes.io/audit-violations annotation onto the audit event. Any fixture where PSA warns and the Kyverno report passes is drift you are about to ship.
- Test the seccomp case with nothing set anywhere. Issue #1537 states that for the restricted profile the
ValidatingPolicytreats an unset profile asRuntimeDefault, so a Pod with no seccomp setting at pod or container level passes where PSA denies it, while thepod-security-celClusterPolicy variant was fixed and is correct. The migration makes that control weaker without logging anything. Apply a Pod with nosecurityContext.seccompProfileand record which component denies it. - Fuzz the capabilities list with an empty string and a duplicate entry. The same issue records three capability defects: Kyverno accepts an empty capability name, so
add: [""]passes where PSA denies it at baseline; the restrictedexists_oneandsize() == 1checks reject duplicate entries that PSA accepts; and the ValidatingPolicyaddexpression raises a CEL error for any container that adds a capability. A CEL evaluation error is not a clean deny, so setfailurePolicydeliberately and confirm where an erroring expression lands in your cluster. That decision is the one covered in stopping Kubernetes admission control from failing open, and it has the same two bad answers here. - If you run Windows nodes, write the
spec.os.nameguard yourself. PSA exempts Windows Pods from theallowPrivilegeEscalation, capabilities and seccomp checks. Issue #1537 notes Kyverno carries nospec.os.nameguard for those controls, so conforming Windows Pods get denied by the converted policy. One query tells you whether this is your problem:
kubectl get nodes -l kubernetes.io/os=windows -o name
- Re-model per-namespace enforcement before
failureActionOverridesgoes away. The guide lists bothvalidate.failureActionOverridesandvalidate.allowExistingViolationsas unsupported and points at policy exceptions instead. A team running Audit in production and Enforce in a pilot namespace holds that split inside one object today; after conversion it becomes separate policies plusmatchConditions. Find every affected policy:
kubectl get clusterpolicy.kyverno.io -o json \
| jq -r '.items[] | select(any(.spec.rules[]?; .validate.failureActionOverrides != null or .validate.allowExistingViolations != null)) | .metadata.name'
- Build the exception list from today's policy reports, not from memory. With
allowExistingViolationsgone, a converted policy in Enforce mode starts rejecting updates to workloads that were already violating, so an unrelatedkubectl rollout restartfails on a Deployment nobody touched. Harvest current failures and turn them into scopedPolicyExceptionobjects before the flip:
kubectl get polr -A -o json \
| jq -r '.items[].results[]? | select(.result=="fail") | "\(.policy)\t\(.resources[0].namespace)/\(.resources[0].name)"' | sort -u
- Migrate exceptions in the same change as the policies. The guide calls for converting legacy
kyverno.ioPolicyException objects topolicies.kyverno.io. An exception in the old group exempts nothing from a new-group policy, so a policy-only migration re-blocks every workload you previously carved out. List them with the group pinned:kubectl get polex.kyverno.io -A -o name. - Treat the upstream policy library as a draft under test. Issue #1537 identifies the two sets it compares as
kyverno.io/v1ClusterPolicy underpod-security-cel/andpolicies.kyverno.io/v1alpha1ValidatingPolicy underpod-security-vpol/. The converted set still sits on the alpha surface even though v1.17 promoted the policy types tov1, and the issue was last updated on 1 October 2026 with no fix merged. Copy from it, test what you copied. - Fail CI on the deprecation warnings, and trust release notes over the guide's limitation table. The migration guide recommends
--warnings-as-errorsso a deprecated field breaks the pipeline instead of logging:
kyverno apply ./policies/ --resource ./tests/fixtures/ --warnings-as-errors
kyverno test ./policies/ --warnings-as-errors
One caveat on that page: it cites issues #14852 (skipBackgroundRequests for MutatingPolicy and GeneratingPolicy) and #14853 (useServerSideApply for GeneratingPolicy) as open gaps, and both were closed after being opened on 26 January 2026. Check each listed limitation against the notes for the version you install. If the proposal on the table is "move to Gatekeeper instead," price its own moving defaults: Gatekeeper v3.22.0 turned on sync-vap-enforcement-scope by default, which changes which namespaces ValidatingAdmissionPolicy enforces on, and gator test plus gator verify give you the equivalent CI gate.
Wrap-up
Tip 4 is the habit that decides this migration: PSA is the referee. A converted policy that matches the ClusterPolicy it replaced can still disagree with the standard in your control narrative, and issue #1537 is the evidence that the official library disagrees today. Build the equivalence suite before v1.20 ships, one fixture per control, both verdicts recorded, Windows nodes included. That suite plus the exception inventory from tip 9 is what an auditor will accept, and it gets expensive to produce once the removal release is already in your upgrade path.
Comments
Be the first to comment.