Creating a CRD

When you create a new CustomResourceDefinition (CRD), the Kubernetes API Server creates a new RESTful resource path for each version you specify. The CRD can be either namespaced or cluster-scoped, as specified in the CRD’s scope field. As with existing built-in objects, deleting a namespace deletes all custom objects in that namespace. CustomResourceDefinitions themselves are non-namespaced and are available to all namespaces.

For example, if you save the following CustomResourceDefinition to resourcedefinition.yaml:

cat <<EoF > ~/environment/resourcedefinition.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # name must match the spec fields below, and be in the form: <plural>.<group>
  name: crontabs.stable.example.com
spec:
  # group name to use for REST API: /apis/<group>/<version>
  group: stable.example.com
  # list of versions supported by this CustomResourceDefinition
  versions:
    - name: v1
      # Each version can be enabled/disabled by Served flag.
      served: true
      # One and only one version must be marked as the storage version.
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                cronSpec:
                  type: string
                image:
                  type: string
                replicas:
                  type: integer
  # either Namespaced or Cluster
  scope: Namespaced
  names:
    # plural name to be used in the URL: /apis/<group>/<version>/<plural>
    plural: crontabs
    # singular name to be used as an alias on the CLI and for display
    singular: crontab
    # kind is normally the CamelCased singular type. Your resource manifests use this.
    kind: CronTab
    # shortNames allow shorter string to match your resource on the CLI
    shortNames:
    - ct
EoF

And create it:

kubectl apply -f ~/environment/resourcedefinition.yaml

It might take a few seconds for the endpoint to be created. You can also watch the Established condition of your CustomResourceDefinition to be true or watch the discovery information of the API server for your resource to show up.

Now, let’s check the recently created CRD.

kubectl get crd crontabs.stable.example.com

The result will be something like this:

NAME                          CREATED AT
crontabs.stable.example.com   2022-07-15T12:09:41Z

Now, let’s see the Custom Resource in detail:

kubectl describe crd crontabs.stable.example.com

The output:

Name:         crontabs.stable.example.com
Namespace:    
Labels:       <none>
Annotations:  <none>
API Version:  apiextensions.k8s.io/v1
Kind:         CustomResourceDefinition
Metadata:
  Creation Timestamp:  2022-07-15T12:09:41Z
  Generation:          1
  Managed Fields:
    API Version:  apiextensions.k8s.io/v1
    Fields Type:  FieldsV1
    Manager:      kube-apiserver
    Operation:    Update
    Time:         2022-07-15T12:09:41Z
    API Version:  apiextensions.k8s.io/v1
    Manager:         kubectl-client-side-apply
    Operation:       Update
    Time:            2022-07-15T12:09:41Z
  Resource Version:  821325
  UID:               c2184050-1a8d-4945-9bd2-722d14d9d0fa
Spec:
  Conversion:
    Strategy:  None
  Group:       stable.example.com
  Names:
    Kind:       CronTab
    List Kind:  CronTabList
    Plural:     crontabs
    Short Names:
      ct
    Singular:  crontab
  Scope:       Namespaced
  Versions:
    Name:  v1
    Schema:
      openAPIV3Schema:
        Properties:
          Spec:
            Properties:
              Cron Spec:
                Type:  string
              Image:
                Type:  string
              Replicas:
                Type:  integer
            Type:      object
        Type:          object
    Served:            true
    Storage:           true
Status:
  Accepted Names:
    Kind:       CronTab
    List Kind:  CronTabList
    Plural:     crontabs
    Short Names:
      ct
    Singular:  crontab
  Conditions:
    Last Transition Time:  2022-07-15T12:09:41Z
    Message:               no conflicts found
    Reason:                NoConflicts
    Status:                True
    Type:                  NamesAccepted
    Last Transition Time:  2022-07-15T12:09:41Z
    Message:               the initial names have been accepted
    Reason:                InitialNamesAccepted
    Status:                True
    Type:                  Established
  Stored Versions:
    v1
Events:  <none>

Or we can check the resource directly from the Kubernetes API. First, we start the proxy in one tab of the Cloud9 environment:

kubectl proxy --port=8080 --address='0.0.0.0' --disable-filter=true

And in another tab we check the existance of the Custom Resource

curl -i 127.0.0.1:8080/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/crontabs.stable.example.com

The response being something like this:

HTTP/1.1 200 OK
Audit-Id: 20146bde-910d-4c82-ab01-609225e4d262
Cache-Control: no-cache, private
Content-Length: 3650
Content-Type: application/json
Date: Fri, 15 Jul 2022 12:47:07 GMT
Warning: 299 - "apiextensions.k8s.io/v1beta1 CustomResourceDefinition is deprecated in v1.16+, unavailable in v1.22+; use apiextensions.k8s.io/v1 CustomResourceDefinition"
X-Kubernetes-Pf-Flowschema-Uid: 16b0834b-6ac6-4b7e-8b43-505208a6efc8
X-Kubernetes-Pf-Prioritylevel-Uid: f3c0805c-6d2b-493b-a958-724b2adeb80b

{
  "kind": "CustomResourceDefinition",
  "apiVersion": "apiextensions.k8s.io/v1beta1",
  "metadata": {
    "name": "crontabs.stable.example.com",
    "uid": "c2184050-1a8d-4945-9bd2-722d14d9d0fa",
    "resourceVersion": "821325",
    "generation": 1,
    "creationTimestamp": "2022-07-15T12:09:41Z",
    "annotations": {
      "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apiextensions.k8s.io/v1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"annotations\":{},\"name\":\"crontabs.stable.example.com\"},\"spec\":{\"group\":\"stable.example.com\",\"names\":{\"kind\":\"CronTab\",\"plural\":\"crontabs\",\"shortNames\":[\"ct\"],\"singular\":\"crontab\"},\"scope\":\"Namespaced\",\"versions\":[{\"name\":\"v1\",\"schema\":{\"openAPIV3Schema\":{\"properties\":{\"spec\":{\"properties\":{\"cronSpec\":{\"type\":\"string\"},\"image\":{\"type\":\"string\"},\"replicas\":{\"type\":\"integer\"}},\"type\":\"object\"}},\"type\":\"object\"}},\"served\":true,\"storage\":true}]}}\n"
    },
    "managedFields": [
      {
        "manager": "kube-apiserver",
        "operation": "Update",
        "apiVersion": "apiextensions.k8s.io/v1",
        "time": "2022-07-15T12:09:41Z",
        "fieldsType": "FieldsV1",
        "fieldsV1": {"f:status":{"f:acceptedNames":{"f:kind":{},"f:listKind":{},"f:plural":{},"f:shortNames":{},"f:singular":{}},"f:conditions":{"k:{\"type\":\"Established\"}":{".":{},"f:lastTransitionTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}},"k:{\"type\":\"NamesAccepted\"}":{".":{},"f:lastTransitionTime":{},"f:message":{},"f:reason":{},"f:status":{},"f:type":{}}}}}
      },
      {
        "manager": "kubectl-client-side-apply",
        "operation": "Update",
        "apiVersion": "apiextensions.k8s.io/v1",
        "time": "2022-07-15T12:09:41Z",
        "fieldsType": "FieldsV1",
        "fieldsV1": {"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}}},"f:spec":{"f:conversion":{".":{},"f:strategy":{}},"f:group":{},"f:names":{"f:kind":{},"f:listKind":{},"f:plural":{},"f:shortNames":{},"f:singular":{}},"f:scope":{},"f:versions":{}}}
      }
    ]
  },
  "spec": {
    "group": "stable.example.com",
    "version": "v1",
    "names": {
      "plural": "crontabs",
      "singular": "crontab",
      "shortNames": [
        "ct"
      ],
      "kind": "CronTab",
      "listKind": "CronTabList"
    },
    "scope": "Namespaced",
    "validation": {
      "openAPIV3Schema": {
        "type": "object",
        "properties": {
          "spec": {
  "type": "object",
  "properties": {
    "cronSpec": {
  "type": "string"
},
    "image": {
  "type": "string"
},
    "replicas": {
  "type": "integer"
}
  }
}
        }
      }
    },
    "versions": [
      {
        "name": "v1",
        "served": true,
        "storage": true
      }
    ],
    "conversion": {
      "strategy": "None"
    },
    "preserveUnknownFields": false
  },
  "status": {
    "conditions": [
      {
        "type": "NamesAccepted",
        "status": "True",
        "lastTransitionTime": "2022-07-15T12:09:41Z",
        "reason": "NoConflicts",
        "message": "no conflicts found"
      },
      {
        "type": "Established",
        "status": "True",
        "lastTransitionTime": "2022-07-15T12:09:41Z",
        "reason": "InitialNamesAccepted",
        "message": "the initial names have been accepted"
      }
    ],
    "acceptedNames": {
      "plural": "crontabs",
      "singular": "crontab",
      "shortNames": [
        "ct"
      ],
      "kind": "CronTab",
      "listKind": "CronTabList"
    },
    "storedVersions": [
      "v1"
    ]
  }
}