mutate and generate — Filling In Instead of Refusing
In one sentence
Validation blocks what is wrong, mutation fills in what is missing, and generation creates what must exist. Of the three, the latter two are what actually reduce people's work.
Why it was needed
Suppose you apply a policy that blocks every Pod without resource limits. The rule is followed, but there is a cost. Ten teams must each fix their manifests, and in the process ten inquiries of "how much should I put in?" arrive. What the organization wanted was "a state with no Pods lacking limits," not "a state where ten teams each agonize on their own."
A mutation rule turns this around. If a value is absent, it puts in the organization's default, and if a value is already there, it leaves it alone. Then the rule is followed and nobody is blocked. A generation rule goes one step further. The moment a new namespace is created, it also creates the default NetworkPolicy, ResourceQuota, and LimitRange. The work of a person following an onboarding checklist and running kubectl apply three times disappears.
But the stronger it is, the bigger the side effects. Mutation makes the manifest the user wrote and the actual object in the cluster different. Half a year later the question "who attached this annotation?" comes up, and it keeps fighting with the deployment tool's drift detection. That is why you need a criterion for judging. Put into mutation only the values that must be enforced across the whole organization, and leave the values a team should be able to change as defaults in the chart or manifest.
How it works
There are three methods of mutation.
| Method | When | Characteristic |
|---|---|---|
patchStrategicMerge |
When adding or filling fields in a map | Easy to read because it is written in the same shape as the object |
patchesJson6902 |
When handling a specific position in an array | Pinpoints the position with op, path, and value |
foreach |
A list whose length is not fixed, such as containers | Takes the list with list and points to the current item with element |
Arrays are the criterion that separates these three. Strategic Merge is natural for maps, but with arrays it is hard to specify "which item." JSON Patch can use an index, as in /spec/containers/0/imagePullPolicy, but it needs the premise that the index is fixed. When you do not know how many containers there are, you ultimately have to loop over the list with foreach.
The most important syntax in mutation is the add anchor +(). +(imagePullPolicy): IfNotPresent means "put it in if it is absent, and do not touch it if it is already there." Without this anchor, the policy quietly overwrites values the user specified. If a team wrote cpu: 500m and the policy changes it to 200m, that team can no longer trust its own manifest. The conditional anchor () does a different job, "apply what is below only when this condition matches," so do not confuse them.
There are two methods for generation rules. data writes the content to create directly inside the policy, and clone copies using an existing resource as the source. Anything whose content must not be written in a policy file, such as a Secret, must use clone, because policies usually go into git. The two cannot be used together.
synchronize: true means it keeps watching after creation. If the source changes, it changes the copy to follow, and if someone edits the copy by hand, it reverts it. If you turn it off, it intervenes only once at creation and does not touch it afterward. Turning it on looks safer, but it is not free — watching and writes grow by the number of target namespaces.
Do not forget permissions either. The background controller is installed with minimal permissions, so you must attach create and update permissions for the kind of resource you want to create separately through a ClusterRole. Without the permission, the request succeeds and only the resource quietly fails to appear. It is the kind of failure that is hardest to notice.
What you see in the field
First, the incident where mutation and validation bite each other. If you inject a sidecar without putting limits in it, that sidecar gets caught by a validating policy that requires limits. Putting limits into the injection spec together is not a matter of taste but a requirement.
Second, a policy that modifies existing resources. If you use mutate.targets, you can modify another resource rather than the resource that triggered it. It runs in the background, not at admission, so it needs extra permissions, and if you turn on mutateExistingOnPolicyUpdate, existing resources are reworked all at once every time you change the policy. The stronger it is, the more narrowly you should use it.
Third, the boundary with GitOps. Managing the resources of hundreds of namespaces with generation rules and synchronization is something GitOps tools do better. Above all, it remains in git. The place where generation rules are valuable is when you must react to an event you can know only at that moment, such as namespace creation.
Fourth, the limits of this environment. The background controller does not run here, so mutate.targets and generation rules do not create actual resources. Instead, the kyverno CLI evaluates the same rules locally and shows you the mutated manifest and the resources that would be created. Grading looks at the structure of the policy YAML and its execution results.
What you will do in the next lab
First, in the mutation lab, you write a policy that adds a label, a policy that fills resource defaults with the add anchor, a rule that fixes an array item with JSON Patch, and a foreach rule that loops over the container list. Then you run the same policy on a Pod whose values already exist and see with your own eyes that it does not overwrite, and build a report contrasting before and after the mutation. In the generation lab that follows, you write an onboarding policy bundle that creates a NetworkPolicy, ResourceQuota, and LimitRange all at once in a new tenant namespace, and the RBAC needed for that policy to actually work.