Certified Kubernetes Application Developer
Build, deploy, configure, observe, and connect apps on Kubernetes, the developer-focused exam.
Aligned to the current CKAD curriculum (Kubernetes v1.31+).
What the exam is like
- Duration
- 2 hours
- Pass mark
- 66%
- Format
- Performance-based, ~15–20 hands-on tasks in a live cluster, driven by kubectl
- Resources
- Open-book: the official kubernetes.io docs are allowed during the exam
- Validity
- ~2 years (then recertify)
Take the full mock exam
17 tasks across every domain · 120 min · scored with a per-domain breakdown
Are you ready for CKAD?
Take one timed mock and we'll score your readiness, no guessing.
Application Design and Build
Package code into containers and run it with the right workload, multi-container, batch, and storage patterns.
Containers & images
A container packages your app with everything it needs, code, runtime, libraries, into one isolated, portable unit. A Dockerfile builds an image; a running copy of that image is a container.
Key points
- Dockerfile → image (build once) → container (run many).
- Images are layered and immutable; tag with versions, not just :latest.
- Multi-stage builds keep final images small by discarding build tools.
Choosing the right workload
Kubernetes has a controller for each kind of app. A Deployment runs stateless apps; a StatefulSet runs apps that need stable names and their own storage (databases); a DaemonSet runs one Pod on every node (agents); a Job/CronJob runs batch work once or on a schedule.
Key points
- Deployment: stateless, interchangeable replicas.
- StatefulSet: stable network IDs + per-Pod persistent storage.
- DaemonSet: exactly one Pod per (matching) node.
- Job / CronJob: run-to-completion / scheduled batch work.
Pods, the smallest unit
A Pod is the smallest thing Kubernetes runs. It wraps one or more containers that share the same network address and can share storage. Most Pods hold a single container; in production a controller like a Deployment manages them for you.
Key points
- Containers in a Pod share the Pod's IP and localhost.
- A Pod is ephemeral, if it dies it is replaced, not resurrected.
- kubectl run / create generate Pods or higher-level objects.
Multi-container patterns
Sometimes a Pod runs a helper beside the main app. An init container runs to completion before the app starts (setup); a sidecar runs alongside the app for its whole life (logging, proxying). They compose behaviour without changing the main app.
Key points
- initContainers run sequentially to completion before app containers start.
- Sidecars (log shipper, Istio proxy) share the Pod lifecycle and volumes.
- Patterns: sidecar, ambassador (proxy outbound), adapter (reshape output).
Init container that waits for a dependency
spec:
initContainers:
- name: wait-db
image: busybox:1.36
command: ['sh','-c','until nc -z db 5432; do sleep 2; done']
containers:
- name: app
image: myapp:1.0Jobs & CronJobs
A Job runs a Pod until a task completes successfully, then stops, ideal for batch work like a migration. A CronJob runs Jobs on a schedule, like cron on Linux.
Key points
- Job: completions and parallelism control how many Pods run/succeed.
- backoffLimit caps retries on failure.
- CronJob uses standard cron syntax in spec.schedule.
Volumes, ephemeral & persistent
Containers lose their files when they restart, so Pods use Volumes for storage. An emptyDir lasts as long as the Pod and is shared between its containers (scratch space). For storage that survives the Pod, a PersistentVolumeClaim (PVC) requests durable storage, which Kubernetes binds to a PersistentVolume (PV).
Key points
- emptyDir: Pod-lifetime, shared across the Pod's containers.
- PVC requests storage; a PV (often auto-provisioned via a StorageClass) fulfils it.
- Always two halves: spec.volumes (define) + container.volumeMounts (use).
- Access modes: RWO (one node), ROX (many read), RWX (many read/write).
Mount a PVC for durable storage (swap for emptyDir = scratch)
spec:
containers:
- name: app
image: nginx
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
persistentVolumeClaim:
claimName: web-pvc
# emptyDir: {} # <- use this instead for pod-lifetime scratchApplication Deployment
Roll apps out and back safely using Deployments, update strategies, and packaging tools.
Deployments & ReplicaSets
A Deployment runs an app reliably: you declare the desired state, "3 copies of this image", and Kubernetes keeps it true, replacing failed Pods. It manages a ReplicaSet, which actually keeps the right number of Pods running.
Key points
- Declarative: you describe the end state, not the steps.
- replicas sets how many Pod copies run.
- Deployment → manages ReplicaSet → manages Pods.
Rolling updates & rollbacks
Changing a Deployment's image triggers a rolling update by default: new Pods come up and old ones leave gradually, so there's no downtime. If the new version is bad, roll back to the previous revision with one command.
Key points
- set image triggers a rolling update.
- rollout status / history / undo to watch, inspect, revert.
- maxSurge and maxUnavailable tune rollout aggressiveness.
Update, watch, and roll back
kubectl set image deploy/web web=nginx:1.26 # trigger update kubectl rollout status deploy/web # watch it kubectl rollout undo deploy/web # revert to previous
Deployment strategies
RollingUpdate (default) replaces Pods gradually. Recreate kills all old Pods then starts new ones, simple but with downtime. Blue-green and canary (two Deployments + a Service or mesh) test a new version on some traffic before full cutover.
Key points
- RollingUpdate: zero downtime, but old + new run together briefly.
- Recreate: downtime, but no version overlap (use for incompatible changes).
- Canary/blue-green: shift a fraction of traffic to validate before promoting.
Helm & Kustomize (packaging)
Real apps are many YAML files. Helm packages them as a reusable chart with values you override per environment. Kustomize layers patches on a base without templating. Both avoid copy-pasting YAML across dev/staging/prod.
Key points
- Helm: charts + values.yaml; helm install / upgrade / rollback.
- Kustomize: base + overlays; kubectl apply -k ./overlay.
- Helm templates; Kustomize patches, different philosophies.
Observability & Maintenance
Tell whether an app is healthy, read what it is doing, debug it, and keep manifests current.
Liveness, readiness & startup probes
Probes let Kubernetes check your app's health. Liveness asks "is this stuck? restart it if so". Readiness asks "is this ready for traffic? if not, stop sending requests (don't kill it)". Startup gives slow-booting apps time before the others kick in.
Key points
- Liveness failure → container restarted.
- Readiness failure → Pod removed from Service endpoints (no restart).
- Put dependency checks in readiness, not liveness, to avoid restart storms.
Liveness + readiness on a container
livenessProbe: # restart if this fails
httpGet: { path: /healthz, port: 8080 }
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe: # stop traffic if this fails
httpGet: { path: /ready, port: 8080 }
failureThreshold: 3Logs & events
When something is wrong, two commands tell most of the story. kubectl logs shows what a container printed; kubectl describe shows recent Events, scheduling failures, image pull errors, probe failures.
Key points
- kubectl logs pod [-c container] [--previous] for crashed containers.
- kubectl describe pod surfaces Events at the bottom, read them first.
- kubectl get events --sort-by=.lastTimestamp for a timeline.
Debugging & metrics
Beyond logs, exec into a running container to poke around, and use kubectl top (when metrics-server is installed) for live CPU/memory. These find why something is slow or crashing.
Key points
- kubectl exec -it pod -- sh to get a shell inside.
- kubectl top pod / node for live resource usage.
- kubectl get pod -o wide shows node and IP for network debugging.
API deprecations
Kubernetes evolves, and old API versions get removed in newer releases (e.g. an object that used a beta apiVersion now needs the stable one). Apply a manifest with a removed apiVersion and it fails, you fix it by updating the apiVersion to the current served one.
Key points
- apiVersion must match what the cluster version still serves.
- Deprecated ≠ removed, you get warnings before a version disappears.
- kubectl explain and kubectl api-resources show the current version.
Find the current apiVersion for a kind
kubectl explain deployment # shows: VERSION: apps/v1 kubectl api-resources | grep deployment
Environment, Configuration & Security
Feed config and secrets into apps, cap resources, control who can do what, and run with least privilege. The heaviest domain.
ConfigMaps & Secrets
Keep configuration out of your image so the same image runs everywhere. A ConfigMap holds non-sensitive settings; a Secret holds sensitive values. Inject either as environment variables or mounted files.
Key points
- Inject via env (envFrom / valueFrom) or as a mounted volume.
- Secrets are only base64-encoded, not encrypted by default, treat as sensitive.
- A mounted ConfigMap updates the file live; env vars need a Pod restart.
Three ways to inject config
envFrom: # all keys → env vars
- configMapRef: { name: app-config }
env:
- name: DB_PASS # one secret key → env var
valueFrom:
secretKeyRef: { name: db, key: password }
volumeMounts: # or mount as files
- name: cfg
mountPath: /etc/configRequests, limits & quotas
A request is the resources a Pod is guaranteed (used by the scheduler to place it); a limit is the ceiling it can't exceed. Over a memory limit gets the container OOMKilled; over a CPU limit just throttles it. ResourceQuotas cap a whole namespace.
Key points
- requests drive scheduling; limits cap usage.
- Memory over limit → OOMKilled (exit 137); CPU over limit → throttled.
- ResourceQuota and LimitRange govern a namespace.
Container requests (scheduling) + limits (cap)
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"SecurityContext
A SecurityContext controls how securely a container runs: which user it runs as, whether it can gain extra privileges, and which Linux capabilities it has. Running as non-root and dropping capabilities is the baseline for a hardened app.
Key points
- runAsNonRoot / runAsUser set identity.
- allowPrivilegeEscalation: false and readOnlyRootFilesystem: true harden it.
- capabilities.drop: [ALL], then add only what's needed.
Hardened SecurityContext (Pod level + container override)
spec:
securityContext: # applies to all containers
runAsNonRoot: true
runAsUser: 1000
containers:
- name: app
image: myapp
securityContext: # overrides for this container
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: ["ALL"] }ServiceAccounts
A ServiceAccount is the identity a Pod uses to talk to the Kubernetes API. By default Pods get the namespace's default account; for least privilege you create a dedicated ServiceAccount and grant it only the permissions (via RBAC) it needs.
Key points
- spec.serviceAccountName sets which account a Pod uses.
- Permissions come from RBAC (Roles + bindings), not the account itself.
- Avoid the default account for anything that calls the API.
RBAC, authorization
RBAC decides who can do what. A Role is a set of allowed verbs (get, list, create…) on resources within a namespace; a RoleBinding grants that Role to a user or ServiceAccount. ClusterRole and ClusterRoleBinding do the same cluster-wide.
Key points
- Role + RoleBinding = namespaced; ClusterRole + ClusterRoleBinding = cluster-wide.
- Bind Roles to ServiceAccounts so Pods get scoped API access.
- Least privilege: grant only the verbs and resources needed.
Grant a ServiceAccount read access to Pods, then verify
kubectl create role reader --verb=get,list,watch --resource=pods kubectl create rolebinding read-pods \ --role=reader --serviceaccount=dev:app-sa kubectl auth can-i list pods --as=system:serviceaccount:dev:app-sa
Admission control
After a request is authenticated and authorized, admission controllers get the final say, they can validate or even mutate it before it's stored. This is how rules like "every Pod must set resource limits" or "no privileged containers" get enforced. Pod Security Admission (PSA) is the built-in one you'll meet most.
Key points
- Runs after authentication + authorization, before the object is saved.
- Two kinds: validating (accept/reject) and mutating (modify defaults).
- Pod Security Admission enforces baseline/restricted profiles per namespace.
Enforce the "restricted" Pod Security profile on a namespace
kubectl label namespace dev \ pod-security.kubernetes.io/enforce=restricted
Custom Resources (awareness)
Kubernetes can be extended with Custom Resource Definitions (CRDs), new object types beyond the built-ins. Operators use these to manage complex apps. For CKAD you mainly recognise them and interact with existing custom resources, not author controllers.
Key points
- A CRD defines a new kind; instances are custom resources.
- kubectl get <crd-plural> works just like built-in objects.
- Operators = CRD + a controller that reconciles it.
Services & Networking
Give Pods stable addresses, expose them, control who can talk to whom, and route external traffic.
Services
Pods are ephemeral and their IPs change, so you never talk to a Pod directly. A Service gives a stable name and IP that load-balances across Pods matched by labels. ClusterIP is internal-only, NodePort opens a port on every node, LoadBalancer provisions an external load balancer.
Key points
- Service selects Pods by label and load-balances across them.
- ClusterIP (internal) → NodePort (node port) → LoadBalancer (external).
- A Service's ClusterIP is stable for its whole life.
Service discovery & DNS
Every Service gets a DNS name inside the cluster, so apps find each other by name, not IP. Same namespace uses the short name; across namespaces use service.namespace. This is how microservices locate one another.
Key points
- Same namespace: http://web. Cross-namespace: http://web.other-ns.
- Full form: service.namespace.svc.cluster.local.
- DNS is provided by CoreDNS in the cluster.
NetworkPolicies
By default every Pod can talk to every other Pod. A NetworkPolicy locks that down, it specifies which Pods may send traffic to (ingress) or receive from (egress) a selected set of Pods. This is how you isolate workloads.
Key points
- Default is allow-all; a policy selecting a Pod flips it to default-deny for that direction.
- Rules match by podSelector, namespaceSelector, or ipBlock.
- Requires a CNI plugin that enforces policies (e.g. Calico).
Namespace-wide default-deny for incoming traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: default-deny-ingress }
spec:
podSelector: {} # selects ALL pods in the namespace
policyTypes: ["Ingress"] # no ingress rules = deny all inboundIngress
A Service exposes one app; Ingress routes external HTTP/HTTPS to many Services by hostname and path, behind one entry point. An Ingress controller (like nginx) does the routing; the Ingress object is just the rules.
Key points
- Routes by host and path to backend Services.
- Needs an Ingress controller running to take effect.
- Handles TLS termination via a referenced Secret.
Independent study material aligned to the public CKAD curriculum. Not affiliated with or endorsed by The Linux Foundation.