Home › CKAD practice tasks › Discover and use a custom resource
CKAD · Application Environment, Configuration and Security

Discover and Use an Operator's Custom Resource

The platform team's database operator adds a custom resource that application teams use to request databases. You have not used it before. Several databases already exist in namespaces `inventory`, `billing` and `analytics`, and namespace `inventory` is migrating from an old database to a new one.

Live lab access

This task description is public. Live practice requires full access unless this is the selected free weekly task. AI Lab Coach is available in single-task labs, including the free weekly task. See this week's free task.

Create an account to continue →

Read this week's free task →

On a phone? Read the task and create your account here, then sign in on a desktop or laptop for the terminal lab. Nothing starts until you choose Start.

The task

The platform team's database operator adds a custom resource that application teams use to request databases. You have not used it before. Several databases already exist in namespaces inventory, billing and analytics, and namespace inventory is migrating from an old database to a new one.

Requirements
1. Find the custom resource that represents a database and its fields.
2. Write every existing MySQL database, from all namespaces, to /root/mysql-databases.txt as namespace/name, one per line.
3. Create database orders in namespace inventory running engine postgres, version 16, with 3 replicas and daily backups enabled at 0 2 * * *.
4. Delete database orders-legacy from inventory. Leave all other databases unchanged.

Original Prepium guide · Written for Kubernetes v1.35 and checked against the official documentation on 6 October 2026. Use a disposable practice cluster. Installing a CustomResourceDefinition changes the cluster-wide API, so you need cluster-admin rights.

Custom resources: discover an unfamiliar API, then query it like any other

A CustomResourceDefinition (CRD) teaches the API server a new resource type: its group, version, names, scope and an OpenAPI schema for its fields. Once installed, the new kind behaves like a built-in one: kubectl get, -A, JSONPath and kubectl explain all work. On the exam you usually meet a CRD someone else installed, so the real skill is finding out what it is called and what fields it has before you touch it.

CRD kites.hobby.example.com ──> API /apis/hobby.example.com/v1/namespaces/*/kites
   names: kite, kites, kt                 kubectl get kites -A
   schema: spec.colour, lengthCm, tail    kubectl explain kite.spec

1. Install a practice CRD

Save this as kite-crd.yaml. The object name must be <plural>.<group>, exactly one version is the storage version, and additionalPrinterColumns adds a COLOUR column to kubectl get.

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: kites.hobby.example.com        # must be <plural>.<group>
spec:
  group: hobby.example.com
  scope: Namespaced
  names:
    plural: kites
    singular: kite
    kind: Kite
    shortNames: ["kt"]
  versions:
  - name: v1
    served: true
    storage: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            required: ["colour"]
            properties:
              colour:
                type: string
                description: Main sail colour.
              lengthCm:
                type: integer
                minimum: 30
                description: Wingspan in centimetres.
              tail:
                type: boolean
                description: Whether a tail ribbon is attached.
    additionalPrinterColumns:
    - name: Colour
      type: string
      jsonPath: .spec.colour
    - name: Age
      type: date
      jsonPath: .metadata.creationTimestamp
kubectl apply -f kite-crd.yaml
kubectl wait --for=condition=Established crd/kites.hobby.example.com --timeout=60s

2. Discover it the way you would on the exam

kubectl get crd | grep -i kite
kubectl api-resources --api-group=hobby.example.com
kubectl explain kite.spec
kubectl explain kite.spec.lengthCm

api-resources reveals the names kubectl accepts (plural, short name, kind) and whether the type is namespaced. kubectl treats those names case-insensitively, so kites, kite and kt are interchangeable. explain reads the schema the API server publishes for structural CRDs, including each field's type and description, so you never have to guess a field name.

3. Create instances and let the schema push back

kubectl create namespace prepium-tutorial
kubectl create namespace prepium-tutorial-2
cat <<'EOF' | kubectl apply -f -
apiVersion: hobby.example.com/v1
kind: Kite
metadata: {name: dawn, namespace: prepium-tutorial}
spec: {colour: red, lengthCm: 120, tail: true}
---
apiVersion: hobby.example.com/v1
kind: Kite
metadata: {name: dusk, namespace: prepium-tutorial-2}
spec: {colour: blue, lengthCm: 90}
EOF

Now try a third Kite with lengthCm: 10: the API server rejects it as invalid because of minimum: 30. A misspelt field such as colur is caught by kubectl's strict validation, and with validation turned off the server would silently prune it. Both behaviours exist because the schema is structural.

4. Query across namespaces

kubectl get kites -A
kubectl get kites -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,LEN:.spec.lengthCm
kubectl get kites -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.colour}{"\n"}{end}'
kubectl get kites -A -o jsonpath='{.items[?(@.spec.colour=="blue")].metadata.name}'

custom-columns gives a quick table; range ... end lets you shape every line exactly, which is what you want when output goes into a file. The ?() filter selects items by a field value. JSONPath in kubectl does not support regular expressions; pipe -o json to a tool such as jq if you need them. Deleting an instance is the ordinary kubectl delete kite NAME -n NAMESPACE.

Common mistakes

Example checkEvidence
Correct resource identifiedGroup, kind and scope from api-resources
Instance matches the schemakubectl get KIND NAME -n NS -o yaml shows the intended spec
Cross-namespace listing completeLine count equals the matching rows in kubectl get KIND -A
Only the intended object removedOther instances still listed after the delete

Cleanup: kubectl delete -f kite-crd.yaml (removes all Kites), then kubectl delete namespace prepium-tutorial prepium-tutorial-2. References: Extend the Kubernetes API with CustomResourceDefinitions, JSONPath support, kubectl api-resources. Next, try API deprecations.

Difficulty
Medium · 7 minutes target
Domain
Application Environment, Configuration and Security
Grading
Live checks · partial credit

Practice scope

This additional curriculum lab is available in single-task practice with the existing 12-minute limit. The three CKAD mocks keep their original 17-task sets. Hints, an explained solution and requirement-by-requirement feedback are available in the lab.

Browse CKAD practice tasks →