skip to content

Kyverno refuses your Deployment with two near-identical messages - what causes the duplication?

level: middleimportance: nice to knowfreq 30%

answer

  1. two messages means two rules matched
  2. not a retry and not a display bug
  3. one control encoded in two places
  4. one name carries the autogen- prefix
  5. keep the Pod-level rule, delete the narrow one

basics

~20 s

Two rules matched the same object: the rule Kyverno derived from a Pod rule for Deployments, and a separately maintained rule that matches Deployment directly. Kyverno reports every failing rule, so the same control is stated twice.

solid answer

~40 s

Kyverno reports each rule that failed, so a duplicate message means the same control exists twice. The usual cause is history: a hand-written rule matching Deployment was added before anyone realised a Pod rule is auto-extended to controllers, and then a Pod-level rule for the same requirement was added later. Both match a Deployment, both fail, and the developer reads two versions of one instruction, sometimes with slightly different wording because they were written months apart. The fix is to keep the Pod-level rule - it covers StatefulSet, DaemonSet, Job and CronJob as well - and delete the hand-written controller rule. The related irritation is that one message string is shown for every kind, so wording that names a Pod field path reads wrong to whoever submitted a Deployment.

go deeper

for a junior

Recall that a denial lists every rule that failed, so two similar messages mean two rules matched rather than one rule firing twice.

for a middle

Be able to trace the duplication to a hand-written controller rule sitting alongside a derived one, and say which copy you remove and why.

for a senior

Explain the drift risk of a duplicated control and the verification step after deleting one, plus the message-wording habit that keeps denials readable for every kind.

for a principal

Own the cleanup as a standard: one requirement, one rule, authored at the Pod level - and a policy review that treats a near-duplicate rule as a defect rather than harmless redundancy.

## What a duplicate message is telling you When a request is refused, Kyverno reports the rules that failed - so two near-identical messages mean **two rules matched and both decided no**. It is not a retry, not a redelivery, and not a display bug. Somewhere in the policy set, the same requirement is encoded twice. The classic version of this involves autogen directly. Consider the control `no container may receive a Secret through env.valueFrom.secretKeyRef or envFrom.secretRef; mount it as a projected volume instead`. - An early policy matched `Deployment` explicitly and reached into `spec.template.spec.containers` by hand, because whoever wrote it did not know Pod rules are extended to controllers. - A later policy - written properly - matches `Pod`, and autogen derives a Deployment copy of it. Both now match a Deployment. Both fail. The developer at 5pm reads the same instruction twice, in two wordings, quoting two rule names, one of which carries an `autogen-` prefix they have never seen. The natural reaction is to assume something is broken and escalate, which costs someone else their evening too. ## Why the duplicate is worse than noise A duplicated control is not merely untidy: - **It drifts.** Two encodings of one requirement get updated separately. Add an exception to one - say, a specific system namespace - and the other keeps refusing. The result is a control whose behaviour nobody can state from reading either rule. - **It teaches the wrong lesson.** A developer who sees two messages learns that the gate is flaky, and the next genuine denial gets treated as noise. - **It hides which copy is load-bearing.** Delete the wrong one and you silently lose coverage of every kind except Deployment. ## The fix Keep the Pod-level rule; delete the hand-written controller rule. That direction matters. The Pod rule plus autogen covers bare Pods, Deployment, StatefulSet, DaemonSet, Job and CronJob from one place. The hand-written Deployment rule covers exactly one kind. Keeping the narrow one because it is the older, more familiar one is the mistake to avoid - and it is a common one, because the older rule is usually the one referenced in a runbook. After deleting, read the policy back from the cluster and confirm the derived rules for the remaining Pod rule exist for the kinds you expect. That is the same verification habit the whole feature demands: what enforces is the derived set, not the authored file. ## The neighbouring artefact: one message, every kind The other thing developers notice about derived rules is the message. Treat it as a single authored string shown to whoever was refused, whatever kind they submitted. It is not adapted per kind. So a message written while staring at a Pod - "remove secretKeyRef from spec.containers" - is what the author of a CronJob reads, and the path they are told to fix does not exist in their manifest; theirs is two templates deeper. The habit that fixes it is to write the message about the **requirement and the remedy**, not the location: - Poor: `secretKeyRef not allowed in spec.containers[*].env` - Better: `Secrets must be mounted as a projected volume, not injected into a container's environment. Replace env.valueFrom.secretKeyRef and envFrom.secretRef with a volume mount.` The second version is correct for a Pod, a Deployment and a CronJob alike, and it tells the person what to do rather than where you happened to be looking when you wrote it. ## What to check when you see duplication 1. List the rule names in the denial. An `autogen-` prefix identifies the derived one; the other is hand-written. 2. Find the hand-written rule and confirm it encodes the same requirement, not a subtly different one - occasionally the two really are different controls that happen to read alike, and then the fix is wording, not deletion. 3. Delete the narrow copy, keep the Pod-level one, and verify the derived set. 4. Fix the surviving message so it reads correctly for every workload kind.

  • Which of the two rules do you delete, and why that one?
    Delete the hand-written rule that matches Deployment directly. The Pod-level rule plus autogen covers bare Pods, Deployment, StatefulSet, DaemonSet, Job and CronJob from a single place; the hand-written one covers a single kind and has to be maintained in parallel forever.
  • Beyond the noise, what is the real risk of leaving both in place?
    Drift. Two encodings of one requirement get edited separately, so an exception added to one leaves the other still refusing, and nobody can state the control's actual behaviour from reading either rule alone.
  • How should the denial message be worded so it works for every workload kind?
    State the requirement and the remedy rather than a field path: mount the Secret as a projected volume instead of injecting it into a container's environment. One string is shown to whoever was refused, and a Pod-shaped path is meaningless to someone holding a CronJob.

saying these in an interview costs you the question

  • Thinks the API server retried the request
  • Assumes autogen derives two rules per controller kind
  • Keeps the hand-written Deployment rule and deletes the Pod rule
  • Reports it as a Kyverno defect without checking the policy set
  • Expects the message text to be rewritten per refused kind

context