CKAD is a 2-hour, performance-based exam on Kubernetes v1.35. You solve application tasks in a terminal: no cluster installation, but relentless resource creation, editing, validation and debugging. Current domains: Application Design & Build 20% · Deployment 20% · Observability & Maintenance 15% · Environment, Configuration & Security 25% · Services & Networking 20%. Generate YAML, edit only what the task needs, apply, and verify.
0. First-minute setup and the task loop
alias k=kubectl export do="--dry-run=client -o yaml" source <(kubectl completion bash) complete -o default -F __start_kubectl k # every task: context → namespace → generate/edit → apply → verify k config use-context <named-context> k config set-context --current --namespace=<namespace> k get ns # useful editor defaults cat <<'EOF' >> ~/.vimrc set expandtab tabstop=2 shiftwidth=2 number autoindent EOF
kubectl apply succeeds. Check the exact requested name, namespace, image, labels, ports, rollout, readiness and output file. API acceptance is not task completion.1. kubectl generation and fast editing
k run app --image=nginx:1.27 $do > pod.yaml k create deploy web --image=nginx:1.27 --replicas=3 $do > deploy.yaml k create job report --image=busybox:1.36 $do -- sh -c 'date; echo done' > job.yaml k create cronjob cleanup --image=busybox:1.36 --schedule='*/5 * * * *' \ $do -- sh -c 'rm -rf /tmp/cache/*' > cron.yaml k create cm app-config --from-literal=MODE=prod $do > cm.yaml k create secret generic db --from-literal=password='change-me' $do > secret.yaml k create service clusterip web --tcp=80:8080 $do > svc.yaml k explain pod.spec.containers --recursive k apply -f deploy.yaml k get deploy,pods,svc -o wide k diff -f deploy.yaml
Use imperative generation as a schema-safe starting point. When a field is difficult to generate—probes, affinity, security context, volumes—use kubectl explain or an allowed documentation example, then adapt it.
2. Application Design and Build (20%)
Choose the right workload
| Requirement | Resource | Key behavior |
|---|---|---|
| Stateless long-running replicas | Deployment | Rolling updates and rollback |
| One Pod on each node | DaemonSet | Node-local agents |
| Finite batch execution | Job | Completion, retries, parallelism |
| Scheduled batch execution | CronJob | Creates Jobs on schedule |
| Stable identity/storage | StatefulSet | Ordered Pods and volume claims |
Container images and commands
FROM python:3.13-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . USER 10001 ENTRYPOINT ["python", "app.py"] CMD ["--port", "8080"]
In a PodSpec, command overrides image ENTRYPOINT and args overrides CMD. Prefer exec-form arrays. Pin a tag or digest required by the task; changing a Deployment's Pod template triggers a rollout.
Multi-container patterns
| Pattern | Use | Lifecycle |
|---|---|---|
| Init | Precondition, migration, file generation | Runs to completion before app containers, sequentially |
| Sidecar | Proxy, log/telemetry helper, file synchronization | Runs alongside the app; shares Pod network/volumes |
| Adapter | Normalizes app output for another consumer | Companion container transforms shared data |
| Ambassador | Local proxy to an external dependency | App calls localhost; proxy owns remote connection logic |
spec:
initContainers:
- name: prepare
image: busybox:1.36
command: ["sh", "-c", "echo ready > /work/status"]
volumeMounts: [{ name: work, mountPath: /work }]
containers:
- name: app
image: nginx:1.27
volumeMounts: [{ name: work, mountPath: /usr/share/nginx/html }]
- name: sidecar
image: busybox:1.36
command: ["sh", "-c", "tail -F /work/status"]
volumeMounts: [{ name: work, mountPath: /work }]
volumes: [{ name: work, emptyDir: {} }]
Ephemeral and persistent volumes
emptyDir: created for the Pod and removed with it; containers in the Pod share it.configMap/secret: project configuration as files.persistentVolumeClaim: attach durable storage through a PVC.projected: combine several sources such as Secret, ConfigMap and service-account token.
3. Application Deployment (20%)
Deployments, rollouts and rollback
k create deploy web --image=nginx:1.26 --replicas=4
k set image deploy/web nginx=nginx:1.27 --record=false
k rollout status deploy/web
k rollout history deploy/web
k rollout undo deploy/web
k scale deploy/web --replicas=6
k patch deploy web -p '{"spec":{"strategy":{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}'
maxSurge allows extra Pods during a rolling update; maxUnavailable limits unavailable desired replicas. A rollout stalls when new Pods never become Ready.
Blue/green and canary with primitives
- Blue/green: two Deployments with different version labels; switch a Service selector after green is verified.
- Canary: stable and canary Deployments share the Service's selector; replica ratio approximates traffic percentage.
- Rolling: one Deployment progressively replaces its ReplicaSet.
# blue/green cutover: Service changes version selector
k patch svc web -p '{"spec":{"selector":{"app":"web","version":"green"}}}'
k get endpointslices -l kubernetes.io/service-name=web
Helm and Kustomize
helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update helm search repo nginx helm install edge bitnami/nginx -n app --create-namespace --set replicaCount=2 helm list -A helm upgrade edge bitnami/nginx -n app --set replicaCount=3 helm rollback edge 1 -n app helm uninstall edge -n app k kustomize overlays/prod # render and inspect k apply -k overlays/prod # apply the overlay
4. Observability and Maintenance (15%)
Startup, readiness and liveness
| Probe | Failure effect | Question it answers |
|---|---|---|
| startup | Blocks liveness/readiness until success; restart on repeated failure | Has the slow app finished starting? |
| readiness | Removes Pod from Service endpoints | Should this Pod receive traffic now? |
| liveness | Restarts the container | Is the process stuck and unable to recover? |
startupProbe:
httpGet: { path: /health/startup, port: 8080 }
failureThreshold: 30
periodSeconds: 2
readinessProbe:
httpGet: { path: /health/ready, port: 8080 }
periodSeconds: 5
livenessProbe:
httpGet: { path: /health/live, port: 8080 }
periodSeconds: 10
Logs, events and ephemeral debugging
k describe pod app
k logs app -c api
k logs app -c api --previous
k logs deploy/web --all-containers --tail=100
k get events --sort-by=.lastTimestamp
k exec -it app -c api -- sh
k debug -it app --image=busybox:1.36 --target=api
k top pods -A
k get pod app -o jsonpath='{.status.containerStatuses[*].state}'
Debug from outside in: object status → events → logs → effective spec → in-container checks → Service endpoints and policy. For API removals, use kubectl explain, current documentation and tools such as kubectl convert if installed; do not keep deprecated versions merely because old YAML still looks familiar.
5. Environment, Configuration and Security (25%)
ConfigMaps and Secrets
envFrom:
- configMapRef: { name: app-config }
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef: { name: db, key: password }
volumeMounts:
- { name: settings, mountPath: /etc/app, readOnly: true }
volumes:
- name: settings
configMap:
name: app-config
items: [{ key: config.yaml, path: config.yaml }]
Environment values are captured when the container starts. Mounted ConfigMap/Secret volumes can update eventually, but the application must reload them. A subPath mount does not receive those updates.
Resources, quotas and limits
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: 500m, memory: 256Mi }
- Scheduler places using requests. CPU limit causes throttling; memory limit can cause OOM termination.
- ResourceQuota caps aggregate namespace consumption/object counts.
- LimitRange supplies or constrains per-object defaults/min/max.
ServiceAccounts, RBAC and admission
k create sa reporter -n team k create role pod-reader --verb=get,list,watch --resource=pods -n team k create rolebinding reporter-read --role=pod-reader \ --serviceaccount=team:reporter -n team k auth can-i list pods --as=system:serviceaccount:team:reporter -n team
Authentication establishes identity, authorization evaluates permission, and admission validates/mutates the requested object. A ServiceAccount is a workload identity. Set automountServiceAccountToken: false when the Pod does not need API credentials.
Application security context
spec:
securityContext:
runAsNonRoot: true
seccompProfile: { type: RuntimeDefault }
containers:
- name: app
image: example/app:1.4
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: ["ALL"] }
Pod-level context supplies defaults; container-level fields can be more specific. Dropping capabilities, disallowing privilege escalation and running as non-root are different controls—apply the exact combination requested.
CRDs and Operators
k get crd k api-resources | grep -i <kind> k explain <resource>.spec k get <custom-resource> -A k describe <custom-resource> <name>
A CRD extends the API with a new resource kind. An Operator pairs custom resources with a controller that reconciles domain-specific lifecycle. Discover the actual API group and schema before writing a custom resource.
6. Services and Networking (20%)
Services and endpoint troubleshooting
k expose deploy web --port=80 --target-port=8080 k get svc web -o yaml k get endpointslices -l kubernetes.io/service-name=web k get pods --show-labels k run curl --image=curlimages/curl --rm -it --restart=Never -- \ curl -sS http://web:80/health
port is the Service port; targetPort is the backend Pod port. The Service selector must match Pod labels, and readiness must pass before endpoints receive traffic.
Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: { name: web }
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service: { name: web, port: { number: 80 } }
An Ingress resource needs an installed Ingress controller. Check class, host/path, Service name and Service port—not the container port.
NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: api-from-web, namespace: team }
spec:
podSelector: { matchLabels: { app: api } }
policyTypes: [Ingress]
ingress:
- from:
- podSelector: { matchLabels: { app: web } }
ports: [{ protocol: TCP, port: 8080 }]
podSelector in a peer selects Pods in the policy's namespace unless combined with a namespaceSelector. Policies are additive and require CNI enforcement.7. High-yield manifest patterns
Jobs and CronJobs
apiVersion: batch/v1
kind: Job
metadata: { name: report }
spec:
completions: 6
parallelism: 2
backoffLimit: 3
activeDeadlineSeconds: 300
template:
spec:
restartPolicy: Never
containers:
- name: report
image: example/report:1.2
completions is successful work items; parallelism is concurrent Pods; backoffLimit bounds retries. CronJobs add schedule, concurrencyPolicy, history limits and startingDeadlineSeconds. To test a CronJob immediately, create a one-off Job from it.
k create job test-run --from=cronjob/cleanup k get jobs,pods k logs job/test-run
Availability, autoscaling and disruption
k autoscale deploy web --min=2 --max=10 --cpu-percent=70 k get hpa web k top pods -l app=web
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata: { name: web-pdb }
spec:
minAvailable: 2
selector: { matchLabels: { app: web } }
An HPA changes replica count from metrics; it needs meaningful requests and a metrics source. A PodDisruptionBudget limits voluntary disruptions such as drain, not crashes or node failure. minAvailable and maxUnavailable are alternative ways to express the budget.
PVC consumption
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: data }
spec:
accessModes: [ReadWriteOnce]
resources: { requests: { storage: 2Gi } }
---
spec:
containers:
- name: app
volumeMounts: [{ name: data, mountPath: /var/lib/app }]
volumes:
- name: data
persistentVolumeClaim: { claimName: data }
If a Pod is Pending, inspect the PVC too. StorageClass, access mode, requested size, topology and provisioning events must be satisfiable.
Output, JSONPath and patching
k get pods -o custom-columns='NAME:.metadata.name,NODE:.spec.nodeName,IMAGE:.spec.containers[*].image'
k get pods --sort-by=.metadata.creationTimestamp
k get pods -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.phase}{"\n"}{end}'
k patch deploy web --type=merge -p '{"spec":{"replicas":4}}'
k label pod app tier=frontend --overwrite
k annotate deploy web owner=platform --overwrite
When the task asks you to write output to a file, redirect the exact requested format and inspect the file. JSONPath quoting mistakes are common; test on the terminal before redirecting.
8. Status-to-action playbook
| Symptom | Check first | Likely fix |
|---|---|---|
| Pending | Events, requests, taints, affinity, PVC | Make placement/storage constraints satisfiable |
| ImagePullBackOff | Image spelling/tag and pull secret | Correct image or registry credentials |
| CrashLoopBackOff | logs --previous, command, config, probes | Repair process input or probe |
| Running but not Ready | Readiness path/port and app health | Fix readiness contract or dependency |
| Service unreachable | EndpointSlice, labels, readiness, targetPort | Repair selector/port/policy |
| Rollout stuck | New ReplicaSet Pods and progress events | Fix new template or undo rollout |
9. Common mistakes and final study order
- Working in the wrong context or namespace.
- Creating a Pod when the task asks for a Deployment, Job or CronJob.
- Changing labels without updating selectors.
- Confusing container port, Service port and targetPort.
- Using liveness where readiness or startup is required.
- Forgetting the container name for logs/exec in multi-container Pods.
- Expecting a ConfigMap environment variable to hot-reload.
- Writing a NetworkPolicy peer in the wrong namespace scope.
- Applying Helm/Kustomize output without inspecting the render.
- Leaving the object accepted but functionally broken.
Practice order: imperative generation → workloads and multi-container Pods → rollouts and deployment strategies → probes/logs/debug → configuration/resources/security → Services/Ingress/NetworkPolicy → timed mixed labs. Aim to complete ordinary object tasks in 2–4 minutes so debugging tasks can consume the time they deserve.
Official sources & freshness
This guide was checked against the official Linux Foundation exam page and CNCF curriculum on 19 July 2026. Exam versions and policies can change; re-check the source page before booking.