Skip to main content
Kubernetes includes built-in types like Deployments, Services, and ConfigMaps, but platform teams often need higher-level, domain-specific abstractions—such as Database or TenantEnvironment—to simplify user workflows. CustomResourceDefinitions (CRDs) let you extend the Kubernetes API with these custom types and enforce server-side validation using openAPIV3Schema. This guide walks through creating a namespaced Database CRD that:
  • Serves a single API version: v1.
  • Requires spec.engine and spec.size.
  • Validates engine and size using enums.
  • Allows optional version and storage fields.
Only one version entry in spec.versions may have storage: true. This version is used as the persisted storage version in etcd.

CRD structure — key fields and meaning

Below are the important CRD fields you will set. Use the links for more details: Notes:
  • The string <plural>.<group> is used as an example for CRD names; when writing docs, wrap such placeholders in backticks: <plural>.<group>.
  • Use openAPIV3Schema inside spec.versions[*] to perform server-side validation for fields, types, required keys, and enums.

Example CRD YAML: Database

Below is a complete CRD definition for a namespaced Database custom resource. Save this as database-crd.yaml and apply it to your cluster.
Schema highlights:
  • The spec object is validated using openAPIV3Schema.
  • engine and size are required and constrained to specific enum values.
  • version and storage are optional string fields.

Apply the CRD

Create the database-crd.yaml file (shown above), then apply it:
Expected output:
Verify the API resource is available:
Example output:
Once the CRD is present, you can create Database custom resources. The API server will validate them according to the schema.

Creating Custom Resources (CRs) — examples and validation behaviour

The examples below show common validation errors and a final valid CR. Save each YAML to the noted filename and apply with kubectl apply -f <file>.
  1. Missing metadata.name (invalid)
cr-invalid-name.yaml:
Apply:
Error:
Explanation: metadata.name is required for any Kubernetes resource.
  1. Unsupported engine (invalid)
cr-invalid-engine.yaml:
Apply:
Error:
Explanation: spec.engine must match one of the allowed enum values from the CRD (postgresql, mysql, mariadb).
  1. Missing required size (invalid)
cr-missing-size.yaml:
Apply:
Error:
Explanation: spec.size is required by the CRD schema.
  1. Valid CR (successful)
cr-valid.yaml:
Apply:
Expected output:
Verify the resource exists (using short name):
Example output:
Inspect the resource:
Example (trimmed) output:
This confirms that:
  • The API server enforces schema validation.
  • The custom resource behaves like native Kubernetes resources with kubectl get, kubectl describe, etc.

Troubleshooting tips

If you don’t see your CRD listed after applying, check the API server logs and kubectl get crd output. Also ensure metadata.name in the CRD follows the required <plural>.<group> naming convention and that spec.versions[*].storage is set on only one version.
Common checks:
  • kubectl get crd databases.platform.example.com -o yaml — inspect what was created.
  • kubectl api-resources | grep platform — confirm the API resource is registered.
  • Ensure your cluster version supports apiextensions.k8s.io/v1 (Kubernetes 1.16+).

Summary and best practices

  • CRDs extend the Kubernetes API to introduce domain-specific object types for your platform.
  • Use openAPIV3Schema inside spec.versions to enable server-side validation (required fields, enums, types).
  • Ensure metadata.name of the CRD follows the <plural>.<group> pattern.
  • Only one spec.versions[*] entry may have storage: true; it determines the persisted version stored in etcd.
  • After creating a CRD, kubectl and the API server will validate and accept/reject CRs according to the schema you define.
Further reading:

Watch Video

Practice Lab