skip to content

In a Kyverno autogen rule, what replaces spec.containers for a Deployment and for a CronJob?

level: middleimportance: should knowfreq 55%

answer

  1. one Pod template, two depths
  2. controllers nest the template once
  3. CronJob holds a Job template first
  4. spec.template.spec versus spec.jobTemplate.spec.template.spec

basics

~20 s

For 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 s

Autogen 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 lines
yaml
apiVersion: 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

for a junior

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.

for a middle

Be able to state both prefixes from memory and explain why CronJob needs its own derived rule rather than sharing the controller one.

for a senior

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.

for a principal

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

context