Introduction

The Certified Kubernetes Application Developer (CKAD) exam is a hands-on, command-line-heavy challenge that tests your ability to design, build, and deploy applications in Kubernetes - all while racing the clock.

I recently took the CKAD and passed with an overall score of around 82%. Not perfect - but let’s be honest, in a terminal-only, time-boxed environment, "running" is the new "succeeding" 😄.

This article collects the practical snippets, workflows, and quick references I used during my preparation.

General Tips

  • Use a consistent naming schema for YAML files, e.g. Question 1: Create Deployment → q1-deploy.yaml

  • Always run helm repo update before pulling from a Helm repo.

  • To inspect a running pod, open a shell: k exec -it podname — sh

  • Use k explain to explore Kubernetes object definitions, e.g.:

    • k explain ingress

    • k explain ingress.spec

    • k explain ingress.spec.rules

Core Workflows

Pods and Deployments

Create a pod with image xxx:1.2.3 and container yyy

  1. Generate YAML:

    k run podname --image=xxx:1.2.3 --dry-run=client -o yaml > q1-pod.yaml
  2. Edit q1-pod.yaml and set the container name.

    k create -f q1-pod.yaml
    k get po

Create a pod that runs a command

For example, a pod that simply runs du -hs.
k run podname --image=busybox:latest --dry-run=client -o yaml --command -- sh -c 'du -hs' > q1-pod.yaml

Convert a pod to a deployment

  1. Export the pod YAML.

  2. Copy a sample deployment from the docs.

  3. Copy the pod’s metadata and spec into the deployment’s template section.

  4. Adjust spec.selector.matchLabels to match one of the labels in spec.template.metadata.labels.

Rollback a deployment

k -n namespace get deploy (1)
k -n namespace rollout history deploy my-deployment (2)
k -n namespace rollout undo deploy my-deployment (3)
1 List deployments
2 Show rollout history
3 Roll back to a previous revision

Move a running pod to another namespace

k -n oldns get po thepod -o yaml > q1-pod.yaml
vim q1-pod.yaml (1)
k -n newns create -f q1-pod.yaml
k -n newns get po
1 Change namespace and remove status, token volumes, and nodeName.

Services and Networking

Create a service

The fastest way is to use k expose pod.
Expose service with port mapping 3333:80
k -n namespace expose pod pod-name --name svc-name --port 3333 --target-port 80

Alternatively, create a YAML definition:

k -n namespace create service clusterip svcname --tcp 3333:80 --dry-run=client -o yaml > q1-svc.yaml
vim q1-svc.yaml (1)
1 Adjust the selector labels.

Check that the service has an active endpoint:

k -n namespace get ep

Run a temporary pod to test a service

k run tmp --restart=Never --rm --image=nginx:alpine -i -- curl http://svcname.namespace:3333

Jobs and CronJobs

Create a job that runs a command

We want to create a job that runs ls -la with 3 completions and 2 parallel pods.

k create job jobname --image=xxxx:1.2.3 -- sh -c "ls -la" --dry-run=client -o yaml > q2-job.yaml

Edit:

spec:
  completions: 3
  parallelism: 2

Configuration

Secrets (as environment variables and volumes)

We need a secret thesecret with username=admin and password=irsosecret.

  1. Create the secret:

    k -n namespace create secret generic thesecret --from-literal=username=admin --from-literal=password=irsosecret
  2. Reference it in a pod:

    - name: S_USERNAME
      valueFrom:
        secretKeyRef:
          name: thesecret
          key: username
    - name: S_PASSWORD
      valueFrom:
        secretKeyRef:
          name: thesecret
          key: password
  3. Test:

    k exec -it nginx -- sh -c printenv | grep S_

Mount the secret as a volume:

spec:
  volumes:
  - name: mysecret-volume
    secret:
      secretName: thesecret
  containers:
  - name: nginx
    volumeMounts:
    - name: mysecret-volume
      mountPath: /tmp/mysecret

Check contents:

k exec -it nginx -- sh -c 'ls -la /tmp/mysecret && cat /tmp/mysecret/*'

ConfigMaps

Mount as env
k create cm envconf --from-literal=mykey=myvalue
k run nginx --image=nginx:latest -o yaml --dry-run=client > q1_pod.yaml

Modify q1_pod.yaml:

envFrom:
  - configMapRef:
      name: envconf

Create and test:

k create -f q1_pod.yaml
k exec -it nginx -- sh -c printenv | grep mykey
mykey=myvalue
Mount as volume
echo '<html><head><body><h1>served from configmap</h1></body></html>' > file.html
k create cm htmlconfig --from-file=index.html=file.html
k run nginx --image=nginx:latest -o yaml --dry-run=client > q2_pod.yaml

Modify q2_pod.yaml:

volumes:
- name: config-volume
  configMap:
    name: htmlconfig
containers:
- name: nginx
  image: nginx:latest
  volumeMounts:
  - name: config-volume
    mountPath: /usr/share/nginx/html

Deploy and test:

k create -f q2_pod.yaml
k exec -it nginx -- sh -c 'curl localhost:80'
<html><head><body><h1>served from configmap</h1></body></html>

Storage (Volumes, PVs, PVCs)

We’ll create a PersistentVolume, PersistentVolumeClaim, and a Deployment that mounts it.

  1. Copy PV and PVC specs from Kubernetes docs and adjust.

  2. Verify that both are Bound:

    k -n namespace get pv,pvc
  3. Create a Deployment referencing the PVC and confirm mount:

    k -n namespace describe pod pod-from-deployment | grep -A2 Mounts:

Helm

Always run helm repo update before installing.
List releases
helm -n namespace ls
Uninstall release
helm -n namespace uninstall releasename
Repository list
helm repo list
Fetch repository updates
helm repo update
Search repository (e.g., nginx)
helm search repo nginx
Upgrade release to latest image
helm -n namespace upgrade myrelease bitnami/nginx
Install with custom values (replicaCount=2)
helm show values bitnami/apache (1)
helm -n namespace install myrelease bitnami/apache --set replicaCount=2 (2)
1 Show default Helm values
2 Override via --set
Find stuck releases
helm -n namespace ls -a (1)
1 The -a flag shows all (including failed) releases.

Containers, Docker, Podman

Docker

Build image xoxo with tags v1 and latest
sudo docker build -t registry:5000/xoxo:latest -t registry:5000/xoxo:v1 .
sudo docker image ls
Push to registry
sudo docker push registry:5000/xoxo:latest
sudo docker push registry:5000/xoxo:v1

Podman

Build image xoxo:v1
podman build -t registry:5000/xoxo:v1 .
podman images
Push to registry
podman push registry:5000/xoxo:v1
Run container
podman run -d --name xoxo registry:5000/xoxo:v1
podman ps
podman logs xoxo

Advanced Topics

InitContainers

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-init
spec:
  initContainers:
  - name: init
    image: busybox
    command: ['sh', '-c', 'echo initializing && sleep 5']
  containers:
  - name: main
    image: nginx

NetworkPolicies

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
  namespace: netpol-test
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress

Resource Limits

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 200m
    memory: 256Mi

Labels and Annotations

k label pod mypod env=prod
k annotate pod mypod description="frontend pod"

Common Pitfalls

  • Forgetting -n when switching namespaces.

  • Copying status: or serviceAccount tokens when cloning pods.

  • Misspelled image names.

  • Missing spec.selector.matchLabels in deployments.

  • Ignoring kubectl get events for debugging.

Exam Time Management

  • Duration: 2 hours - aim for ~5 minutes per question.

  • Mark tricky questions and return later.

  • Use kubectl explain instead of searching docs.

  • Practice with alias k=kubectl for speed.

  • Generate YAMLs with --dry-run=client -o yaml.

  • Always verify with kubectl get all -n <ns>.

Final Thoughts

The CKAD exam is less about theory and more about speed and precision under pressure. Practice each task until it feels automatic. Good luck - and may your pods always be Running!