Skip to content
Notifications
Clear all

Thoughts on the new Kyverno 2.0 policy-as-code approach?

3 Posts
3 Users
0 Reactions
1 Views
(@devops_grunt)
Estimable Member
Joined: 4 months ago
Posts: 159
Topic starter   [#5183]

Been running Kyverno in production for about two years now, managing everything from simple required labels to complex image validation. The 2.0 release, specifically the shift towards a more declarative, policy-as-code approach, is a significant change that has some real teeth to it. It's moving away from the old "generate" and "mutate" focused model to something that feels more like Gatekeeper OPA, but with the YAML-native and Kubernetes-aware advantages Kyverno always had.

My initial take after migrating a dozen or so policies is that it's more powerful but also more complex. The new `match` and `exclude` blocks with their resource filters are far more expressive. The `validation` rule writing now feels like you're writing a proper policy language, not just a simple overlay. However, this comes with a steeper learning curve for team members who were used to the old style.

Here's a concrete example. We had a policy to enforce that all `ConfigMaps` in a specific namespace had a `managed-by` label. Old style was simpler, but less flexible. New style is more verbose but lets you build logic.

Old 1.10-style snippet:
```yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-managed-by-label
spec:
rules:
- name: check-for-label
match:
resources:
kinds:
- ConfigMap
namespaces:
- my-apps
validate:
message: "All ConfigMaps must have a 'managed-by' label."
pattern:
metadata:
labels:
managed-by: "?*"
```

New 2.0-style snippet:
```yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-managed-by-label
spec:
background: false
validationFailureAction: Enforce
rules:
- name: check-managed-by
match:
any:
- resources:
kinds:
- ConfigMap
namespaces:
- my-apps
preconditions:
all:
- key: "{{ request.operation }}"
operator: NotEquals
value: DELETE
validate:
message: "The label `managed-by` is required."
manifest:
patches:
- patch: |
- op: add
path: /metadata/labels/managed-by
value: "{{ request.object.metadata.labels."managed-by" }}"
```

See the difference? The new one uses a `manifest` block with a JSON Patch. It's more lines, but you can see how this opens the door for far more sophisticated logic and even dynamic values. The `preconditions` block is also a huge win for avoiding unnecessary validations.

The pros I'm seeing so far:
* Much finer-grained control over policy application with `any`/`all` in match/exclude.
* The `preconditions` and `context` sections allow for logic that depends on external data (like a ConfigMap lookup), which was clunky before.
* The policy reports are more detailed, which helps when debugging why something was blocked.
* The ability to write policies that can both validate *and* mutate in a single, ordered rule is a game-changer for certain compliance flows.

The cons:
* Migration is non-trivial. The API change breaks all existing policies. The `kyverno migrate` CLI helps, but you still need to test extensively.
* Increased verbosity means more to maintain. A simple label check now feels like overkill.
* The learning curve for new team members just got steeper. "Policy-as-code" sounds great until you have to explain JSON Patch to a platform team that just wants to enforce labels.

I'm also diving into the new `verifyImages` rules with Cosign, which are now more integrated into this same declarative model. Has anyone else run a production workload through the migration? Specifically, I'm curious about performance impact with larger policy sets, or if anyone has hit any sharp edges with the new `mutate` and `generate` rules under this model. Also, are we all just going to end up writing custom controllers for truly complex logic, or does this 2.0 approach cover enough ground?


Automate everything. Twice.


   
Quote
(@danielr23)
Trusted Member
Joined: 1 week ago
Posts: 67
 

The migration complexity is the real hidden cost. We ran into it with our image registry policies. The new `match`/`exclude` logic is indeed more powerful, but it broke our existing CI/CD validation pipeline because the test structure changed.

You need to factor in time for rewriting all your policy unit tests, not just the policies themselves. The old style was easier to mock.

Also, watch for performance on large clusters with the new resource filters. We saw a 15-20% increase in admission latency for complex `any`/`all` match blocks until we optimized the selectors.


Trust, but verify


   
ReplyQuote
(@emmaj)
Estimable Member
Joined: 1 week ago
Posts: 92
 

That point about the steeper learning curve is so true. We've seen it with our platform team - the shift to writing "proper policy language" means folks can't just tweak an overlay anymore. They have to understand the new logic structure.

One thing that helped us was creating a small internal cheatsheet mapping old patterns to new 2.0 constructs, especially for those `match`/`exclude` blocks. It cut down the "how do I rewrite this?" questions by half.

But I'm curious, for your ConfigMap label example, did you find the added verbosity was worth it for the extra flexibility in the long run? Like, are you using that logic to exclude certain resource types or namespaces now?



   
ReplyQuote