<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Validation Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/validation/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/validation/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Sat, 09 May 2026 18:40:35 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.4</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>Validation Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/validation/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<item>
		<title>Kubernetes CRD CEL Validation: Replace Admission Webhooks for Schema Rules</title>
		<link>https://linuxcent.com/kubernetes-crd-cel-validation/</link>
					<comments>https://linuxcent.com/kubernetes-crd-cel-validation/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Sat, 25 Apr 2026 18:15:55 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Admission Webhooks]]></category>
		<category><![CDATA[CEL]]></category>
		<category><![CDATA[CRD]]></category>
		<category><![CDATA[Kubernetes 1.25]]></category>
		<category><![CDATA[Validation]]></category>
		<category><![CDATA[x-kubernetes-validations]]></category>
		<guid isPermaLink="false">https://linuxcent.com/kubernetes-crd-cel-validation/</guid>

					<description><![CDATA[<p><span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span>Use CEL expressions in Kubernetes CRDs to validate fields without an admission webhook — cross-field rules, regex checks, and defaulting with x-kubernetes-validations.</p>
<p>The post <a href="https://linuxcent.com/kubernetes-crd-cel-validation/">Kubernetes CRD CEL Validation: Replace Admission Webhooks for Schema Rules</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></description>
										<content:encoded><![CDATA[<span class="span-reading-time rt-reading-time" style="display: block;"><span class="rt-label rt-prefix">Reading Time: </span> <span class="rt-time"> 6</span> <span class="rt-label rt-postfix">minutes</span></span><style>
pre{position:relative;background:#1e1e1e;color:#d4d4d4;
    padding:16px 16px 16px 20px;border-radius:6px;overflow-x:auto;
    font-family:'JetBrains Mono','Fira Code','Cascadia Code',Consolas,'Courier New',monospace;
    font-size:.88em;line-height:1.6;border-left:4px solid #555}
code{background:#f4f4f4;padding:2px 5px;border-radius:3px;font-size:.9em}
pre code{background:transparent;padding:0;color:inherit}
pre[data-lang="bash"],pre[data-lang="sh"],
pre[data-lang="shell"],pre[data-lang="zsh"]{border-left-color:#4ec9b0}
pre[data-lang="yaml"],pre[data-lang="json"],
pre[data-lang="toml"],pre[data-lang="xml"]{border-left-color:#569cd6}
pre[data-lang="python"],pre[data-lang="go"],pre[data-lang="rust"],
pre[data-lang="java"],pre[data-lang="c"],pre[data-lang="cpp"]{border-left-color:#c586c0}
pre[data-lang="text"],pre[data-lang="output"],
pre[data-lang="console"]{border-left-color:#888}
.lc-copy-btn{position:absolute;top:8px;right:8px;background:#2d2d2d;color:#ccc;
    border:1px solid #444;border-radius:4px;padding:3px 9px;font-size:.75em;
    font-family:system-ui,sans-serif;cursor:pointer;opacity:0;
    transition:opacity .15s,background .15s;line-height:1.6}
pre:hover .lc-copy-btn{opacity:1}
.lc-copy-btn:hover{background:#3a3a3a;color:#fff}
.lc-copy-btn.copied{color:#4ec9b0;border-color:#4ec9b0}
.lc-lang-badge{position:absolute;top:8px;left:20px;font-family:system-ui,sans-serif;
    font-size:.7em;color:#666;text-transform:uppercase;letter-spacing:.04em;
    line-height:1;pointer-events:none;opacity:0;transition:opacity .15s}
pre:hover .lc-lang-badge{opacity:1}
table{border-collapse:collapse;width:100%;margin:16px 0}
th,td{border:1px solid #ddd;padding:10px 14px;text-align:left}
th{background:#f0f0f0;font-weight:600}
tr:nth-child(even){background:#fafafa}
</style>
<p><script>
(function(){
  if(window.__lcCodeEnhanced)return;
  window.__lcCodeEnhanced=true;
  function enhance(){
    document.querySelectorAll('pre').forEach(function(pre){
      var code=pre.querySelector('code');
      var lang='';
      if(code){var m=(code.className||'').match(/language-(\S+)/);if(m)lang=m[1].toLowerCase();}
      if(lang)pre.setAttribute('data-lang',lang);
      if(lang){var badge=document.createElement('span');badge.className='lc-lang-badge';badge.textContent=lang;pre.insertBefore(badge,pre.firstChild);}
      var btn=document.createElement('button');
      btn.className='lc-copy-btn';btn.textContent='Copy';btn.setAttribute('aria-label','Copy code to clipboard');
      pre.appendChild(btn);
      btn.addEventListener('click',function(){
        var text=code?code.innerText:pre.innerText;
        if(navigator.clipboard&&window.isSecureContext){
          navigator.clipboard.writeText(text).then(function(){ok(btn);}).catch(function(){fb(text,btn);});
        }else{fb(text,btn);}
      });
    });
  }
  function ok(btn){btn.textContent='Copied!';btn.classList.add('copied');setTimeout(function(){btn.textContent='Copy';btn.classList.remove('copied');},2000);}
  function fb(text,btn){
    try{var ta=document.createElement('textarea');ta.value=text;ta.style.cssText='position:fixed;left:-9999px;top:-9999px;opacity:0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);ok(btn);}
    catch(e){btn.textContent='✗ Failed';setTimeout(function(){btn.textContent='Copy';},2000);}
  }
  if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',enhance);}else{enhance();}
})();
</script></p>
<p><em>Kubernetes CRDs &amp; Operators: Extending the API, Episode 5</em><br />
<em><a href="/what-is-kubernetes-crd/">What Is a CRD?</a> · <a href="/kubernetes-custom-resources-examples/">CRDs You Already Use</a> · <a href="/kubernetes-crd-schema-explained/">CRD Anatomy</a> · <a href="/write-kubernetes-crd-yaml-walkthrough/">Write Your First CRD</a> · </em><em><a href="/kubernetes-crd-cel-validation/">CEL Validation</a></em><em> · <a href="/kubernetes-controller-reconcile-loop/">Controller Loop</a> · <a href="/build-kubernetes-operator-controller-runtime/">Build an Operator</a> · <a href="/kubernetes-crd-versioning-conversion-webhook/">CRD Versioning</a> · <a href="/kubernetes-admission-webhooks-explained/">Admission Webhooks</a> · <a href="/kubernetes-crd-production-finalizers-conditions-rbac/">CRDs in Production</a></em></p>
<hr />
<h2 id="tldr">TL;DR</h2>
<ul>
<li>Kubernetes CRD CEL validation (<code class="" data-line="">x-kubernetes-validations</code>) lets you write arbitrary validation rules in the CRD schema — no admission webhook needed<br />
  <em>(CEL = Common Expression Language, a lightweight expression language built into Kubernetes since 1.25 stable; replaces most reasons you would write a validating admission webhook)</em></li>
<li>CEL rules are evaluated by the API server at admit time — the same place as OpenAPI schema validation, before etcd</li>
<li><code class="" data-line="">self</code> refers to the current object&#8217;s field; <code class="" data-line="">oldSelf</code> refers to the previous value (for update rules)</li>
<li>Cross-field validation: &#8220;if <code class="" data-line="">storageClass</code> is <code class="" data-line="">premium</code>, <code class="" data-line="">retentionDays</code> must be ≤ 90&#8243; — impossible with plain OpenAPI schema, trivial with CEL</li>
<li>Immutable fields: <code class="" data-line="">oldSelf == self</code> with <code class="" data-line="">reason: Immutable</code> prevents users from changing values after creation</li>
<li>CEL rules run in ~microseconds inside the API server; no external service, no TLS, no latency budget to manage</li>
</ul>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<pre><code class="" data-line="">  CEL VALIDATION: WHERE IT FITS IN THE ADMISSION CHAIN

  kubectl apply -f backup.yaml
         │
         ▼
  API Server admission chain
  ┌────────────────────────────────────────────────────┐
  │                                                    │
  │  1. Mutating admission webhooks (modify object)    │
  │  2. Schema validation (OpenAPI types, required,    │
  │     minimum/maximum, pattern)                      │
  │  3. CEL validation (x-kubernetes-validations)  ←  │ THIS EPISODE
  │  4. Validating admission webhooks (external)       │
  │                                                    │
  └────────────────────────────────────────────────────┘
         │
         ▼ (passes all checks)
  etcd storage
</code></pre>
<p>Kubernetes CRD CEL validation sits between schema validation and external webhooks. For most validation requirements, CEL eliminates the need for a webhook entirely — which means no separate deployment to maintain, no TLS certificates to rotate, no availability dependency between your CRD and a webhook server.</p>
<hr />
<h2 id="why-cel-replaces-most-admission-webhooks">Why CEL Replaces Most Admission Webhooks</h2>
<p>Before CEL (stable in Kubernetes 1.25), the only way to express &#8220;if field A has value X, field B must be present&#8221; was an admission webhook — a separate HTTP server that Kubernetes called synchronously during every API request.</p>
<p>Webhooks work, but they have real costs:</p>
<ul>
<li><strong>Availability dependency</strong>: if the webhook is down, creates/updates for that resource type fail</li>
<li><strong>TLS management</strong>: webhook endpoints require valid TLS certs that must be rotated</li>
<li><strong>Deployment overhead</strong>: another Deployment, Service, and certificate to manage</li>
<li><strong>Latency</strong>: every API operation waits for an HTTP round-trip</li>
</ul>
<p>CEL runs inside the API server process. There is no network call, no certificate, no separate deployment. Rules are compiled once and evaluated in microseconds.</p>
<p>The trade-off: CEL cannot make network calls or access state outside the object being validated. For rules that need to look up other resources (e.g., &#8220;does this referenced Secret exist?&#8221;), you still need a webhook or a controller that validates via status conditions.</p>
<hr />
<h2 id="cel-syntax-basics">CEL Syntax Basics</h2>
<p>CEL expressions are small programs. In Kubernetes CRD validation, the key variables are:</p>
<table>
<thead>
<tr>
<th>Variable</th>
<th>Meaning</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="" data-line="">self</code></td>
<td>The current field value (or root object at top level)</td>
</tr>
<tr>
<td><code class="" data-line="">oldSelf</code></td>
<td>The previous value of the field (only available on update; nil on create)</td>
</tr>
</tbody>
</table>
<p>CEL returns <code class="" data-line="">true</code> (validation passes) or <code class="" data-line="">false</code> (validation fails, API returns error).</p>
<p>Common patterns:</p>
<pre><code class="" data-line=""># String not empty
self.size() &gt; 0

# String matches format
self.matches(&#039;^[a-z][a-z0-9-]*$&#039;)

# Integer in range
self &gt;= 1 &amp;&amp; self &lt;= 365

# Field present (for optional fields)
has(self.fieldName)

# Conditional: if A then B
!has(self.premium) || self.retentionDays &lt;= 90

# List not empty
self.size() &gt; 0

# All items in list satisfy condition
self.all(item, item.namespace.size() &gt; 0)

# Cross-field: access sibling field via parent
self.retentionDays &gt;= self.minRetentionDays
</code></pre>
<hr />
<h2 id="adding-cel-rules-to-the-backuppolicy-crd">Adding CEL Rules to the BackupPolicy CRD</h2>
<p>Start from the CRD built in <a href="/write-kubernetes-crd-yaml-walkthrough/">EP04</a>. Add <code class="" data-line="">x-kubernetes-validations</code> at the levels where you need them.</p>
<h3 id="rule-1-cron-expression-validation">Rule 1: Cron expression validation</h3>
<p>The OpenAPI <code class="" data-line="">pattern</code> field can validate basic structure, but a proper cron regex is unwieldy. CEL is cleaner:</p>
<pre><code class="" data-line="">spec:
  type: object
  required: [&quot;schedule&quot;, &quot;retentionDays&quot;]
  x-kubernetes-validations:
    - rule: &quot;self.schedule.matches(&#039;^(\\\\*|[0-9,\\\\-\\\\/]+) (\\\\*|[0-9,\\\\-\\\\/]+) (\\\\*|[0-9,\\\\-\\\\/]+) (\\\\*|[0-9,\\\\-\\\\/]+) (\\\\*|[0-9,\\\\-\\\\/]+)$&#039;)&quot;
      message: &quot;schedule must be a valid 5-field cron expression&quot;
</code></pre>
<h3 id="rule-2-cross-field-validation">Rule 2: Cross-field validation</h3>
<pre><code class="" data-line="">spec:
  type: object
  x-kubernetes-validations:
    - rule: &quot;!(self.storageClass == &#039;premium&#039;) || self.retentionDays &lt;= 90&quot;
      message: &quot;premium storage class supports at most 90 days retention&quot;
    - rule: &quot;!self.suspended || !has(self.pausedBy) || self.pausedBy.size() &gt; 0&quot;
      message: &quot;when suspended is true, pausedBy must be non-empty if provided&quot;
</code></pre>
<h3 id="rule-3-immutable-fields">Rule 3: Immutable fields</h3>
<p>Once a <code class="" data-line="">BackupPolicy</code> is created, the <code class="" data-line="">schedule</code> field should not be changeable without deleting and recreating:</p>
<pre><code class="" data-line="">schedule:
  type: string
  x-kubernetes-validations:
    - rule: &quot;self == oldSelf&quot;
      message: &quot;schedule is immutable after creation&quot;
      reason: Immutable
</code></pre>
<blockquote>
<p><strong><code class="" data-line="">reason</code> field:</strong> Available reasons are <code class="" data-line="">FieldValueInvalid</code> (default), <code class="" data-line="">FieldValueForbidden</code>, <code class="" data-line="">FieldValueRequired</code>, and <code class="" data-line="">Immutable</code>. Using <code class="" data-line="">Immutable</code> returns HTTP 422 with a clear message that the field cannot be changed.</p>
</blockquote>
<h3 id="rule-4-conditional-required-field">Rule 4: Conditional required field</h3>
<p>If <code class="" data-line="">storageClass</code> is <code class="" data-line="">encrypted</code>, then <code class="" data-line="">encryptionKeyRef</code> must be present:</p>
<pre><code class="" data-line="">spec:
  type: object
  x-kubernetes-validations:
    - rule: &quot;self.storageClass != &#039;encrypted&#039; || has(self.encryptionKeyRef)&quot;
      message: &quot;encryptionKeyRef is required when storageClass is &#039;encrypted&#039;&quot;
</code></pre>
<h3 id="rule-5-list-element-validation">Rule 5: List element validation</h3>
<p>Ensure each target namespace is a valid RFC 1123 DNS label:</p>
<pre><code class="" data-line="">targets:
  type: array
  items:
    type: object
    x-kubernetes-validations:
      - rule: &quot;self.namespace.matches(&#039;^[a-z0-9]([-a-z0-9]*[a-z0-9])?$&#039;)&quot;
        message: &quot;namespace must be a valid DNS label&quot;
</code></pre>
<hr />
<h2 id="the-complete-updated-crd-with-cel">The Complete Updated CRD with CEL</h2>
<pre><code class="" data-line="">apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: backuppolicies.storage.example.com
spec:
  group: storage.example.com
  scope: Namespaced
  names:
    plural:     backuppolicies
    singular:   backuppolicy
    kind:       BackupPolicy
    shortNames: [bp]
  versions:
    - name: v1alpha1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          required: [&quot;spec&quot;]
          properties:
            spec:
              type: object
              required: [&quot;schedule&quot;, &quot;retentionDays&quot;]
              x-kubernetes-validations:
                - rule: &quot;!(self.storageClass == &#039;premium&#039;) || self.retentionDays &lt;= 90&quot;
                  message: &quot;premium storage class supports at most 90 days retention&quot;
              properties:
                schedule:
                  type: string
                  x-kubernetes-validations:
                    - rule: &quot;self == oldSelf&quot;
                      message: &quot;schedule is immutable after creation&quot;
                      reason: Immutable
                retentionDays:
                  type: integer
                  minimum: 1
                  maximum: 365
                storageClass:
                  type: string
                  default: &quot;standard&quot;
                  enum: [&quot;standard&quot;, &quot;premium&quot;, &quot;encrypted&quot;, &quot;archive&quot;]
                encryptionKeyRef:
                  type: string
                targets:
                  type: array
                  maxItems: 20
                  items:
                    type: object
                    required: [&quot;namespace&quot;]
                    x-kubernetes-validations:
                      - rule: &quot;self.namespace.matches(&#039;^[a-z0-9]([-a-z0-9]*[a-z0-9])?$&#039;)&quot;
                        message: &quot;namespace must be a valid DNS label&quot;
                    properties:
                      namespace:
                        type: string
                      includeSecrets:
                        type: boolean
                        default: false
                suspended:
                  type: boolean
                  default: false
            status:
              type: object
              x-kubernetes-preserve-unknown-fields: true
      subresources:
        status: {}
      additionalPrinterColumns:
        - name: Schedule
          type: string
          jsonPath: .spec.schedule
        - name: Retention
          type: integer
          jsonPath: .spec.retentionDays
        - name: Ready
          type: string
          jsonPath: .status.conditions[?(@.type==&#039;Ready&#039;)].status
        - name: Age
          type: date
          jsonPath: .metadata.creationTimestamp
</code></pre>
<hr />
<h2 id="testing-cel-rules">Testing CEL Rules</h2>
<p>Apply the updated CRD:</p>
<pre><code class="" data-line="">kubectl apply -f backuppolicies-crd-cel.yaml
</code></pre>
<p>Test cross-field validation:</p>
<pre><code class="" data-line="">kubectl apply -f - &lt;&lt;&#039;EOF&#039;
apiVersion: storage.example.com/v1alpha1
kind: BackupPolicy
metadata:
  name: premium-long
  namespace: demo
spec:
  schedule: &quot;0 2 * * *&quot;
  retentionDays: 180          # violates: premium + &gt; 90 days
  storageClass: premium
EOF
</code></pre>
<pre><code class="" data-line="">The BackupPolicy &quot;premium-long&quot; is invalid:
  spec: Invalid value: &quot;object&quot;:
    premium storage class supports at most 90 days retention
</code></pre>
<p>Test immutability:</p>
<pre><code class="" data-line=""># Create valid policy
kubectl apply -f - &lt;&lt;&#039;EOF&#039;
apiVersion: storage.example.com/v1alpha1
kind: BackupPolicy
metadata:
  name: immutable-test
  namespace: demo
spec:
  schedule: &quot;0 2 * * *&quot;
  retentionDays: 30
EOF

# Try to change the schedule
kubectl patch bp immutable-test -n demo \
  --type=merge -p &#039;{&quot;spec&quot;:{&quot;schedule&quot;:&quot;0 3 * * *&quot;}}&#039;
</code></pre>
<pre><code class="" data-line="">The BackupPolicy &quot;immutable-test&quot; is invalid:
  spec.schedule: Invalid value: &quot;0 3 * * *&quot;:
    schedule is immutable after creation
</code></pre>
<p>Test list element validation:</p>
<pre><code class="" data-line="">kubectl apply -f - &lt;&lt;&#039;EOF&#039;
apiVersion: storage.example.com/v1alpha1
kind: BackupPolicy
metadata:
  name: bad-namespace
  namespace: demo
spec:
  schedule: &quot;0 2 * * *&quot;
  retentionDays: 7
  targets:
    - namespace: &quot;UPPERCASE_IS_INVALID&quot;
EOF
</code></pre>
<pre><code class="" data-line="">The BackupPolicy &quot;bad-namespace&quot; is invalid:
  spec.targets[0]: Invalid value: &quot;object&quot;:
    namespace must be a valid DNS label
</code></pre>
<hr />
<h2 id="cel-cost-and-limits">CEL Cost and Limits</h2>
<p>CEL expressions are evaluated at admission time in the API server. Kubernetes imposes cost limits to prevent expressions from consuming excessive CPU:</p>
<ul>
<li>Each expression is assigned a <em>cost</em> based on its operations (string matches, list iteration, etc.)</li>
<li>If the expression cost exceeds the per-validation limit, the API server rejects the CRD itself when you apply it</li>
<li>Complex <code class="" data-line="">all()</code> over large lists is the most common way to hit cost limits</li>
</ul>
<p>If you hit a cost limit error:</p>
<pre><code class="" data-line="">CustomResourceDefinition is invalid: spec.validation.openAPIV3Schema...
  CEL expression cost exceeds budget
</code></pre>
<p>Solutions:<br />
&#8211; Reduce list traversal in CEL rules; enforce list length with <code class="" data-line="">maxItems</code> instead<br />
&#8211; Split one expensive rule into multiple simpler rules<br />
&#8211; Move the expensive validation to a controller (status condition) rather than admission</p>
<hr />
<h2 id="common-mistakes"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Common Mistakes</h2>
<p><strong>Using <code class="" data-line="">oldSelf</code> on create.</strong> On create operations, <code class="" data-line="">oldSelf</code> is nil/unset. A rule like <code class="" data-line="">self == oldSelf</code> for immutability will panic on create unless you guard it: <code class="" data-line="">oldSelf == null || self == oldSelf</code>. In practice, Kubernetes applies immutable rules only on updates (the reason: Immutable annotation helps here), but be explicit in rules that reference <code class="" data-line="">oldSelf</code>.</p>
<p><strong>Forgetting <code class="" data-line="">has()</code> checks for optional fields.</strong> If <code class="" data-line="">encryptionKeyRef</code> is optional (not in <code class="" data-line="">required</code>) and you write a rule like <code class="" data-line="">self.encryptionKeyRef.size() &gt; 0</code>, it will fail with a &#8220;no such key&#8221; error when the field is absent. Always guard optional field access with <code class="" data-line="">has(self.fieldName)</code>.</p>
<p><strong>Overloading CEL for what a controller should do.</strong> CEL validates fields at admission. If your rule needs to verify that a referenced Secret actually exists, CEL cannot do that — it only sees the object being submitted. Use a controller status condition for existence checks, not CEL.</p>
<hr />
<h2 id="quick-reference-common-cel-patterns">Quick Reference: Common CEL Patterns</h2>
<pre><code class="" data-line=""># String not empty
self.size() &gt; 0

# String matches regex
self.matches(&#039;^[a-z][a-z0-9-]{1,62}$&#039;)

# Optional field guard
!has(self.fieldName) || self.fieldName.size() &gt; 0

# Conditional requirement
!(condition) || has(self.requiredWhenConditionIsTrue)

# Immutable field (update only)
self == oldSelf

# All list items satisfy condition
self.all(item, item.namespace.size() &gt; 0)

# At least one list item satisfies condition
self.exists(item, item.type == &#039;primary&#039;)

# Cross-field comparison
self.minReplicas &lt;= self.maxReplicas

# Enum-style check
self.in([&#039;standard&#039;, &#039;premium&#039;, &#039;archive&#039;])
</code></pre>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li><code class="" data-line="">x-kubernetes-validations</code> with CEL rules replaces most validating admission webhooks for CRD-specific logic</li>
<li>CEL runs inside the API server — no external service, no TLS, no separate deployment</li>
<li>Cross-field validation, immutable fields, and conditional requirements are all expressible in CEL</li>
<li>Use <code class="" data-line="">has()</code> guards for optional fields; use <code class="" data-line="">oldSelf</code> carefully (it is nil on create)</li>
<li>CEL has cost limits — avoid unbounded list iteration; use <code class="" data-line="">maxItems</code> to bound lists first</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p><a href="/kubernetes-controller-reconcile-loop/">EP06: The Kubernetes Controller Reconcile Loop</a> explains how a controller watches <code class="" data-line="">BackupPolicy</code> objects and acts on them — the mechanism that makes CRDs useful beyond validated configuration storage. Before writing code in EP07, you need to understand the reconcile loop conceptually.</p>
<p>Get EP06 in your inbox when it publishes → <a href="https://linuxcent.com">subscribe at linuxcent.com</a></p>
<p><a class="a2a_button_mastodon" href="https://www.addtoany.com/add_to/mastodon?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-cel-validation%2F&amp;linkname=Kubernetes%20CRD%20CEL%20Validation%3A%20Replace%20Admission%20Webhooks%20for%20Schema%20Rules" title="Mastodon" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-cel-validation%2F&amp;linkname=Kubernetes%20CRD%20CEL%20Validation%3A%20Replace%20Admission%20Webhooks%20for%20Schema%20Rules" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_whatsapp" href="https://www.addtoany.com/add_to/whatsapp?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-cel-validation%2F&amp;linkname=Kubernetes%20CRD%20CEL%20Validation%3A%20Replace%20Admission%20Webhooks%20for%20Schema%20Rules" title="WhatsApp" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_reddit" href="https://www.addtoany.com/add_to/reddit?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-cel-validation%2F&amp;linkname=Kubernetes%20CRD%20CEL%20Validation%3A%20Replace%20Admission%20Webhooks%20for%20Schema%20Rules" title="Reddit" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_x" href="https://www.addtoany.com/add_to/x?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-cel-validation%2F&amp;linkname=Kubernetes%20CRD%20CEL%20Validation%3A%20Replace%20Admission%20Webhooks%20for%20Schema%20Rules" title="X" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_linkedin" href="https://www.addtoany.com/add_to/linkedin?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-cel-validation%2F&amp;linkname=Kubernetes%20CRD%20CEL%20Validation%3A%20Replace%20Admission%20Webhooks%20for%20Schema%20Rules" title="LinkedIn" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_copy_link" href="https://www.addtoany.com/add_to/copy_link?linkurl=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-cel-validation%2F&amp;linkname=Kubernetes%20CRD%20CEL%20Validation%3A%20Replace%20Admission%20Webhooks%20for%20Schema%20Rules" title="Copy Link" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Flinuxcent.com%2Fkubernetes-crd-cel-validation%2F&#038;title=Kubernetes%20CRD%20CEL%20Validation%3A%20Replace%20Admission%20Webhooks%20for%20Schema%20Rules" data-a2a-url="https://linuxcent.com/kubernetes-crd-cel-validation/" data-a2a-title="Kubernetes CRD CEL Validation: Replace Admission Webhooks for Schema Rules"></a></p><p>The post <a href="https://linuxcent.com/kubernetes-crd-cel-validation/">Kubernetes CRD CEL Validation: Replace Admission Webhooks for Schema Rules</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/kubernetes-crd-cel-validation/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1687</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: linuxcent.com @ 2026-09-01 07:19:41 by W3 Total Cache
-->