skip to content

In a Kyverno pattern over spec.containers, how many containers must match the sub-pattern?

level: middleimportance: should knowfreq 45%

answer

  1. a list pattern is not a filter
  2. the default quantifier is every element
  3. the caret anchor means at least one
  4. init containers are a separate array
  5. an optional list needs the equality anchor

basics

~20 s

All of them. A Kyverno list pattern is applied to every element of the resource array, so one container without a memory limit fails the rule. The existence anchor on the array key relaxes it to at least one element.

solid answer

~40 s

By default a pattern element written under a list applies to **every** element of that list in the resource, so a single container missing `resources.limits.memory` fails the rule and the pod is rejected. That universal behaviour is what a limits rule needs. The existence anchor, written on the array key as `^(containers)`, flips it to "at least one element must match" — right for "the pod must have at least one container doing X", wrong for a limits rule, where it would let a compliant sidecar cover for a non-compliant app container. Two practical corollaries: `initContainers` is a separate array and needs its own block, and that block must carry an equality anchor, `=(initContainers)`, or pods that legitimately have no init containers are rejected for the absence of the key itself.

code

yaml · 12 lines
yaml
pattern:
  spec:
    containers:            # applied to EVERY element of the array
    - resources:
        limits:
          memory: "?*"
          ephemeral-storage: "?*"
    =(initContainers):     # optional list: checked only when present
    - resources:
        limits:
          memory: "?*"
          ephemeral-storage: "?*"

go deeper

for a junior

Remember that a sub-pattern written under a list applies to every element of that list, so one non-compliant container is enough to fail the whole pod.

for a middle

Explain the three quantifiers you can express: universal by default, existential with the caret anchor on the array key, and optional-but-validated with the equality anchor on the list itself.

for a senior

Show that you check the containers, initContainers and volumes shapes separately, and that you can say why the existence anchor would silently gut a limits rule while looking correct in review.

for a principal

Own the review standard for list-shaped rules across the library, since existence and equality anchors are where a rule quietly stops covering part of the estate without anything turning red.

## Universal by default When a Kyverno pattern contains an array, the element you write is a template applied to every element of the corresponding array on the resource. Write one container sub-pattern and it must hold for container zero, container one and container seventeen. This is the right default for a guardrail: a rule that requires a memory limit means *every* container, or it means nothing, because the one container you did not cover is the one that will eat the node. Many authors arrive expecting the opposite — that a list pattern is a filter, matching if some element matches. That intuition comes from query languages, and it is exactly backwards here. ## The existence anchor flips the quantifier `^()` is the anchor that changes the quantifier from *all* to *at least one*, and it is written on the key whose value is the array: ```yaml pattern: spec: ^(containers): - name: "audit-sidecar" ``` That says the pod must have at least one container named `audit-sidecar`; the others are unconstrained. It only makes sense for existence claims. Applied to a limits rule it becomes actively dangerous, because one well-behaved container satisfies the rule on behalf of every badly-behaved one in the same pod — a policy that reports green while the thing it was written to prevent ships every day. The question to ask before reaching for `^()` is which sentence you mean: "every container must…" or "the pod must contain a container that…". Only the second is an existence claim. ## Optional lists and the absence trap A pod may have no `initContainers` key at all. If your pattern names `initContainers` unanchored, then by the ordinary rule that unanchored keys are required, every pod without init containers is rejected — for the absence of the key, not for anything about limits. Your rule now blocks a large fraction of legitimate workloads and the message will point at a path developers cannot make sense of. The fix is the equality anchor: ```yaml pattern: spec: containers: - resources: limits: memory: "?*" =(initContainers): - resources: limits: memory: "?*" ``` `=(initContainers)` means: if the key is absent, check nothing; if it is present, the element sub-pattern must hold for every init container. Presence stays optional; correctness does not. The same shape recurs one level deeper for volumes. A rule that every `emptyDir` volume declares a `sizeLimit` is written as an optional `volumes` list whose elements carry an optional `emptyDir` object — `=(volumes)` with `=(emptyDir)` inside it — because most volumes are not `emptyDir` and a pod may have no volumes at all. Get the anchoring wrong there and you either reject every pod with a ConfigMap volume, or you assert nothing. ## What the developer is told Because the check is per element, the rejection message names the index: `failed at path /spec/containers/2/resources/limits/memory/`. That index is genuinely useful — it distinguishes "you forgot limits on the app container" from "your injected sidecar has none", which are different conversations with different owners. If you use a conditional anchor to skip an element, the indices in the message still refer to positions in the resource, so they remain a reliable pointer. ## Summary Default is universal, `^()` makes it existential, `=()` makes the list itself optional, and each of the three says something different about the policy you are actually writing. Choosing among them is the whole content of a list-shaped pattern.

  • What would ^(containers) do to a memory-limit rule?
    It would break it. The existence anchor requires only one element of the array to match, so a single container with limits would satisfy the rule for the whole pod and every other container could ship with none. `^()` belongs to existence claims — the pod must contain a container that does X — never to a rule that must hold for all of them.
  • Why does an unanchored initContainers key reject pods that have none?
    Because unanchored keys in a pattern are mandatory, and that applies to the array key itself, not only to fields inside the elements. A pod with no `initContainers` fails on the missing key. Writing `=(initContainers)` makes the list optional while still requiring every element that does exist to satisfy the sub-pattern.
  • How would you require a sizeLimit on every emptyDir volume?
    Nest two equality anchors: `=(volumes)` because a pod may have none, and inside each element `=(emptyDir)` because most volumes are some other type, with `sizeLimit: "?*"` unanchored underneath as the actual assertion. Volumes that are not `emptyDir` are skipped by the inner anchor; every `emptyDir` that exists must declare a size.

saying these in an interview costs you the question

  • Assumes one matching element satisfies a list pattern
  • Uses the existence anchor where every element must comply
  • Forgets initContainers is a separate array
  • Leaves an optional list unanchored and blocks normal pods
  • Treats a list pattern as a selector or filter

context