Why can a Kyverno policy pass a local kyverno apply run yet decide differently in the cluster?
answer
- the file is not the admitted object
- defaults and mutations land first
- no API server to look things up
- no request means no user, no old object
- stub it with a values file
basics
~20 sBecause the CLI evaluates the raw file, while admission judges the object after API-server defaulting and mutating admission, and rules that read cluster state or the requesting user get nothing offline unless a values file supplies it.
solid answer
~50 sThree gaps. First, shape: the API server fills in defaults and mutating admission runs before validating admission, so the validated object is not the YAML you handed the CLI. Second, context: a rule with a `context` block that reads a ConfigMap, calls the API or looks up image metadata has no API server offline, so those lookups return nothing and the rule errors or silently takes the wrong branch. Third, the request itself: `request.operation`, `request.userInfo` and the old object on an update do not exist when you evaluate a file. The CLI's answer is a values file that stubs variables, namespace labels and context results, plus a user-info file for the requesting identity — kept in the repo beside the policy. A mismatch is either a missing stub or a rule leaning on state it should not.
go deeper
Remember the headline: the local run judges the file, the cluster judges the object after it has been defaulted and possibly rewritten. Knowing that a values file exists to stand in for cluster lookups is enough at this level.
Name the three gaps precisely — object shape, context lookups, and the absent request — and get the ordering right: mutating admission runs before validating admission. Be able to describe what a values file supplies.
Walk a mismatch investigation end to end: diff the stored object against the file, locate the field's origin, and decide whether the fix is a better stub or a simpler rule. Note that request-dependent rules cannot run in background scans either.
Take a position on how much cluster state a rule may depend on. Every lookup buys expressiveness and costs testability and stability; deciding where your platform draws that line is a standard you own, not a per-rule choice.
## The two inputs are not the same object It is tempting to think of `kyverno apply` as "admission, run early". It is not. It is the same rule engine pointed at a different input, and almost every surprise comes from the difference between those inputs. ### Gap 1 — defaulting and mutation change the object When a manifest is submitted, the API server applies defaults for fields you left out, and **mutating admission runs before validating admission** — that ordering is fixed by the API server's request pipeline. So a validating rule in a cluster sees an object that has already been filled in and possibly rewritten, by the API server's own defaulting, by Kyverno's `mutate` rules, and by anybody else's mutating webhooks. The CLI, given a file, sees the file. It will apply `mutate` rules from the policies you handed it, which is useful, but it knows nothing about defaults or about the other webhooks installed in your cluster. This cuts both ways. A rule that requires a field the API server would have defaulted in fails locally and passes in the cluster. A rule that forbids a value that a mutating webhook injects passes locally and fails in the cluster. ### Gap 2 — there is no API server to look things up in Kyverno rules can declare a `context`: a ConfigMap to read, an API call to make, image metadata to fetch. In the cluster those resolve against the live API. Offline there is nothing to resolve against. What you get is usually one of two failure shapes. Either the rule reports **error** because a variable it needs is unresolved, or — worse — a precondition that depends on the lookup evaluates to something you did not intend and the rule quietly **skips**, which reads as "no problem found" if you are only scanning the summary line. The remedy is the values file. It supplies concrete values for the variables the rules reference, labels for the namespaces the rules select on, and stand-ins for the results of context lookups, plus global values shared across policies: ``` kyverno apply policy.yaml --resource deploy.yaml --values-file values.yaml ``` Keeping that file in the repository next to the policy is the practice worth stating in an interview: it is the documented statement of what the rule assumes about the world, and it is reviewable. ### Gap 3 — there is no request Admission gives a rule things that only exist because somebody asked for something: which operation it is (CREATE or UPDATE), who asked (`request.userInfo` — the username and the groups), and on an update the **old object** alongside the new one. A file on disk has none of that. An update-only rule — one that fires when a particular field *changes* — cannot be exercised at all without supplying both versions, and a rule keyed on the requesting identity needs that identity supplied by hand through a user-info file. This gap is worth remembering for a second reason: it is the same gap that stops such rules running in a **background scan**. A scan reads a stored object; there is no request behind it, so the requesting user and the operation are unavailable there too. A rule that depends on them can only ever decide at admission. ## Diagnosing a real mismatch When CI is green and admission rejects the same change, work the gaps in order: 1. Fetch what the cluster actually holds or what the request carried, and diff it against the file. If the offending value is not in your YAML, it came from defaulting or from a mutation, and you are in gap 1. 2. Read the rule for a `context` block or a variable that resolves against cluster state. If there is one, you are in gap 2, and your values file is stubbing something that does not match reality. 3. Check whether the rule reads the operation, the user, or the old object. If it does, gap 3, and the local run never exercised that path. ## The trap in the fix The cheap fix for a red local run is to add another stub to the values file until it goes green. Sometimes that is right — the stub is describing the world honestly. Often it is a signal about the rule itself: a validating rule that only reaches the correct verdict when three cluster lookups line up is a rule that is hard to reason about, hard to test and fragile in production, because each lookup is a way for the decision to change without the policy changing. Where the same guardrail can be expressed against the object alone, that version is both testable offline and stable in the cluster. Stubs are for the assumptions you genuinely need, not a way to make an awkward rule look tested.
- What goes into a values file for an offline run?Concrete values for the variables the rules reference, labels for the namespaces the rules select on, stand-ins for the results of context lookups such as a ConfigMap read or an API call, and global values shared across several policies. It is the written statement of what the rule assumes about the cluster, which is why it belongs in the repository beside the policy.
- Why can a rule that reads the requesting user never be fully evaluated offline?Because that data only exists because someone made a request. A file on disk has no operation, no username and no groups, so the identity has to be supplied by hand. The same absence applies to a background scan over stored objects, which is why such rules are admission-only by nature.
- How do you tell a stubbing gap from a genuine policy bug?Diff the object the cluster judged against the file you evaluated. If the deciding value is absent from your YAML, it arrived through defaulting or a mutation and your local input was simply incomplete. If the value is identical in both and the verdicts still differ, the rule is reading cluster state, and the stub you supplied does not match what the cluster returns.
saying these in an interview costs you the question
- Believes the CLI evaluates the same object admission validates
- Assumes context lookups still resolve with no cluster present
- Thinks validating admission runs before mutating admission
- Adds stubs until the run goes green without asking why
- Reads a skipped rule as a passing rule