<?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>CRD Schema Archives - Linuxcent</title>
	<atom:link href="https://linuxcent.com/tag/crd-schema/feed/" rel="self" type="application/rss+xml" />
	<link>https://linuxcent.com/tag/crd-schema/</link>
	<description>Infrastructure security, from the kernel up.</description>
	<lastBuildDate>Sat, 09 May 2026 18:40:31 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.3</generator>

<image>
	<url>https://linuxcent.com/wp-content/uploads/2026/04/favicon-512x512-1-150x150.png</url>
	<title>CRD Schema Archives - Linuxcent</title>
	<link>https://linuxcent.com/tag/crd-schema/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">211632295</site>	<item>
		<title>Kubernetes CRD Schema Explained: Versions, Validation, and Status Subresource</title>
		<link>https://linuxcent.com/kubernetes-crd-schema-explained/</link>
					<comments>https://linuxcent.com/kubernetes-crd-schema-explained/#respond</comments>
		
		<dc:creator><![CDATA[Vamshi Krishna Santhapuri]]></dc:creator>
		<pubDate>Sat, 25 Apr 2026 18:15:21 +0000</pubDate>
				<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[CRD]]></category>
		<category><![CDATA[CRD Schema]]></category>
		<category><![CDATA[OpenAPI]]></category>
		<category><![CDATA[Platform Engineering]]></category>
		<category><![CDATA[spec.versions]]></category>
		<category><![CDATA[Status Subresource]]></category>
		<guid isPermaLink="false">https://linuxcent.com/kubernetes-crd-schema-explained/</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>Understand Kubernetes CRD anatomy: spec.versions, OpenAPI schema validation, scope, and the status subresource — what each YAML field does and why it matters in production.</p>
<p>The post <a href="https://linuxcent.com/kubernetes-crd-schema-explained/">Kubernetes CRD Schema Explained: Versions, Validation, and Status Subresource</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 3</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> · </em><em><a href="/kubernetes-crd-schema-explained/">CRD Anatomy</a></em><em> · <a href="/write-kubernetes-crd-yaml-walkthrough/">Write Your First CRD</a> · <a href="/kubernetes-crd-cel-validation/">CEL Validation</a> · <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>The Kubernetes CRD schema is defined in <code class="" data-line="">spec.versions[].schema.openAPIV3Schema</code> — the API server uses it to validate every custom resource create and update before storing in etcd<br />
  <em>(OpenAPI v3 schema = a JSON Schema dialect that describes the structure, types, and constraints of your resource&#8217;s fields)</em></li>
<li><code class="" data-line="">spec.versions</code> is a list — CRDs can serve multiple API versions simultaneously; exactly one version must have <code class="" data-line="">storage: true</code></li>
<li><code class="" data-line="">scope: Namespaced</code> vs <code class="" data-line="">scope: Cluster</code> controls whether custom resources live inside a namespace or at cluster level (like <code class="" data-line="">PersistentVolume</code> vs <code class="" data-line="">PersistentVolumeClaim</code>)</li>
<li><code class="" data-line="">spec.names</code> defines the plural, singular, kind, and optional shortNames used in <code class="" data-line="">kubectl</code> and RBAC</li>
<li>The <strong>status subresource</strong> (<code class="" data-line="">subresources.status: {}</code>) separates user writes (<code class="" data-line="">spec</code>) from controller writes (<code class="" data-line="">status</code>) — enabling optimistic concurrency and <code class="" data-line="">kubectl status</code> support</li>
<li>The <strong>scale subresource</strong> (<code class="" data-line="">subresources.scale</code>) makes your custom resource compatible with <code class="" data-line="">kubectl scale</code> and the HorizontalPodAutoscaler</li>
</ul>
<hr />
<h2 id="the-big-picture">The Big Picture</h2>
<pre><code class="" data-line="">  ANATOMY OF A CUSTOMRESOURCEDEFINITION

  apiVersion: apiextensions.k8s.io/v1
  kind: CustomResourceDefinition
  metadata:
    name: {plural}.{group}        ← MUST be exactly this format
  spec:
    group: {group}                ← API group (e.g. storage.example.com)
    scope: Namespaced | Cluster   ← where instances live
    names:                        ← how kubectl refers to this resource
      plural: backuppolicies
      singular: backuppolicy
      kind: BackupPolicy
      shortNames: [bp]
    versions:                     ← can be a list; one must have storage: true
      - name: v1alpha1
        served: true              ← API server responds to this version
        storage: true             ← etcd stores objects in this version
        schema:
          openAPIV3Schema:        ← validation schema for ALL objects of this type
            type: object
            properties:
              spec: {...}
              status: {...}
        subresources:
          status: {}              ← enables separate status write path
          scale:                  ← enables kubectl scale + HPA
            specReplicasPath: .spec.replicas
            statusReplicasPath: .status.replicas
        additionalPrinterColumns: ← extra columns in kubectl get output
          - name: Schedule
            type: string
            jsonPath: .spec.schedule
</code></pre>
<p>Understanding the Kubernetes CRD schema is the prerequisite for writing a CRD that behaves correctly in production — validation catches bad data at the API boundary, the status subresource prevents controller race conditions, and scope determines your entire RBAC and multi-tenancy model.</p>
<hr />
<h2 id="specgroup-and-metadataname"><code class="" data-line="">spec.group</code> and <code class="" data-line="">metadata.name</code></h2>
<p>The group is a reverse-DNS identifier for your API. Convention:</p>
<pre><code class="" data-line="">storage.example.com     ← domain you control + functional area
monitoring.myteam.io
databases.platform.company.com
</code></pre>
<p>The CRD&#8217;s <code class="" data-line="">metadata.name</code> must be exactly <code class="" data-line="">{plural}.{group}</code>:</p>
<pre><code class="" data-line="">metadata:
  name: backuppolicies.storage.example.com
spec:
  group: storage.example.com
  names:
    plural: backuppolicies
</code></pre>
<p>If these do not match, the API server rejects the CRD with a validation error. This is the most common first-timer mistake.</p>
<hr />
<h2 id="specscope-namespaced-vs-cluster"><code class="" data-line="">spec.scope</code>: Namespaced vs Cluster</h2>
<pre><code class="" data-line="">  SCOPE DETERMINES WHERE INSTANCES LIVE

  Namespaced (scope: Namespaced)       Cluster (scope: Cluster)
  ─────────────────────────────         ──────────────────────────
  kubectl get backuppolicies -n prod    kubectl get clusterbackuppolicies
  kubectl get backuppolicies -A         (no -n flag, no namespace)

  Analogous to: Pod, Deployment,        Analogous to: PersistentVolume,
                ConfigMap                             ClusterRole, Node
</code></pre>
<p><strong>Namespaced</strong>: Use when instances are per-tenant or per-application. Users with namespace-scoped RBAC can manage their own instances without cluster-admin. Most CRDs should be namespaced.</p>
<p><strong>Cluster-scoped</strong>: Use when instances represent cluster-wide configuration — a <code class="" data-line="">ClusterIssuer</code> (cert-manager), <code class="" data-line="">ClusterSecretStore</code> (ESO), a <code class="" data-line="">StorageClass</code>-like concept. Requires cluster-level RBAC to create/modify.</p>
<p>You cannot change scope after a CRD is created without deleting and recreating it (which deletes all instances). Choose carefully.</p>
<hr />
<h2 id="specversions-serving-multiple-api-versions"><code class="" data-line="">spec.versions</code>: Serving Multiple API Versions</h2>
<pre><code class="" data-line="">spec:
  versions:
    - name: v1alpha1
      served: true
      storage: false       # not stored; converted on read
      schema:
        openAPIV3Schema: {...}
    - name: v1beta1
      served: true
      storage: false
      schema:
        openAPIV3Schema: {...}
    - name: v1
      served: true
      storage: true        # etcd stores in this version
      schema:
        openAPIV3Schema: {...}
</code></pre>
<p>Rules:<br />
&#8211; <code class="" data-line="">served: true</code> means the API server accepts requests at this version<br />
&#8211; <code class="" data-line="">served: false</code> means the API server returns <code class="" data-line="">404</code> for that version — use to deprecate<br />
&#8211; Exactly one version must have <code class="" data-line="">storage: true</code> — this is what gets written to etcd<br />
&#8211; When a client requests a non-storage version, the API server converts on the fly (or calls your conversion webhook — see <a href="/kubernetes-crd-versioning-conversion-webhook/">EP08</a>)</p>
<p>Early in development, start with <code class="" data-line="">v1alpha1 storage: true</code>. Promote to <code class="" data-line="">v1</code> when the schema is stable. EP08 covers how to do this without losing data.</p>
<hr />
<h2 id="specnames-what-kubectl-sees"><code class="" data-line="">spec.names</code>: What kubectl Sees</h2>
<pre><code class="" data-line="">spec:
  names:
    plural:     backuppolicies     # kubectl get backuppolicies
    singular:   backuppolicy       # kubectl get backuppolicy (also works)
    kind:       BackupPolicy       # used in YAML apiVersion/kind
    listKind:   BackupPolicyList   # optional; auto-derived if omitted
    shortNames:                    # kubectl get bp
      - bp
    categories:                    # kubectl get all includes this type
      - all
</code></pre>
<p><code class="" data-line="">categories</code> is worth noting: if you add <code class="" data-line="">all</code> to categories, your custom resources appear when someone runs <code class="" data-line="">kubectl get all -n mynamespace</code>. Most CRDs deliberately do not add this — it clutters <code class="" data-line="">get all</code> output. Only add it if your resource is a primary operational concern.</p>
<hr />
<h2 id="schemaopenapiv3schema-validation"><code class="" data-line="">schema.openAPIV3Schema</code>: Validation</h2>
<p>The schema is where you define field types, required fields, constraints, and descriptions. The API server validates every create and update against this schema before writing to etcd.</p>
<pre><code class="" data-line="">schema:
  openAPIV3Schema:
    type: object
    required: [&quot;spec&quot;]
    properties:
      spec:
        type: object
        required: [&quot;schedule&quot;, &quot;retentionDays&quot;]
        properties:
          schedule:
            type: string
            description: &quot;Cron expression for backup schedule&quot;
            pattern: &#039;^(\*|[0-9,\-\/]+)\s+(\*|[0-9,\-\/]+)\s+(\*|[0-9,\-\/]+)\s+(\*|[0-9,\-\/]+)\s+(\*|[0-9,\-\/]+)$&#039;
          retentionDays:
            type: integer
            minimum: 1
            maximum: 365
          storageClass:
            type: string
            default: &quot;standard&quot;        # default value (Kubernetes 1.17+)
          targets:
            type: array
            maxItems: 10
            items:
              type: object
              required: [&quot;name&quot;]
              properties:
                name:
                  type: string
                namespace:
                  type: string
                  default: &quot;default&quot;
      status:
        type: object
        x-kubernetes-preserve-unknown-fields: true   # controllers write arbitrary status
</code></pre>
<h3 id="field-types-available">Field types available</h3>
<table>
<thead>
<tr>
<th>Type</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="" data-line="">string</code></td>
<td>Text values; supports <code class="" data-line="">format</code>, <code class="" data-line="">pattern</code>, <code class="" data-line="">enum</code>, <code class="" data-line="">minLength</code>, <code class="" data-line="">maxLength</code></td>
</tr>
<tr>
<td><code class="" data-line="">integer</code></td>
<td>Whole numbers; supports <code class="" data-line="">minimum</code>, <code class="" data-line="">maximum</code></td>
</tr>
<tr>
<td><code class="" data-line="">number</code></td>
<td>Floating point</td>
</tr>
<tr>
<td><code class="" data-line="">boolean</code></td>
<td><code class="" data-line="">true</code>/<code class="" data-line="">false</code></td>
</tr>
<tr>
<td><code class="" data-line="">object</code></td>
<td>Nested structure; use <code class="" data-line="">properties</code> to define fields</td>
</tr>
<tr>
<td><code class="" data-line="">array</code></td>
<td>List; use <code class="" data-line="">items</code> to define element schema; supports <code class="" data-line="">minItems</code>, <code class="" data-line="">maxItems</code></td>
</tr>
</tbody>
</table>
<h3 id="x-kubernetes-preserve-unknown-fields-true"><code class="" data-line="">x-kubernetes-preserve-unknown-fields: true</code></h3>
<p>This tells the API server not to prune fields it does not know about. Use it on <code class="" data-line="">status</code> (controllers write whatever they need) and on fields that are intentionally free-form (like a <code class="" data-line="">config</code> field that accepts arbitrary YAML). Avoid it on <code class="" data-line="">spec</code> — it bypasses validation.</p>
<h3 id="validation-behavior-in-practice">Validation behavior in practice</h3>
<pre><code class="" data-line=""># This will fail with a clear error:
kubectl apply -f - &lt;&lt;EOF
apiVersion: storage.example.com/v1alpha1
kind: BackupPolicy
metadata:
  name: bad
  namespace: default
spec:
  schedule: &quot;not-a-cron&quot;    # fails pattern validation
  retentionDays: 500         # fails maximum: 365
EOF
</code></pre>
<pre><code class="" data-line="">The BackupPolicy &quot;bad&quot; is invalid:
  spec.schedule: Invalid value: &quot;not-a-cron&quot;: spec.schedule in body should match
    &#039;^(\*|[0-9,\-\/]+)\s+...&#039;
  spec.retentionDays: Invalid value: 500: spec.retentionDays in body should be
    less than or equal to 365
</code></pre>
<p>Schema validation catches configuration mistakes at apply time, not at runtime inside a pod. This is one of the core advantages of expressing domain configuration as CRDs rather than ConfigMaps.</p>
<hr />
<h2 id="additionalprintercolumns-what-kubectl-get-shows"><code class="" data-line="">additionalPrinterColumns</code>: What kubectl get Shows</h2>
<p>By default, <code class="" data-line="">kubectl get backuppolicies</code> shows only <code class="" data-line="">NAME</code> and <code class="" data-line="">AGE</code>. You can add columns:</p>
<pre><code class="" data-line="">additionalPrinterColumns:
  - name: Schedule
    type: string
    jsonPath: .spec.schedule
    description: Cron schedule for backups
  - name: Retention
    type: integer
    jsonPath: .spec.retentionDays
    priority: 1          # 0 = always shown; 1 = only with -o wide
  - name: Ready
    type: string
    jsonPath: .status.conditions[?(@.type==&#039;Ready&#039;)].status
  - name: Age
    type: date
    jsonPath: .metadata.creationTimestamp
</code></pre>
<p>Result:</p>
<pre><code class="" data-line="">NAME        SCHEDULE      READY   AGE
nightly     0 2 * * *     True    3d
weekly      0 0 * * 0     False   7d
</code></pre>
<p>Good printer columns turn <code class="" data-line="">kubectl get</code> into a useful operational dashboard. Include <code class="" data-line="">Ready</code> (from status conditions) so operators can immediately see which custom resources are healthy without running <code class="" data-line="">kubectl describe</code>.</p>
<hr />
<h2 id="the-status-subresource">The Status Subresource</h2>
<pre><code class="" data-line="">subresources:
  status: {}
</code></pre>
<p>Without the status subresource, <code class="" data-line="">spec</code> and <code class="" data-line="">status</code> are part of the same object. Any user with <code class="" data-line="">update</code> permission on the CRD can modify both. Controllers write status through the same path as users write spec.</p>
<p>With the status subresource enabled:<br />
&#8211; <code class="" data-line="">kubectl apply</code> / <code class="" data-line="">kubectl patch</code> only update <code class="" data-line="">spec</code> — the <code class="" data-line="">status</code> block is stripped<br />
&#8211; Controllers use the <code class="" data-line="">/status</code> subresource endpoint to write status<br />
&#8211; RBAC can grant <code class="" data-line="">update</code> on <code class="" data-line="">backuppolicies</code> (spec) independently from <code class="" data-line="">update</code> on <code class="" data-line="">backuppolicies/status</code></p>
<pre><code class="" data-line="">  WITHOUT status subresource:         WITH status subresource:
  ─────────────────────────            ──────────────────────────
  PUT /backuppolicies/nightly          PUT /backuppolicies/nightly
  → updates spec AND status            → updates spec only

                                       PUT /backuppolicies/nightly/status
                                       → updates status only (controller path)
</code></pre>
<p>Always enable the status subresource on production CRDs. The split between spec and status is fundamental to the Kubernetes API contract. Without it, a controller updating status can accidentally overwrite spec changes made by a user at the same time.</p>
<hr />
<h2 id="the-scale-subresource">The Scale Subresource</h2>
<pre><code class="" data-line="">subresources:
  scale:
    specReplicasPath: .spec.replicas
    statusReplicasPath: .status.replicas
    labelSelectorPath: .status.labelSelector
</code></pre>
<p>This makes your custom resource compatible with:</p>
<pre><code class="" data-line="">kubectl scale backuppolicy nightly --replicas=3
</code></pre>
<p>And with <code class="" data-line="">HorizontalPodAutoscaler</code> targeting your custom resource. If your CRD manages something replica-based (workers, shards, connections), enabling the scale subresource lets it plug into the standard Kubernetes autoscaling ecosystem without extra plumbing.</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>Forgetting <code class="" data-line="">x-kubernetes-preserve-unknown-fields: true</code> on status.</strong> If you validate the status field with a strict schema but do not add this, the API server will prune any status fields the controller writes that are not in the schema. The controller&#8217;s status updates will silently lose fields. Either define the full status schema or use <code class="" data-line="">x-kubernetes-preserve-unknown-fields: true</code>.</p>
<p><strong>Using <code class="" data-line="">scope: Cluster</code> for resources that should be namespaced.</strong> Once a CRD is created as cluster-scoped, you cannot make it namespaced without deleting and recreating it. Plan scope before deploying to production.</p>
<p><strong>Not enabling the status subresource.</strong> Without it, controllers writing status can race with users updating spec. It also means <code class="" data-line="">kubectl patch --subresource=status</code> does not work and some tooling behaves unexpectedly. Enable it from the start.</p>
<p><strong>Loose schema with no <code class="" data-line="">required</code> fields.</strong> An <code class="" data-line="">openAPIV3Schema</code> with no <code class="" data-line="">required</code> constraint accepts objects with empty <code class="" data-line="">spec</code>. This usually means your controller gets called with a resource that is missing mandatory configuration. Define required fields and validate them at the API boundary, not inside the controller.</p>
<hr />
<h2 id="quick-reference">Quick Reference</h2>
<pre><code class="" data-line=""># Inspect the full schema of a CRD
kubectl get crd backuppolicies.storage.example.com -o yaml | \
  yq &#039;.spec.versions[0].schema&#039;

# Check what subresources are enabled
kubectl get crd certificates.cert-manager.io -o jsonpath=\
  &#039;{.spec.versions[0].subresources}&#039;

# See all served versions for a CRD
kubectl get crd prometheuses.monitoring.coreos.com \
  -o jsonpath=&#039;{.spec.versions[*].name}&#039;

# Check which version is the storage version
kubectl get crd certificates.cert-manager.io \
  -o jsonpath=&#039;{.spec.versions[?(@.storage==true)].name}&#039;

# Describe the printer columns for a CRD
kubectl get crd scaledobjects.keda.sh \
  -o jsonpath=&#039;{.spec.versions[0].additionalPrinterColumns}&#039;
</code></pre>
<hr />
<h2 id="key-takeaways">Key Takeaways</h2>
<ul>
<li><code class="" data-line="">spec.versions</code> allows serving and storing multiple API versions; only one version has <code class="" data-line="">storage: true</code></li>
<li><code class="" data-line="">scope</code> (Namespaced vs Cluster) cannot be changed after creation — choose deliberately</li>
<li><code class="" data-line="">openAPIV3Schema</code> validates every CR at the API boundary, before etcd storage</li>
<li>The status subresource separates the user write path (spec) from the controller write path (status) — always enable it</li>
<li><code class="" data-line="">additionalPrinterColumns</code> makes <code class="" data-line="">kubectl get</code> operationally useful; include a <code class="" data-line="">Ready</code> column from status conditions</li>
</ul>
<hr />
<h2 id="whats-next">What&#8217;s Next</h2>
<p><a href="/write-kubernetes-crd-yaml-walkthrough/">EP04: Write Your First Kubernetes CRD</a> puts the anatomy into practice — a complete hands-on walkthrough building a <code class="" data-line="">BackupPolicy</code> CRD from scratch, applying it to a cluster, creating instances, and verifying validation, RBAC, and status behavior.</p>
<p>Get EP04 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-schema-explained%2F&amp;linkname=Kubernetes%20CRD%20Schema%20Explained%3A%20Versions%2C%20Validation%2C%20and%20Status%20Subresource" 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-schema-explained%2F&amp;linkname=Kubernetes%20CRD%20Schema%20Explained%3A%20Versions%2C%20Validation%2C%20and%20Status%20Subresource" 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-schema-explained%2F&amp;linkname=Kubernetes%20CRD%20Schema%20Explained%3A%20Versions%2C%20Validation%2C%20and%20Status%20Subresource" 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-schema-explained%2F&amp;linkname=Kubernetes%20CRD%20Schema%20Explained%3A%20Versions%2C%20Validation%2C%20and%20Status%20Subresource" 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-schema-explained%2F&amp;linkname=Kubernetes%20CRD%20Schema%20Explained%3A%20Versions%2C%20Validation%2C%20and%20Status%20Subresource" 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-schema-explained%2F&amp;linkname=Kubernetes%20CRD%20Schema%20Explained%3A%20Versions%2C%20Validation%2C%20and%20Status%20Subresource" 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-schema-explained%2F&amp;linkname=Kubernetes%20CRD%20Schema%20Explained%3A%20Versions%2C%20Validation%2C%20and%20Status%20Subresource" 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-schema-explained%2F&#038;title=Kubernetes%20CRD%20Schema%20Explained%3A%20Versions%2C%20Validation%2C%20and%20Status%20Subresource" data-a2a-url="https://linuxcent.com/kubernetes-crd-schema-explained/" data-a2a-title="Kubernetes CRD Schema Explained: Versions, Validation, and Status Subresource"></a></p><p>The post <a href="https://linuxcent.com/kubernetes-crd-schema-explained/">Kubernetes CRD Schema Explained: Versions, Validation, and Status Subresource</a> appeared first on <a href="https://linuxcent.com">Linuxcent</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://linuxcent.com/kubernetes-crd-schema-explained/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1681</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-08-12 20:15:03 by W3 Total Cache
-->