In a Kubernetes ValidatingAdmissionPolicy, what does messageExpression give you that a static message cannot?
answer
- one is fixed, one is computed
- CEL over the same inputs as the rule
- evaluated only when the rule fails
- must return a single-line string
- broken text falls back, never flips the decision
basics
~20 smessageExpression is a CEL expression evaluated against the request object, so the failure text can name the specific object, field or value that broke the rule. A static message is one fixed sentence that can only describe the rule.
solid answer
~50 sA validation in a ValidatingAdmissionPolicy can carry two kinds of failure text. `message` is a fixed single-line string: it states what the rule requires, but not which of a Pod's fifteen containers broke it. `messageExpression` is CEL evaluated against the same inputs as the validation itself - `object`, `oldObject`, `request`, `params` - and must return a string, so you can interpolate the object's name, the offending field or the value you rejected: something like `'PodDisruptionBudget ' + object.metadata.name + ' must be submitted as policy/v1'`. It is evaluated only when the validation fails. If it errors, returns empty or whitespace, or contains a line break, the API server logs that and falls back to the static `message`; if `message` is unset too, you get a generated default quoting the failed CEL expression back at the developer - the message you never want to ship.
go deeper
Recall that a policy's failure text is either a fixed sentence someone wrote or a string the engine builds from the object being admitted, and that the second kind can name the specific thing that was wrong.
Explain that messageExpression is CEL evaluated against the same inputs as the validation, only when that validation fails, that it must yield a single-line string, and what the fallback chain does when it does not.
Demonstrate that you write the message for the person reading it at 5pm: name the object, the field and the replacement, and never ship the generated default that quotes raw CEL back at a developer.
Treat refusal and warning text as a product surface with an owner and a review standard, the way you would treat an error page. The cost of bad text is paid in support load and in workarounds, not in policy evaluations.
## The message is the product A policy decision reaches a developer as one string. Everything the engine knows - which expression fired, which field was wrong, what the acceptable value is - is thrown away unless it is put into that string. A ValidatingAdmissionPolicy gives you two fields for building it, and they differ in one important way: whether the text can see the object. ### message: a fixed sentence `message` is a static string attached to a validation. It must be a single line - line breaks are not allowed in it - and it is the same for every request the validation ever refuses. That is fine for a rule with one possible failure: 'CronJob must be submitted as batch/v1' says everything there is to say. It stops being fine the moment the rule quantifies over a collection. A validation whose expression walks every container, every volume or every rule in a list knows precisely which element failed while it is evaluating, and a static message throws that knowledge away. The developer gets 'one or more containers violate the policy' and then has to find the one. ### messageExpression: text computed from the request `messageExpression` is a CEL expression that must evaluate to a string, evaluated with the same variables available to the validation expression - typically `object` (the incoming object), `oldObject` (its previous state on an update), `request` (the request metadata), and `params` if the policy binds a parameter resource. So the failure text can be built out of the thing being refused: `messageExpression: "'PodDisruptionBudget ' + object.metadata.name + ' must be submitted as policy/v1'"` That single change turns a generic refusal into one a developer can act on without opening the policy. Two mechanical points matter: - **It runs only on failure.** The API server evaluates the message expression when the validation expression has already failed, so the cost is paid only by rejected requests. - **It is expression-only.** CEL here has no I/O: it cannot look up another object, call a service, or read cluster state. The message can only be built from what was already handed to the policy. If the useful context is not in the request, no message expression can conjure it. ### The fallback chain, which you should be able to recite If `messageExpression` produces a runtime error, an empty string, a whitespace-only string, or a string containing line breaks, the API server logs the problem and produces the message **as if messageExpression were unset**. It falls back to the static `message`. If that is also unset, the API server generates a default that quotes the failed expression itself. The consequence is worth stating plainly: **a broken message expression never changes the decision.** The request is still refused; the developer just gets worse text. This is a nice property - text generation cannot accidentally admit something - but it also means a bug here is silent from the outside, and the only place it surfaces is the API server log. The other consequence is the failure mode you see most often in real clusters: a policy shipped with neither field usefully set, so developers are handed a raw CEL expression as their explanation. It is technically accurate and practically useless, and it is the single fastest way to make a guardrail feel hostile. ### Where the same text goes when the rule only warns The message a validation produces is not tied to refusal. If the binding lists `Warn` rather than `Deny` in its `validationActions`, that same computed string is what gets emitted as the warning the client sees. So writing a good message pays off twice: once for the person you are blocking, once for the person you are only telling. The same string is also what lands in the audit annotation when the binding uses `Audit`. ### Contrast with a webhook A validating admission webhook has no equivalent field, and does not need one: it builds the status message in code, with the whole object in hand, and can format it however it likes. `messageExpression` exists because a declarative policy has nowhere else to compute text - the expression language is the only code there is. ### Writing the text itself Aim for one line that answers three questions: which object, which field or value, and what to do instead. Name the rule so a developer knows what to search for and whom to ask. Resist the temptation to format a bulleted list into the string - line breaks disqualify the message and drop you back to the fallback, which is worse than the plain sentence you started with.
- What happens if messageExpression itself throws a runtime error?The API server logs the error and produces the failure message as if the field were unset: it falls back to the static `message`, or to a generated default naming the failed expression if there is none. The admit-or-deny decision is unaffected - a broken message expression degrades the text, never the verdict.
- Why can messageExpression not include the current state of some other object in the cluster?CEL in a ValidatingAdmissionPolicy is expression-only with no I/O. It sees only what was handed to the policy for this request - the object, its previous version on an update, request metadata and any bound parameter resource. Anything outside that has to arrive as a parameter, or it cannot appear in the message.
- Does a good message matter if the rule is bound with Warn instead of Deny?More, if anything. A warning admits the object, so the text is the only thing that will ever prompt a fix; there is no failed command forcing the developer to look. The computed string is emitted as the warning body, so the same messageExpression serves both bindings.
- How would you review a colleague's policy for message quality?Read the string a developer would actually receive, not the expression. Check that it names the object, the offending field or value and the required alternative, that it identifies the rule, that it is one line, and that no path through the policy can fall back to the generated default that echoes raw CEL.
saying these in an interview costs you the question
- Thinks a failing messageExpression changes the admit or deny decision
- Puts line breaks in the message to format a list
- Leaves both fields unset and ships raw CEL to developers
- Believes the message expression can look up other cluster objects
- Assumes messageExpression is evaluated on every matching request