Create a Custom Resource Definition and Verify it in the Kubernetes API
Scenario
Your team is building an internal platform that manages website configurations
inside Kubernetes. Rather than storing these configs in ConfigMaps, the
platform team wants a proper custom resource type — so operators can work
with Website objects the same way they work with any built-in resource.
Your job is to register the Website custom resource type with the cluster,
grant the default service account the RBAC permissions it needs to query the
new endpoint, confirm the endpoint is live with curl, and save the response to
disk for the platform audit trail.
Task
- Create a CRD with the following specification:
Field Value Resource name (plural) websitesResource name (singular) websiteShort name wsGroup apps.acme.comVersion v1Scope NamespacedKind Website
Thev1schema must accept these fields underspec:Field Type Required urlstringyes replicasintegerno - Confirm the CRD is
Established— meaning the API server has registered and activated the new resource type. - Create the following RBAC resources so the
defaultservice account in thedefaultnamespace can query thewebsitesendpoint:- A
ClusterRolenamedwebsite-readerthat grantsgetandlistonwebsitesin theapps.acme.comAPI group. - A
ClusterRoleBindingnamedwebsite-reader-bindingthat bindswebsite-readerto thedefaultservice account in thedefaultnamespace.
- A
- Query the live API endpoint using curl and save the JSON response to
/home/laborant/crd-live.json.
Hint 1 — CRD Manifest Structure
A CRD is itself a Kubernetes resource — apply it with a manifest like any other object. The key fields to get right are the group, versions, names, and scope:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: <plural>.<group> # must match names.plural + spec.group exactly
spec:
group: <group>
scope: Namespaced
names:
plural: <plural>
singular: <singular>
shortNames:
- <short>
kind: <Kind>
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
# your fields here
The metadata.name must be exactly <plural>.<group> — Kubernetes enforces
this and will reject the manifest if they don't match.
Documentation
Hint 2 — Confirming the CRD is Established
After applying the manifest, Kubernetes takes a moment to register the new API endpoint. Inspect the CRD status to check its conditions:
kubectl describe crd websites.apps.acme.com
Look for this in the Conditions section of the output:
Type: Established
Status: True
If NamesAccepted shows False, the metadata.name does not match
the combination of spec.group and names.plural — fix the manifest and
reapply.
Documentation
Hint 3 — Creating RBAC for the Custom Resource
RBAC for custom resources works exactly like RBAC for built-in resources.
Use the custom resource's API group (apps.acme.com) and plural name
(websites) in the rules block of your ClusterRole:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: website-reader
rules:
- apiGroups:
- apps.acme.com
resources:
- websites
verbs:
- get
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: website-reader-binding
subjects:
- kind: ServiceAccount
name: default
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: website-reader
Apply both resources in one step with kubectl apply -f <file>. You can
also verify the permission took effect with:
kubectl auth can-i list websites.apps.acme.com \
--as=system:serviceaccount:default:default
Documentation
Hint 4 — Querying the API with curl
The Kubernetes API server listens on port 6443 on the control plane node.
Requests must be authenticated with a Bearer token. You can create a
short-lived token for the default service account:
TOKEN=$(kubectl create token default --duration=60s)
Then query the endpoint and save the output in one step using curl's -o flag:
curl -sk \
--header "Authorization: Bearer $TOKEN" \
https://localhost:6443/apis/apps.acme.com/v1/websites \
-o /home/laborant/crd-live.json
-ssuppresses the progress bar-kskips TLS certificate verification (the cluster uses a self-signed cert)-owrites the response body directly to the file
Documentation