In a Kyverno autogen rule, what replaces spec.containers for a Deployment and for a CronJob?
answer
- one Pod template, two depths
- controllers nest the template once
- CronJob holds a Job template first
- spec.template.spec versus spec.jobTemplate.spec.template.spec
basics
~20 sFor a Deployment, StatefulSet, DaemonSet or Job the path becomes spec.template.spec.containers. For a CronJob it becomes spec.jobTemplate.spec.template.spec.containers, because the Pod template sits under a Job template. That extra depth is why CronJob gets its own generated rule.
solid answer
~40 sAutogen rewrites the rule body so it addresses the Pod template where that kind actually keeps it. Deployment, StatefulSet, DaemonSet and Job all nest the template once, so `spec.containers[*].env` becomes `spec.template.spec.containers[*].env`, and one derived rule - `autogen-<rule>` - serves all of them. A CronJob nests twice: the Pod template lives under `spec.jobTemplate.spec.template.spec`, so a second derived rule, `autogen-cronjob-<rule>`, carries the deeper prefix. The match block is rewritten too, from kind `Pod` to the controller kinds. Everything else - the container list traversal, the condition or pattern, the message - is the logic you wrote, relocated. When a rule behaves for Deployments but not CronJobs, the depth difference is the first thing to check.
code
yaml · 20 linesapiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-report
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: report
image: reports:1.4.2
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-creds
key: password
...go deeper
Know that a controller keeps its Pod spec under a template, so the same containers live at a deeper path than they do in a bare Pod.
Be able to state both prefixes from memory and explain why CronJob needs its own derived rule rather than sharing the controller one.
Use the depth difference as a diagnostic: when a control holds for Deployments but not CronJobs, decide quickly whether the variant is missing or is present and mis-pathed.
Set the authoring convention - rules written at the Pod shape only, messages phrased about the requirement - so that coverage does not depend on each author remembering two path prefixes.
## The two depths Every Kubernetes workload controller that produces Pods carries a **Pod template**: a copy of a Pod spec that the controller stamps out. The templates sit at two different depths. **One level down** - Deployment, StatefulSet, DaemonSet, Job (and the ReplicaSet layer beneath a Deployment): ``` spec.template.spec.containers[*] ``` **Two levels down** - CronJob, which holds a *Job* template that in turn holds the Pod template: ``` spec.jobTemplate.spec.template.spec.containers[*] ``` That single structural fact explains the whole shape of Kyverno's autogen output. A rule authored against a Pod addresses `spec.containers[*]`. To make the same logic apply to a controller, Kyverno prefixes the paths inside the rule body. Because there are two prefixes, there are two derived rules: - `autogen-<rule>` - match rewritten to the controllers that nest once, paths prefixed with `spec.template.spec` - `autogen-cronjob-<rule>` - match rewritten to CronJob, paths prefixed with `spec.jobTemplate.spec.template.spec` ## What is rewritten and what is carried over Rewritten: - the **match kinds**, from `Pod` to the controller kinds - the **field paths inside the rule body**, so a pattern or a condition that traversed `spec.containers` now traverses the template's container list Carried over unchanged: - the **decision logic** itself - the same containers are examined, the same field is judged, the same verdict is produced - the **message** - one string, authored once, shown to whoever was refused, whatever kind they submitted That asymmetry is worth internalising. Take the control `no container may receive a Secret through env.valueFrom.secretKeyRef or envFrom.secretRef; mount it as a projected volume instead`. The logic travels perfectly. The message does not adapt: if you wrote "remove secretKeyRef from spec.containers", the author of a CronJob reads a path that does not exist anywhere in their manifest, and goes looking for it. Write the message about the *thing* - "mount the Secret as a volume rather than injecting it into a container's environment" - and it reads correctly at every depth. ## Why this is the first thing to check when coverage looks uneven The symptom is specific and common: the rule refuses Deployments exactly as intended, and lets an identical violation through in a CronJob. Two candidate causes, and they are distinguished by reading the policy back from the cluster: 1. **The CronJob variant does not exist.** The controller list was narrowed, so no rule with the `autogen-cronjob-` prefix was derived. Nothing failed; a whole kind is simply outside the rule. 2. **The CronJob variant exists but does not fire.** Then the rule's own logic does not survive relocation to a deeper path - typically because the author hard-coded a path prefix rather than writing against the Pod shape, so autogen prefixed an already-prefixed path. That second failure mode is the argument for the discipline of writing rules **only** at the Pod level and letting autogen do the prefixing. The moment you hand-write `spec.template.spec.containers` into a rule that also matches Pod, you are fighting the rewrite. ## Reading a generated rule When you read the policy back, the derived rules look like ordinary rules: a match block naming controller kinds, and a body whose paths start with the template prefix. There is nothing magical to decode. The two useful checks are: - Does the `autogen-cronjob-` variant exist at all? - In each variant, does the path in the body actually match the shape of that kind - one `template` for the controller variant, `jobTemplate` then `template` for the CronJob one? If both are true, the control covers every workload kind that can produce a Pod, from one authored rule. ## Summary table | Kind | Where the Pod spec lives | | --- | --- | | Pod | `spec` | | Deployment, StatefulSet, DaemonSet, Job | `spec.template.spec` | | CronJob | `spec.jobTemplate.spec.template.spec` | One authored rule, two rewrites, three depths covered.
- Why does Kyverno emit a separate CronJob variant instead of one derived rule for everything?Because a single path prefix cannot address both depths. Deployment, StatefulSet, DaemonSet and Job keep the Pod template at `spec.template.spec`; a CronJob keeps it at `spec.jobTemplate.spec.template.spec`. Two prefixes means two derived rules.
- What happens if you hand-write spec.template.spec into a rule that matches Pod?You get a rule that is wrong twice over: it does not match a bare Pod's actual shape, and autogen prefixes the already-prefixed path when it derives the controller copies. Write against the Pod shape and let the rewrite add the prefix.
- Is the denial message rewritten along with the paths?Treat it as not adapting. One message string is authored once and shown to whoever was refused, whatever kind they submitted, so a message naming a Pod field path will confuse the author of a Deployment or CronJob. Write it about the requirement, not the path.
saying these in an interview costs you the question
- Uses spec.template.spec for a CronJob, missing the jobTemplate level
- Hand-writes template paths into a rule that matches Pod
- Thinks autogen rewrites only the match block, not the body
- Expects the message text to adapt to the refused kind