CRDs

Extending the API with Custom Resource Definitions

A CustomResourceDefinition (CRD) adds a resource type: kubectl 5,150 , RBAC and watches treat it like a built-in kind 14,561 , and the API server enforces its OpenAPI v3 schema on every write. BookNest's promotions are an example:

k8s/promotion-crd.yaml: a Promotion kind for BookNestYAML
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata: { name: promotions.booknest.example.com }
spec:
  group: booknest.example.com
  scope: Namespaced
  names: { kind: Promotion, plural: promotions, singular: promotion, shortNames: [promo] }
  versions:
  - name: v1
    served: true
    storage: true
    additionalPrinterColumns:
    - { name: Book, type: integer, jsonPath: .spec.bookId }
    - { name: Percent, type: integer, jsonPath: .spec.percentOff }
    - { name: Until, type: string, jsonPath: .spec.until }
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            required: [bookId, percentOff, until]
            properties:
              bookId: { type: integer, minimum: 1 }
              percentOff: { type: integer, minimum: 5, maximum: 50 }
              until: { type: string, format: date }
Registering the CRD, then creating a valid and an invalid PromotionShell
kubectl apply -f k8s/promotion-crd.yaml >/dev/null
P='{"apiVersion": "booknest.example.com/v1", "kind": "Promotion", "metadata": {"name": "%s"},'
P+=' "spec": {"bookId": %s, "percentOff": %s, "until": "%s"}}'
printf "$P" autumn-cooking 3 20 2026-10-31 | kubectl apply -f -
kubectl get promo
printf "$P" too-generous 0 90 soon | kubectl create -f - 2>&1 | sed 's/ in body//'
Output
promotion.booknest.example.com/autumn-cooking created
NAME             BOOK   PERCENT   UNTIL
autumn-cooking   3      20        2026-10-31
The Promotion "too-generous" is invalid:
* spec.bookId: Invalid value: 0: spec.bookId should be greater than or equal to 1
* spec.percentOff: Invalid value: 90: spec.percentOff should be less than or equal to 50
* spec.until: Invalid value: "soon": spec.until must be of type date: "soon"

The API server rejected all three mistakes before storing anything; rules that span fields go in x-kubernetes-validations as CEL expressions. Still, a Promotion is inert data: nothing changes a price until a controller watches these objects and acts on them.