Skip to main content

Kubernetes 1.36's Declarative Validation Went GA — But Not for the CRDs Your Agent Actually Writes To

8 min readDora NodaDora Noda
Share
On this page

If you've seen the Kubernetes 1.36 release notes, you've probably seen the claim: "Declarative Validation graduates to GA." It's tempting to read that as "Kubernetes now lets you write CEL rules straight into a CRD's schema" — and if you're building a platform where an AI agent generates its own Cluster API or App manifests, that sounds like exactly the feature you'd want to write about. It's also not what shipped.

The Declarative Validation that went GA in Kubernetes v1.36 replaces handwritten Go validation code for built-in API types — Pod, Deployment, Service — with +k8s: tags in their types.go source and a new code generator, validation-gen. It has nothing to do with CustomResourceDefinitions. The CEL-based mechanism that does govern CRDs, x-kubernetes-validations, has been generally available since Kubernetes 1.29 — over two years and seven releases before 1.36 shipped. If you're operating a self-hosted PaaS's control-plane CRDs, or an agent is generating manifests against them, the tool you need already exists and has for a while. Here's the real timeline, what's actually GA where, and a worked example — using a genuine CEL rule from Cluster API's own Cluster CRD — of what synchronous, schema-level rejection actually buys an agent over a validating webhook.

Three Features, Three GA Dates, One Common Name​

The confusion is understandable because Kubernetes has shipped three related-sounding, differently-scoped validation features over three years, and "declarative" describes all of them:

FeatureApplies toGA versionWhat it replaces
CRD validation rules (x-kubernetes-validations, CEL)CustomResourceDefinitions1.29 (Dec 2023)Validating admission webhooks for CRDs
CRD validation ratcheting (CRDValidationRatcheting)CustomResourceDefinitions1.33 (2025)Hard failures on unchanged-but-invalid fields during CRD schema tightening
Declarative Validation (+k8s: tags, validation-gen)Built-in API types only1.36 (April 2026)~18,000 lines of handwritten Go validation functions for core resources

Only the first two rows apply to a CRD you author yourself — an App, a Task, a MachineDeployment. The third row, the one making headlines this release, is Kubernetes replacing its own internal validation code with a tag-based, code-generated approach. The Kubernetes blog post announcing it is explicit that the scope is "Kubernetes native types," and doesn't mention CEL or x-kubernetes-validations anywhere — they're separate systems solving separate problems.

The reason this matters beyond pedantry: if you build your roadmap around "wait for 1.36 to add schema validation to my CRDs," you'll wait forever, because that was never the plan. The capability has been sitting there, GA and stable, since 2023.

A Real CEL Rule From Cluster API's Own CRD​

Since bex is built on Cluster API, it's worth pointing at something that already runs in production rather than a toy example. Cluster API's Cluster CRD schema includes this, attached to a condition's type field:

yaml
type: string
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
x-kubernetes-validations:
  - rule: "!(self in ['Ready','Available','HealthCheckSucceeded','OwnerRemediated','ExternallyRemediated'])"
    message: 'type must not be one of: Ready, Available, HealthCheckSucceeded, OwnerRemediated, ExternallyRemediated'

That's a real constraint from cluster.x-k8s.io_clusters.yaml: a condition can't reuse one of Cluster API's own reserved condition-type names. It's a genuinely awkward thing to enforce with a regex alone (a pattern can shape a string, it can't exclude a specific enumerated set of values), which is exactly the class of rule CEL exists for — anything past "match this format" needs either a webhook or an inline expression.

What the Webhook Version of This Looks Like — and Costs​

Before x-kubernetes-validations, the only way to enforce a rule like "not one of these five reserved strings" was a validating admission webhook: a separate Deployment, its own TLS certificate, a ValidatingWebhookConfiguration registering it against the resource, and Go code implementing the check. When an agent (or a human) submits a Cluster manifest with a reserved condition type, here's the two paths that check can take:

Webhook path. The API server serializes the request, calls out over HTTP to the webhook Service, waits for a response (subject to the configured timeoutSeconds, commonly 10s), and only then returns an error. If the webhook pod is mid-rollout, OOM-killed, or the Service's endpoints are briefly empty — all real, observed failure modes for anything running as a Deployment — the request either hangs until timeout or fails with a generic failurePolicy-driven error that has nothing to do with the actual validation rule. An agent retrying against a webhook timeout has no way to tell "your input was invalid" apart from "the validator was momentarily down."

CEL path. The rule lives in the CRD's OpenAPI schema. The API server evaluates it in-process, synchronously, as part of admission — no network call, no separate pod to be unavailable, no timeout budget. The rejection comes back as a structured Invalid error (HTTP 422) carrying the exact message string from the schema: type must not be one of: Ready, Available, HealthCheckSucceeded, OwnerRemediated, ExternallyRemediated. An agent parsing that response gets the specific field, the specific reason, and enough information to correct the manifest and retry — without a human reading webhook logs to explain why the last attempt hung for ten seconds and then failed anyway.

That gap — a webhook's opaque, network-dependent failure mode versus CEL's synchronous, self-describing one — is the actual, practical reason to convert a CRD's validating webhook to x-kubernetes-validations wherever the logic fits (anything expressible against the object's own fields; cross-resource lookups still need a webhook). It has nothing to do with 1.36. It's been true, and available, since 1.29.

Ratcheting: Why an Agent's Partial Update Shouldn't Get Blocked by Someone Else's Old Mistake​

The other real, CRD-relevant GA milestone is CRDValidationRatcheting, stable since 1.33. It solves a different problem: what happens when you tighten a validation rule on a CRD that already has resources violating the new, stricter version?

Without ratcheting, any update to that resource — even one that touches a completely unrelated field — fails, because the whole object gets re-validated against the current schema. An agent trying to bump a Task's activeDeadlineSeconds gets rejected because some other field, untouched by this update, predates a rule that didn't exist when the object was created. With ratcheting enabled, the API server compares old and new objects field-by-field: if a failing field's value is unchanged by this specific update, the failure is ratcheted (ignored) rather than blocking the whole write. The agent's actual change goes through; the pre-existing violation stays exactly as invalid as it always was, untouched and unblocked.

For an agent submitting frequent, narrow updates against long-lived resources — nudging a rollback strategy, bumping a resource limit, patching a label — this is the difference between "my one-field patch works" and "my one-field patch fails because of a schema migration that has nothing to do with what I'm changing."

What 1.36 Actually Signals, Even Though It Doesn't Touch CRDs Yet​

None of this makes 1.36's Declarative Validation irrelevant to CRD authors — it's a signal worth watching, just not a tool available today. The +k8s: tag system Kubernetes core now uses to generate Go validation from types.go comments is conceptually the same move CRD authors already made with x-kubernetes-validations: push validation into a declarative, discoverable layer instead of imperative code, so the constraint is visible next to the field it governs instead of buried in a separate function. The Kubernetes blog post notes this "unlocks the future ability to publish validation rules via OpenAPI and integrate with ecosystem tools like Kubebuilder" — which is the honest way to read it: a plausible future direction, not a shipped feature. If validation-gen-style tags ever reach Kubebuilder for CRD authors, the payoff would be writing one +k8s:minimum=0 comment on a Go struct field and getting both compiled-in Go validation and the equivalent CEL/OpenAPI schema rule generated together, instead of hand-maintaining CEL expressions in YAML separately from the Go types they constrain. That's not available yet. Don't build a roadmap item around it landing on any particular release.

The Concrete Move for a Cluster-API-Based Control Plane​

The action item isn't "wait for 1.36" — it's auditing whatever validating webhooks already exist on your own CRDs today. For each one, ask whether the rule only references fields on the object being validated (a reserved-value check, a format constraint, a cross-field comparison like "max ≥ min"). If so, it's a x-kubernetes-validations candidate, and converting it removes a webhook Service, its certificate rotation, and its failure mode from the request path — replacing an async, network-dependent check with a synchronous one that fails fast and explains itself. Rules that need to look up a different resource, call an external system, or check global cluster state still need a webhook; CEL only sees the object in front of it. Pair that audit with turning on CRDValidationRatcheting (default-enabled since 1.33) before tightening any existing rule, so schema evolution doesn't retroactively break resources an agent isn't even touching.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own, with the same API surface a human deploys through open to an agent as a first-class operator. Star the repo on GitHub or deploy your first app today.

Related articles

Give your agents a chain backend

Autonomous agents hit RPC endpoints very differently than people do. See what bex router handles on their behalf.

Read the agents guide