4. Custom Resource Definitions (CRDs) and Operators
Tip
This tutorial requires access to a public container registry, which your trainer will provide.
Trainer Instructions
Note: We’ll use a simple container registry hosted in Azure. Trainer tip: Ensure anonymous (public) pull access is enabled using:
az acr update --name <your-acr-name> --anonymous-pull-enabled true
1. Custom Resource Definitions (CRDs)
Custom resources extend the Kubernetes API to support application-specific objects. These resources aren’t available by default and must be defined explicitly.
Follow this example from the Kubernetes docs to create a CRD for participants.
Your custom resource should define the following schema:
fullName: string
email: string
registered: boolean
It should be namespaced in scope, with these naming attributes:
api-group: stable.workshop.io
scope: Namespaced
names:
plural: participants
singular: participant
kind: Participant
shortNames:
- part
Tip
You can choose any api group you like. The solutions are based on stable.workshop.io
Once defined, apply your CRD to the cluster.
Solution
The CRD should look like this one:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: participants.stable.workshop.io
spec:
group: stable.workshop.io
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
fullName:
type: string
email:
type: string
registered:
type: boolean
scope: Namespaced
names:
plural: participants
singular: participant
kind: Participant
shortNames:
- part
kubectl apply -f ~/exercise/kubernetes/CRD-Operator/participant-crd.yaml
Check that your CRD has been successfully created:
kubectl get crd
Let’s now create an actual Participant object named Alice in the classroom namespace.
apiVersion: stable.workshop.io/v1
kind: Participant
metadata:
name: alice-smith
namespace: classroom
spec:
fullName: Alice Smith
email: alice@example.com
registered: true
Solution
kubectl apply -f ~/exercise/kubernetes/CRD-Operator/participant.yaml
Verify that your custom resource was created by using kubectl:
Solution
kubectl get participants -n classroom
#OR
kubectl describe participant alice-smith -n classroom
2. Operators
Operators are powerful Kubernetes extensions that automate the management of complex workloads. They act as controllers for custom resources and perform operational tasks in response to changes.
In this section, we’ll write a basic operator that logs when a new participant is created, deleted or modified. It’s intentionally simple—just enough to show how operators work without getting lost in complexity.
We’ll use the Shell-Operator, a tool that lets you write operators using shell scripts. This is ideal for educational purposes and rapid prototyping.
Create a Simple Operator
First, create the folder structure for the operator:
mkdir -p operator/hooks
cd operator
Create a Dockerfile in the operator directory:
FROM ghcr.io/flant/shell-operator:latest
ADD ./hooks /hooks
RUN chmod +x /hooks/hook.sh
Now, inside the hooks folder, create a script file (e.g., hook.sh). Based on the documentation, try to fill in the blanks and questionmarks! Note, that our operator should just work on the namespace classroom, ignoring all other namespaces.
Example
#!/usr/bin/env bash
if [[ $1 == "--config" ]] ; then
cat <<EOF
configVersion: v1
kubernetes:
- apiVersion: <enter correct version>
kind: <enter correct kind>
executeHookOnEvent: ["<enter correct hooks>"]
namespace: ???
EOF
else
notifierName=$(jq -r .[0].??? $BINDING_CONTEXT_PATH)
watchEvent=???
echo "Particpant ${notifierName} was ${watchEvent,,}."
fi
This script outputs the hook configuration when called with --config (which is used by the shell operator), and otherwise logs a message using the resource name and the event.
Solution
#!/usr/bin/env bash
if [[ $1 == "--config" ]] ; then
cat <<EOF
configVersion: v1
kubernetes:
- apiVersion: stable.workshop.io/v1
kind: Participant
executeHookOnEvent: [ "Added", "Modified", "Deleted" ]
namespace:
nameSelector:
matchNames: ["classroom"]
EOF
else
notifierName=$(jq -r .[0].object.metadata.name $BINDING_CONTEXT_PATH)
watchEvent=$(jq -r .[0].watchEvent $BINDING_CONTEXT_PATH)
echo "Particpant ${notifierName} was ${watchEvent,,}."
fi
Build and Push Your Operator Image
Login to your Azure Container Registry:
docker login <your-acr-name>.azurecr.io -u <your-username> -p <your-password>
Solution
From within /kubernetes/CRD-Operator/operator, build and push the image:
docker buildx build -t <your-acr-name>.azurecr.io/participant-operator:<your-name> .
docker push <your-acr-name>.azurecr.io/participant-operator:<your-name>
Deploy the Operator Pod
Now it’s time to run your operator inside a Kubernetes pod. Create a yaml file for a pod that is using our previously built image. Add our service-account participant-notifier to monitor resources and log events.
name: participant-notifier-operator
namespace: classroom
serviceAccountName: participant-notifier
Solution
apiVersion: v1
kind: Pod
metadata:
name: participant-notifier-operator
namespace: classroom
spec:
containers:
- name: participant-notifier-operator
image: philipptest.azurecr.io/participant-operator:latest
#image: <your-acr-name>.azurecr.io/participant-operator:<your-name>
imagePullPolicy: Always
serviceAccountName: participant-notifier
kubectl apply -f ~/exercise/kubernetes/CRD-Operator/participant-operator-pod.yaml
Testing
Watch the pod logs:
kubectl logs participant-notifier-operator -n classroom -f
Question
You will notice that the operator cannot watch for new participant-ressources. What could be the reason?
Solution and Answer
We are missing permissions for our service account. We need to give our service account permissions to watch (get, list) for participants instead for pods.
Modify the created role to include permissions for participants:
k create clusterrole participant-notifier --verb=* --resource=participants.stable.workshop.io --dry-run=client -o yaml
or modify the exiting yaml:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
creationTimestamp: null
name: participant-notifier-role
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
- apiGroups:
- stable.workshop.io
resources:
- participants
verbs:
- '*'
Copy and past the new rules section to the existing yaml. Run kubectl apply -f <path to role file> to adjust the role. A solution is provided in ~/exercise/kubernetes/CRD-Operator/participant-notifier-role-update.yaml.
Now create a new Participant in the classroom namespace. You should see log output from your operator confirming that it reacted to the new resource.
Solution
k delete -f participant.yaml
k apply -f participant.yaml
k logs -n classroom pods/participant-notifier-operator | grep alice
End of Lab