In a Gatekeeper ConstraintTemplate, what belongs in the rule body versus in its parameters?
answer
- logic versus values
- one template, many instances
- typed schema catches it at apply time
- fork only when the check changes
- readable Constraint is the test
basics
~20 sPut the invariant in the template body and the values that vary between teams in parameters, declared with types in the openAPIV3Schema. Fork into a second template only when the logic differs, not when a number does.
solid answer
~50 sThe dividing line is logic versus values. Take a rule that every container must declare CPU and memory requests and limits and stay under a ceiling: the walk over containers, the check that the fields exist, and the comparison are the invariant and belong in the template body. The ceilings themselves, and any opt-outs such as an exempt image list, vary per team and belong in `spec.parameters`, described in the template's `openAPIV3Schema` with real types and `required` fields. That schema is what makes the generated Constraint CRD reject a bad value at `kubectl apply` time rather than letting a rule quietly match nothing later. Write a second template when the *logic* differs — a different check, not a different number — because every fork costs another Rego body, another generated CRD and another test suite to keep in step. Over-parameterising is the opposite failure: a schema so general that nobody can read the Constraint and tell what is enforced.
go deeper
Know that the template holds the rule and the shape of its parameters, and that the numbers live on each Constraint. Recognise that copying a template to change a value is the wrong instinct.
Explain that the template's openAPIV3Schema becomes the generated Constraint CRD's schema, so a badly typed or missing parameter is rejected at apply time rather than handled inside the rule.
Draw the line deliberately: invariant in the body, varying values in typed parameters, a second template only when the check itself differs. Be able to price the cost of forks and of over-parameterising.
Own it as a delegation decision. What you expose as a parameter is what teams may decide without you, and consolidating a forked template library has to be sequenced so enforcement never lapses in between.
## The design decision this leaf is really about A ConstraintTemplate has two halves that can each hold the same information: the rule body, and the parameter schema. Where you draw the line decides whether your policy library is one template with six small Constraints or six templates that started identical and no longer are. ### The default answer **Invariant in the body. Values in parameters.** Take the rule that every container must declare CPU and memory requests and limits, and that the limits must not exceed a ceiling. Decompose it: - Iterating over `containers` and `initContainers`; asserting the fields are present; comparing the declared limit against a ceiling; producing a message that names the offending container and field — **this is the invariant.** It is the same for every team, it is what gets reviewed and tested, and it belongs in the template body. - The CPU ceiling, the memory ceiling, whether initContainers are in scope, an exempt image prefix — **these vary**, and they belong in `spec.parameters`. Done that way, one template backs a Constraint for production namespaces with tight ceilings and another for a batch namespace with looser ones. The Rego is written, reviewed and tested once. ### The parameter schema is a guardrail, not paperwork `spec.crd.spec.validation.openAPIV3Schema` describes what `spec.parameters` may contain. Gatekeeper turns it into the generated Constraint CRD's schema, so the API server validates every Constraint against it at apply time. Declare `maxCpu` as an integer, mark the fields that have no sensible absence as `required`, and a Constraint carrying a string or missing a value is rejected by `kubectl apply` with a validation error — in front of the person editing it, in the moment. The alternative is a schema that declares nothing much. Then a wrong or missing value is not caught by the API server; it is handled, or not handled, by the rule at admission time, where the usual outcome is that the rule finds nothing to compare against and produces no violation. A rule that silently stops enforcing is strictly worse than one that fails loudly, and the schema is where you buy the loud failure. This is also why the rule body should say explicitly what a missing parameter means rather than leaving it to chance. ### When to fork instead A new template is right when the **logic** differs, not the values: - "Also require an owner label" is a different check — different template (or a deliberate extension of this one). - "Compare against a per-namespace quota fetched from elsewhere" is different logic — different template. - "Use 2 CPU instead of 4" is a value — same template, second Constraint. The cost of forking is concrete and it compounds: each fork is another Rego body, another generated CRD in the cluster's API, another set of offline tests, another thing to patch when the container walk turns out to have missed `ephemeralContainers`. Six near-copies means six chances to fix five of them. ### The opposite failure: parameterising everything It is possible to push so much into parameters that the template becomes a small configuration language — a schema with field names, operators and comparison directions in it, and a rule body that mostly interprets its own parameters. Two things go wrong. The Constraint stops being readable: someone reviewing it sees a block of settings and cannot tell what is enforced. And review moves to the wrong place: the meaning of the rule now lives in objects that individual teams edit, rather than in a template that goes through the review path you built for policy. A workable test: **could a reviewer read the Constraint alone and say, in one sentence, what it enforces?** If understanding it requires reading the template's Rego, you have parameterised too far. ### The ownership angle The split is also an ownership boundary. Templates are code: they belong to whoever owns policy, live in a repository, get reviewed and tested offline before they land. Constraints are configuration: small objects with a match block and a few values, safe for a namespace owner to propose. Choosing what to parameterise is choosing what teams may decide for themselves. Making the ceiling a parameter says teams pick their own ceiling; keeping it in the body says they do not. Answer the question that way in an interview and you are answering the real one. ### How this shows up as a question Usually as a scenario: "you now have nine templates that are the same rule with different numbers — what happened, and what do you do?" What happened is that the first variation arrived before there was a parameter for it and copying was the fast path. The fix is one template with the varying values in a typed schema, the nine Constraints regenerated against it, and the old templates removed — remembering that deleting a ConstraintTemplate takes every Constraint of that kind with it, so the new rule goes in before the old one comes out.
- What does declaring types and required fields in the template's openAPIV3Schema actually buy you?It becomes the generated Constraint CRD's schema, so the API server validates every Constraint against it. A wrong type or a missing required value is rejected at apply time, in front of whoever is editing the object, instead of surfacing later as a rule that quietly compares against nothing and stops enforcing.
- You inherit nine ConstraintTemplates that are the same rule with different numbers. What do you do?Collapse them into one template with the varying values as typed parameters, and re-express the nine as Constraints against it. Land the new template and its Constraints before removing the old ones, because deleting a ConstraintTemplate garbage-collects every Constraint of that kind and would leave a window with no enforcement at all.
- Is there such a thing as too many parameters on a template?Yes. Once the schema carries field names, operators and comparison directions, the template is a small configuration language and the Constraint no longer says what it enforces. The usable test is whether a reviewer can read the Constraint alone and state the rule in a sentence; if not, that logic belongs back in the body.
- Why is the body-versus-parameters split also an ownership decision?Because templates are code owned by whoever owns policy — reviewed, tested, versioned — while Constraints are small configuration objects a namespace owner can reasonably propose. Making a value a parameter says teams may choose it themselves; keeping it in the body says they may not. That is a delegation decision dressed as a schema question.
saying these in an interview costs you the question
- Writes a new template for every threshold
- Leaves the parameter schema empty and validates in the rule
- Parameterises the rule into a configuration language
- Assumes a missing parameter safely means no violation
- Deletes old templates before the replacement is in force