Certification Prep
CKAD5 domains · 25 topics

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.

Run a diagnostic mock
1

Application Design and Build

Package code into containers and run it with the right workload, multi-container, batch, and storage patterns.

20%
Practice this domain: drills + timed mock →
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.

An image is a recipe; a container is the cooked dish. The same recipe makes the identical dish in any kitchen (machine).

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.
Exam tip: You won't build images much in CKAD, but know how a Pod references one: spec.containers[].image.
Go deeper in the full lesson →
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.

Pick the right vehicle for the trip: Deployment is a fleet of identical taxis, StatefulSet is numbered reserved cars, DaemonSet is "one on every street", Job is a single delivery run.

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.
Exam tip: If the task says "one per node" → DaemonSet; "stable identity / storage" → StatefulSet; "run once" → Job.
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.

A Pod is like an apartment: the containers inside are roommates who share one address (IP) and some shared rooms (volumes).

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.
Exam tip: Generate fast: kubectl run nginx --image=nginx --dry-run=client -o yaml > pod.yaml
Go deeper in the full lesson →
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.

Init container = the person who unlocks and preps the shop before opening. Sidecar = the assistant working beside you all day.

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.0
Exam tip: initContainers is a separate list from containers, don't put them in the same array.
Jobs & 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.

A Job is a one-off errand you mark "done". A CronJob is a recurring calendar reminder that kicks off that errand automatically.

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.
Exam tip: kubectl create job db-migrate --image=migrate -- ./run.sh · kubectl create cronjob nightly --image=busybox --schedule="0 2 * * *" -- echo hi
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).

emptyDir is a shared scratchpad the roommates use while they live there. A PVC is renting a storage unit that keeps your stuff even after you move out.

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 scratch
Exam tip: You can't generate PVCs imperatively, write the YAML. Forgetting either volumes OR volumeMounts is the classic mistake.
2

Application Deployment

Roll apps out and back safely using Deployments, update strategies, and packaging tools.

20%
Practice this domain: drills + timed mock →
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.

You set a thermostat to the temperature you want (desired state); it constantly works to keep the room there.

Key points

  • Declarative: you describe the end state, not the steps.
  • replicas sets how many Pod copies run.
  • Deployment → manages ReplicaSet → manages Pods.
Exam tip: kubectl create deploy web --image=nginx --replicas=3 --dry-run=client -o yaml · kubectl scale deploy/web --replicas=5
Go deeper in the full lesson →
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.

Replacing the tyres on a moving car one at a time, so it never has to stop.

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
Exam tip: kubectl rollout undo deploy/web is the fastest fix when a rollout goes bad.
Go deeper in the full lesson →
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.

Rolling = swap staff one shift at a time. Recreate = close the shop to re-staff. Canary = let a few customers try the new menu first.

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.
Exam tip: strategy.type is RollingUpdate or Recreate in the Deployment spec.
Go deeper in the full lesson →
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.

Helm is a fill-in-the-blanks template with a values form. Kustomize is a base document plus sticky-note edits per environment.

Key points

  • Helm: charts + values.yaml; helm install / upgrade / rollback.
  • Kustomize: base + overlays; kubectl apply -k ./overlay.
  • Helm templates; Kustomize patches, different philosophies.
Exam tip: kubectl apply -k is built in, no extra binary needed for Kustomize.
Go deeper in the full lesson →
3

Observability & Maintenance

Tell whether an app is healthy, read what it is doing, debug it, and keep manifests current.

15%
Practice this domain: drills + timed mock →
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.

Liveness = a pulse check (no pulse → revive). Readiness = the "open" sign (flip it off while restocking, but the shop still exists).

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: 3
Exam tip: Probe handlers: httpGet, tcpSocket, exec. Know initialDelaySeconds, periodSeconds, failureThreshold.
Go deeper in the full lesson →
Logs & 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.

logs is the app's diary; describe's Events are the building's security-camera log of what happened to it.

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.
Exam tip: logs --previous is gold for CrashLoopBackOff, it shows why the last run died.
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.

exec is walking into the room to look around; top is the fitness tracker showing how hard it's working.

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.
Exam tip: If a Pod is Pending, the reason is almost always in kubectl describe, scheduling/resource constraints.
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.

Like a form that's been replaced by a new edition, submit the old one and it's rejected; you re-file on the current form.

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
Exam tip: If a manifest errors on apply, check its apiVersion first, fixing one line often resolves it.
4

Environment, Configuration & Security

Feed config and secrets into apps, cap resources, control who can do what, and run with least privilege. The heaviest domain.

25%
Practice this domain: drills + timed mock →
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.

ConfigMap is the settings menu; a Secret is the locked drawer for passwords. The app reads them at runtime instead of baking them in.

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/config
Exam tip: kubectl create configmap app --from-literal=KEY=val · kubectl create secret generic db --from-literal=password=...
Go deeper in the full lesson →
Requests, 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.

Request = the seat reserved for you; limit = the most you can take from the buffet before being cut off.

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"
Exam tip: Set both requests and limits. Right-size from real usage, not guesses.
Go deeper in the full lesson →
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.

It's the access-badge level you give the app, grant the least it needs, nothing more.

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"] }
Exam tip: securityContext sits at Pod level (all containers) or per-container (overrides).
Go deeper in the full lesson →
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.

A ServiceAccount is the app's employee ID badge, it decides which doors (API actions) the app can open.

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.
Exam tip: kubectl create serviceaccount app, then bind a Role to it for scoped access.
Go deeper in the full lesson →
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.

A Role is a job description (what actions are allowed); a RoleBinding is hiring a specific person into that job.

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
Exam tip: kubectl auth can-i ... --as=... confirms a binding works without deploying anything.
Go deeper in the full lesson →
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.

Auth is the bouncer checking your ID and the guest list; admission control is the dress-code check at the door, even valid guests can be turned away or asked to adjust.

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
Exam tip: For CKAD, focus on recognising admission control and applying PSA labels (pod-security.kubernetes.io/enforce).
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.

CRDs add new kinds of "forms" to Kubernetes' filing system; an operator is the clerk that acts on those forms.

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.
Exam tip: kubectl api-resources lists every kind available, including CRDs.
5

Services & Networking

Give Pods stable addresses, expose them, control who can talk to whom, and route external traffic.

20%
Practice this domain: drills + timed mock →
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.

A Service is a company phone number that always reaches someone, even as individual employees (Pods) come and go.

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.
Exam tip: kubectl expose deploy/web --port=80 --target-port=8080 creates a Service in one line.
Go deeper in the full lesson →
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.

Cluster DNS is the phone book, look up a service by name and get its current number automatically.

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.
Exam tip: Test resolution from a Pod: kubectl exec pod -- nslookup web.namespace.
Go deeper in the full lesson →
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.

A firewall guest list: only the names on the list (matching labels) get through the door.

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 inbound
Exam tip: An empty podSelector {} selects every Pod, the basis of a namespace default-deny.
Go deeper in the full lesson →
Ingress

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.

Ingress is the building's front-desk receptionist who reads where you want to go (host/path) and directs you to the right office (Service).

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.
Exam tip: kubectl create ingress shop --rule="shop.example.com/*=store:80". Rules point at a Service + port, not Pods.
Go deeper in the full lesson →

Independent study material aligned to the public CKAD curriculum. Not affiliated with or endorsed by The Linux Foundation.