Should a memory-limit rule target Pods or the Deployment and CronJob templates that create them?
answer
- completeness versus feedback
- every path ends in a Pod
- only the parent is on the user's connection
- CronJob nests one template deeper
- namespace defaulting is applied to Pods
basics
~20 sTarget Pods for enforcement — every path ends in a Pod. Target the workload kinds too for feedback: only there does the denial reach the author's terminal. The two views can disagree, so decide which your intent means.
solid answer
~50 sThese are two different guarantees. A rule on Pods is complete: bare Pods, Deployments, StatefulSets, DaemonSets, Jobs and anything an operator creates all end in a Pod, so nothing slips past — but the create is made by a controller, so the denial lands on a ReplicaSet or Job and the developer sees only a workload that never becomes ready. A rule on the workload kinds is loud: the rejection comes back on the developer's `kubectl apply`. But it is partial — you must enumerate every kind, the field path differs (`spec.template.spec.containers` on most, `spec.jobTemplate.spec.template.spec.containers` on a CronJob), and a Pod created some other way is unguarded. The usual answer is both: enforce at the Pod for the guarantee, duplicate at the workload kinds for the feedback, and accept that you now maintain two expressions of one intent.
go deeper
Know that the field you are checking exists in more than one place: on a Pod directly, and inside the pod template of the workload that creates it. Recognise that a Pod is what actually runs.
Be able to state both paths precisely, including the CronJob's extra nesting, and explain the trade: matching Pods catches every route, matching workloads is the only place the user gets an error back.
Demonstrate that you would ship both and know which is authoritative, and name a concrete case where they disagree — namespace defaulting applied to Pods but not to templates is the standard one.
Frame it as a maintenance decision: two expressions of one rule is duplication you are choosing to pay for, in exchange for developer feedback. Decide whether a generated pair or better denial alerting is the cheaper answer.
## The two targets are not interchangeable The same intent — "every container declares a memory limit" — can be expressed against the Pod or against the objects whose templates produce Pods. They enforce different things and fail differently. **Targeting Pods gives completeness.** Every workload path in Kubernetes terminates in a Pod object passing through admission. A Deployment goes Deployment to ReplicaSet to Pod; a CronJob goes CronJob to Job to Pod; a DaemonSet, a StatefulSet, a bare Pod, and any Pod an operator or a custom controller creates from a resource kind you have never heard of — all of them create a Pod through the ordinary API. One rule at the Pod closes every one of those paths, including the ones that do not exist yet. **Targeting the workload kinds gives feedback.** The Deployment create or update is the request the human actually made, on a connection the human's `kubectl` is holding. A denial there is printed with the rule's message, the exit code is non-zero, and CI fails the change. Nobody has to go looking. ## The costs of each Targeting Pods costs you the feedback path. The requester is a controller, so a rejection is recorded against the intermediate object — a create-failure event on the ReplicaSet and a `ReplicaFailure` condition, or a create-failure event on the Job — and retried with backoff. The developer sees a Deployment stuck at zero ready replicas, or a CronJob quietly producing nothing. Targeting workload kinds costs you coverage and repetition. You must list every kind that carries a pod template, and the path to the containers is not uniform: it is `spec.template.spec.containers[]` on Deployment, StatefulSet, DaemonSet, ReplicaSet, ReplicationController and Job, but `spec.jobTemplate.spec.template.spec.containers[]` on CronJob, and `spec.containers[]` on a bare Pod. Miss one kind and the guardrail has a hole; add a new operator that creates Pods directly and the hole reopens silently. ## The subtle part: the two views can genuinely disagree Namespace defaulting makes this concrete. A LimitRange with default limits is applied by the built-in LimitRanger admission plugin, and it acts on **Pods**, during mutating admission, before validating rules run. It does not rewrite a Deployment's pod template. So for a Deployment whose template declares no memory limit, in a namespace with a defaulting LimitRange: - a rule on the **Pod** sees the defaulted limit and **passes**; - a rule on the **Deployment template** sees no limit and **fails**. Neither is a bug. They answer different questions. "No container may run without a memory limit" is a runtime property, and the Pod is where it is true or false. "Authors must declare the limit explicitly rather than inherit whatever the namespace happens to default today" is an authoring property, and only the template can express it. Decide which one you mean before you write the match — an interviewer will push on exactly this, because teams routinely write the template rule, believe they have the runtime guarantee, and do not. ## The pragmatic answer Enforce at the Pod, because that is the security boundary and it is the only place the guarantee holds. Add the same check at the workload kinds so the change is rejected while the author is still looking at it. Treat the workload-level rule as a usability layer, not as the control, and be explicit that it is a duplicate — two expressions of one intent will drift, and the one that matters is the Pod-level one. If you can only maintain one, keep the Pod-level rule and invest in surfacing its denials — an alert on admission denials by namespace beats a second rule that misses a kind. And whichever you choose, avoid writing the template rule alone and calling the estate covered. ## Ordering trap worth naming Because mutating admission runs before validating admission, anything that injects fields into the Pod — a defaulting plugin, a sidecar injector — has already run by the time your Pod-level rule sees the object. A template-level rule sees the raw author intent instead. That ordering, not the rule text, explains most of the disagreements between the two.
- Give a case where the Pod-level and template-level rules legitimately disagree.A namespace with a defaulting LimitRange. The built-in LimitRanger plugin injects the default limit into the Pod during mutating admission, so a Pod-level rule passes while a rule on the Deployment's pod template still sees nothing declared and fails. Both are correct; they encode different intents — runtime guarantee versus explicit authorship.
- Which kinds do you have to enumerate if you go the workload-template route?Everything carrying a pod template: Deployment, StatefulSet, DaemonSet, ReplicaSet, ReplicationController and Job at `spec.template.spec`, CronJob one level deeper at `spec.jobTemplate.spec.template.spec`, plus bare Pods at `spec` itself. Any custom kind whose controller creates Pods is not covered at all — which is the argument for keeping the Pod-level rule as the real control.
- If you maintain both, how do you stop them drifting apart?Generate them from one source of truth rather than writing them twice, keep the parent-level version non-authoritative and test both against the same fixtures — a workload manifest and the Pod it produces. Then treat any case where they disagree as something to explain deliberately, like namespace defaulting, rather than as an accident.
saying these in an interview costs you the question
- Believes a rule on Deployments covers every Pod
- Forgets the CronJob's extra jobTemplate nesting level
- Assumes the Pod and the template always show the same fields
- Treats parent-level checks as the security boundary
- Ignores mutation that runs before the validating rule