Kyverno Generate Rules: The Immutable Fields That Break Helm Upgrades
You deploy a Kyverno generate policy.
Kyverno Generate Rules: The Immutable Fields That Break Helm Upgrades
You deploy a Kyverno generate policy.
Everything works perfectly.
Namespaces are getting ConfigMaps, RBAC objects are being created automatically, and the platform team is happy.
A few weeks later, you need to make what seems like a harmless change. Maybe you want to modify the match criteria, add a new exclusion, change the generated resource name, or update a field introduced in a newer Kyverno release.
You run:
kubectl apply -f policy.yaml
and immediately get:
admission webhook "validate-policy.kyverno.svc" denied the request:
change of immutable fields for a generate rule is disallowed
At first glance, this feels strange. Kubernetes lets you update lots of resources. Why would Kyverno reject what appears to be a normal policy change?
The answer lies in how generate rules work internally and how Kyverno tracks ownership of generated resources.
Understanding this behavior is critical because it often appears during Helm upgrades, GitOps deployments, and policy migrations — usually at the worst possible moment.
Why Generate Rules Are Different
Validation and mutation policies evaluate resources and then move on.
Generate rules are different.
A generate rule creates and manages downstream resources. Once those resources exist, Kyverno must maintain a relationship between:
- The policy
- The generate rule
- The triggering resource
- The generated resource
For example:
Namespace
│
▼
Generate Rule
│
▼
ConfigMap
Kyverno needs a reliable way to determine:
- Which resource was generated by which rule
- Whether synchronization should occur
- Whether a generated resource should be updated
- Whether it should be deleted when ownership changes
If fundamental parts of the rule were allowed to change freely, Kyverno would have no unambiguous way to determine what should happen to resources that already exist.
Instead of making assumptions, Kyverno protects consistency by treating key portions of generate rules as immutable.
What “Immutable” Actually Means
An immutable field is a field that cannot be modified after the resource has been created.
This doesn’t mean the policy is completely locked.
It means certain structural parts of the generate rule become fixed once the policy is installed.
When those fields change, Kyverno considers it a different generate rule rather than an update to an existing one.
As a result, the update is rejected.
The Fields That Typically Trigger Problems
The exact list can vary between Kyverno releases, but immutable fields generally include the parts that define the identity and behavior of a generate rule, such as:
- Rule name
- Match conditions
- Exclude conditions
- Preconditions
- Generated resource kind
- Generated resource name
- Generated resource namespace
- Generated resource API version
These fields define what the rule targets and what it creates.
Changing them fundamentally changes the relationship between Kyverno and already-generated resources.
Why Helm Upgrades Commonly Fail
This is where most teams encounter immutable-field validation.
Imagine a Helm chart containing a ClusterPolicy:
rules:
- name: generate-config
Months later someone updates it:
rules:
- name: generate-default-config
From Helm’s perspective this is simply a manifest change.
From Kyverno’s perspective the rule identity changed.
When Helm performs the upgrade, Kyverno validates the update and rejects it.
The result is a failed release:
UPGRADE FAILED:
admission webhook "validate-policy.kyverno.svc" denied the request
The same issue appears when changing:
- Match blocks
- Exclude blocks
- Generated resource names
- Generated resource kinds
- Generated resource namespaces
This is why generate policies require more planning than typical Kubernetes resources.
The Fields You Usually Can Change
Not everything is immutable.
Depending on the generate rule type and Kyverno version, updates are generally allowed for operational settings such as:
generate:
synchronize: true
and updates to the generated resource content itself.
For example:
generate:
data:
key: new-value
In these situations Kyverno can safely reconcile existing resources because ownership relationships remain unchanged.
Think of it this way:
You can often change what’s inside the generated resource.
You usually cannot change the identity of the generate rule itself.
Understanding synchronize
One of the most important generate settings is:
generate:
synchronize: true
Without synchronization, generation is typically a one-time event.
Kyverno creates the downstream resource and then stops managing it.
With synchronization enabled:
- Deleted resources are recreated
- Drift is corrected
- Updates are reconciled
Example:
generate:
synchronize: true
apiVersion: v1
kind: ConfigMap
name: proxy-config
If someone modifies or deletes the ConfigMap, Kyverno restores it to the desired state.
For production environments, this is often the preferred behavior.
Understanding background
Another commonly misunderstood setting is:
spec:
background: true
This allows Kyverno’s background controller to evaluate existing resources rather than only processing admission requests.
Without background processing:
- New resources are evaluated
- Existing resources may be ignored
With background processing:
- Existing resources can be re-evaluated
- Reconciliation becomes possible
- Generate workflows become more consistent
Most production generate policies use background processing.
Understanding generateExisting
By default, generate rules typically affect resources created after the policy is installed.
Suppose you already have:
40 Namespaces
and then install a policy that generates a ConfigMap.
Those namespaces may not automatically receive the generated resource.
That’s where:
generate:
generateExisting: true
becomes useful.
During policy installation, Kyverno performs a backfill operation and generates resources for existing matches.
After the initial backfill, the policy continues handling future resources normally.
This setting is particularly valuable during platform migrations and first-time policy adoption.
The Safe Way To Change Immutable Fields
Eventually you’ll need to modify something immutable.
When that happens, updating the existing policy won’t work.
The safest approach is:
Step 1: Export the current policy
kubectl get clusterpolicy <policy-name> -o yaml > backup.yaml
Step 2: Review generated resources
Identify resources currently owned by the generate rule.
kubectl get configmaps -A
kubectl get rolebindings -A
or whatever resource type your policy generates.
Step 3: Delete and recreate the policy
Update the policy manifest with the desired changes.
Then:
kubectl delete -f old-policy.yaml
kubectl apply -f new-policy.yaml
Step 4: Verify reconciliation
Check policy status:
kubectl get clusterpolicy
Check UpdateRequests:
kubectl get updaterequests -n kyverno
Investigate failures:
kubectl describe updaterequest <name> -n kyverno
UpdateRequests: The Hidden Component Most Engineers Ignore
When a generate rule executes, Kyverno doesn’t always create resources immediately.
Instead it creates an UpdateRequest.
Think of UpdateRequests as Kyverno’s work queue.
Whenever generate behavior appears broken, this should be one of the first places you investigate:
kubectl get updaterequests -n kyverno
and
kubectl describe updaterequest <name> -n kyverno
Many generate-policy troubleshooting sessions become much shorter once teams start looking here.
RBAC Still Matters
Generate rules require permissions.
If Kyverno doesn’t have permission to create the resource being generated, generation fails.
This becomes especially important when generating:
- Custom resources
- Cluster-scoped resources
- Third-party operator resources
Always verify the Kyverno background controller has the necessary RBAC permissions before deploying generate policies.
A Practical Example
The following policy creates a ConfigMap in every namespace and backfills existing namespaces.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: inject-proxy-config
spec:
background: true
rules:
- name: inject-proxy-configmap
match:
any:
- resources:
kinds:
- Namespace
generate:
generateExisting: true
synchronize: true
apiVersion: v1
kind: ConfigMap
name: proxy-config
namespace: "{{ request.object.metadata.name }}"
data:
data:
http_proxy: "http://proxy.internal:3128"
https_proxy: "http://proxy.internal:3128"
This policy:
- Watches namespaces
- Backfills existing namespaces
- Generates ConfigMaps automatically
- Reconciles drift
- Keeps generated resources synchronized
It’s a straightforward example that demonstrates the lifecycle of a generate rule without introducing unnecessary complexity.
The Real Lesson
Most engineers think of policies as configuration.
Kyverno generate rules are closer to infrastructure ownership contracts.
Once deployed, Kyverno begins tracking relationships between policies and generated resources. Certain fields become part of that ownership model, which is why they’re treated as immutable.
The result can be surprising during a Helm upgrade or GitOps deployment, but the behavior exists for a good reason: it prevents Kyverno from making unsafe assumptions about resources it already owns.
Before deploying a generate policy:
- Plan match and exclude logic carefully
- Choose generated resource names intentionally
- Understand synchronization behavior
- Test upgrades in a non-production environment
Because when the error appears:
change of immutable fields for a generate rule is disallowed
the problem usually isn’t the update itself.
It’s that Kyverno is protecting the ownership relationships it has already established.
메타데이터
- post_id
- eab4b59f40f0
- slug
- kyverno-generate-rules-the-immutable-fields-that-break-helm-upgrades-eab4b59f40f0
- url
- https://medium.com/@m-engineer/kyverno-generate-rules-the-immutable-fields-that-break-helm-upgrades-eab4b59f40f0
- canonical_url
- https://medium.com/@m-engineer/kyverno-generate-rules-the-immutable-fields-that-break-helm-upgrades-eab4b59f40f0
- author_url
- https://medium.com/@m-engineer
- status
- ok
- fetched_at
- 2026-06-09 15:37:30