CKAD: Certified Kubernetes Application Developer Study Guide
The Certified Kubernetes Application Developer (CKAD) exam validates your ability to design, build, configure, expose, deploy, observe, and maintain cloud-native applications on Kubernetes. It is a 2-hour, hands-on, performance-based test taken in a live cluster from the command line, aimed at developers and engineers who package and run applications on Kubernetes rather than administer the cluster itself. You need 66% to pass, and speed with kubectl plus comfort editing YAML are essential.
Domain 1: Application Design and Build
- A Job uses spec.completions to set how many successful pod completions are required and spec.parallelism to cap how many pods run concurrently; completions: 5 with parallelism: 2 runs until 5 succeed while never exceeding 2 at once.
- spec.completionMode: Indexed (paired with completions) gives each pod a unique completion index via the JOB_COMPLETION_INDEX env var, useful for partitioning work; the default is NonIndexed.
- A CronJob's --schedule uses the standard 5-field cron syntax (minute hour day-of-month month day-of-week); '*/5 * * * *' means every 5 minutes.
- spec.concurrencyPolicy on a CronJob controls overlap: Allow (default) permits concurrent runs, Forbid skips a new run if the prior Job is still active, and Replace cancels the running Job and starts a new one.
- spec.startingDeadlineSeconds on a CronJob limits how late a missed scheduled run may still start; if the controller cannot start within that window the run is skipped and counted as missed.
- Init containers run sequentially to completion before any app container starts, making them the right place to gate startup on an external dependency (e.g. a loop running 'until nslookup db; do sleep 2; done').
- A native sidecar is declared as an entry in spec.initContainers with restartPolicy: Always; unlike a plain init container it keeps running alongside the app containers for the pod's lifetime.
- lifecycle.postStart runs asynchronously with the container entrypoint and has no ordering guarantee relative to the main process, so it cannot be relied on to complete setup before the app starts.
- Containers in the same pod share a network namespace and reach each other over localhost; they can also share storage by mounting the same volume.
- An emptyDir volume is created empty when the pod is assigned to a node, shares the pod's lifetime, and is the standard way two containers in one pod share a filesystem path; emptyDir.medium: Memory backs it with tmpfs RAM.
- spec.shareProcessNamespace: true lets containers in a pod see each other's processes in a shared PID namespace, useful for debugging or signaling between containers.
- spec.terminationGracePeriodSeconds (default 30) sets how long Kubernetes waits after sending SIGTERM before sending SIGKILL during pod shutdown.
- A kubeadm cluster using containerd does not read the local Docker daemon image store; an image must be pushed to a registry every node can pull from and referenced by its full registry/repository:tag path.
- kubectl create job task --image=busybox -- /bin/sh -c 'echo done' and kubectl create cronjob report --image=busybox --schedule='*/5 * * * *' both support --dry-run=client -o yaml to scaffold a manifest without contacting the API server.
Domain 2: Application Deployment
- kubectl rollout undo deployment/web with no --to-revision reverts to the immediately previous revision; add --to-revision=N to roll back to a specific historical revision.
- In a RollingUpdate, maxUnavailable caps how many pods may be down and maxSurge caps how many extra pods may be created; with 10 replicas, maxUnavailable: 2 keeps at least 8 available and maxSurge: 2 allows at most 12 total during the update.
- strategy.type: Recreate terminates all existing pods before creating any new ones, causing downtime; RollingUpdate (the default) replaces pods gradually with no downtime.
- kubectl set image deployment/web app=myreg/app:v2 updates a named container's image in place and triggers a new rollout and revision.
- kubectl rollout status deployment/web watches and blocks until the rollout completes or fails, returning non-zero on failure, making it ideal for scripting and diagnosis.
- A rollout is marked failed after spec.progressDeadlineSeconds (default 600s) of no progress, surfacing condition Progressing=False with reason ProgressDeadlineExceeded.
- Only changes to the pod template (spec.template), such as an image tag or an env var, create a new revision; scaling replicas does not produce a new revision.
- Record rollout history meaningfully by setting the kubernetes.io/change-cause annotation on the Deployment (via kubectl annotate or in the manifest); the deprecated --record flag is no longer the recommended path.
- spec.revisionHistoryLimit (default 10) controls how many old ReplicaSets are retained for rollback; lowering it reduces clutter but limits how far back you can undo.
- kubectl scale deployment/api --current-replicas=3 --replicas=6 only scales when the current count matches the precondition, guarding against races; kubectl scale deployment/web --replicas=0 fully stops a Deployment without deleting it.
- kubectl autoscale deployment web --min=2 --max=10 --cpu-percent=70 creates a HorizontalPodAutoscaler; HPA requires metrics-server and container resource requests to compute utilization.
- Blue-green cutover is done by editing the Service's spec.selector to match the new (green) pods' labels; a Deployment's own spec.selector is immutable after creation.
- kubectl rollout pause deployment/web batches multiple edits without triggering intermediate rollouts; kubectl rollout resume deployment/web then applies them as a single rollout.
- kubectl apply -k ./overlays/prod applies a Kustomize overlay, and helm install web bitnami/nginx -n prod --create-namespace installs a Helm chart as release 'web', creating the namespace if needed; note kubectl apply relies on the last-applied-configuration annotation that kubectl create does not set.
Domain 3: Application Observability and Maintenance
- A startupProbe protects slow-starting containers: while it runs, the kubelet disables the liveness and readiness probes, so set periodSeconds x failureThreshold to cover the expected warm-up (e.g. ~45s) before liveness can kill the container.
- A livenessProbe restarts the container on failure; a readinessProbe only removes the pod from Service endpoints on failure without restarting it; a startupProbe gates the other two during startup.
- Probe handlers come in three forms: httpGet (path/port), exec (command, success on exit 0, e.g. cat /tmp/healthy), and tcpSocket (connection succeeds, e.g. port 5432).
- failureThreshold (consecutive failures before acting) and periodSeconds (interval between checks) together set how long a probe tolerates failure before restarting or de-registering the pod.
- kubectl logs <pod> --previous (or -p) retrieves logs from the previously terminated container instance, which is essential for diagnosing CrashLoopBackOff.
- kubectl logs web -c app -f streams a specific container's logs in a multi-container pod; -c is required when the pod has more than one container and no default-container annotation; --since=5m and --timestamps narrow and label output.
- kubectl exec -it web -c app -- /bin/sh opens an interactive shell in a running container; kubectl cp web:/var/log/app.log ./app.log -c app copies files in or out of a container.
- kubectl debug -it <pod> --image=busybox --target=app attaches an ephemeral debug container that shares the target's process namespace, letting you inspect a distroless or shell-less container without restarting it.
- An OOMKilled container shows Last State: Terminated, Reason: OOMKilled in kubectl describe pod, meaning it exceeded its memory limit; raise limits.memory or fix the leak.
- kubectl top pod (and kubectl top node) reads live CPU and memory usage from the Metrics API and requires metrics-server to be installed in the cluster.
- kubectl get events -n web --sort-by=.lastTimestamp shows recent cluster events in chronological order, surfacing scheduling failures, image pull errors, and probe failures.
- kubectl get pods -w (watch) streams pod state changes live; kubectl wait --for=condition=Ready pod/web --timeout=60s blocks until a condition is met or times out.
- jsonpath extracts raw fields for scripting: kubectl get pod web -o jsonpath='{.status.podIP}' returns the pod IP and '{.status.phase}' returns the lifecycle phase (Pending, Running, Succeeded, Failed, Unknown).
- A pod can be Running but show 0/1 READY when its readiness probe is failing, which excludes it from Service endpoints even though the container has not crashed; custom-columns and -o wide help map pods to nodes.
Domain 4: Application Environment, Configuration and Security
- envFrom with configMapRef (or secretRef) imports every key in the source as environment variables; valueFrom.configMapKeyRef (or secretKeyRef) with name and key selects a single entry, and an explicit env value overrides envFrom for the same key.
- kubectl create secret generic db --from-literal=password=s3cr3t creates a Secret; in a manifest, data values must be base64-encoded while stringData accepts plain text that the API server encodes for you.
- kubectl create configmap appcfg --from-file=app.conf builds a ConfigMap from a file; setting immutable: true on a ConfigMap or Secret blocks updates and improves performance but requires recreation to change.
- A configMap volume with an items list projects only the selected keys to chosen relative paths; use volumeMount.subPath (e.g. subPath: nginx.conf with mountPath: /etc/nginx/nginx.conf) to mount a single file without hiding the rest of the directory.
- A projected volume can combine configMap, secret, and downwardAPI sources under one mount; the Downward API via valueFrom.fieldRef also exposes pod metadata (name, namespace, labels, IP) as environment variables.
- securityContext.runAsUser: 1000 forces a non-root UID and allowPrivilegeEscalation: false blocks gaining extra privileges; with runAsNonRoot: true the kubelet refuses to start a container whose image would run as root, erroring 'container has runAsNonRoot and image will run as root'.
- securityContext.fsGroup: 2000 sets supplemental group ownership on mounted volumes so all containers in the pod can read and write shared files.
- spec.serviceAccountName: deployer sets the pod's ServiceAccount (a pod-level field, not per-container); automountServiceAccountToken: false prevents mounting the token when the pod makes no API calls.
- Resource requests drive scheduling and limits cap usage; cpu is measured in millicores where 500m equals 0.5 of a core, and a pod is unschedulable when the summed container requests exceed every node's allocatable capacity.
- A ResourceQuota caps total resource consumption in a namespace; pairing it with a LimitRange that defines default and defaultRequest values lets existing pods without explicit requests satisfy the quota automatically.
- nodeAffinity requiredDuringSchedulingIgnoredDuringExecution with a matchExpressions like disktype=ssd hard-constrains where a pod may schedule; the 'preferred' variant is a soft preference.
- Create a docker-registry Secret (kubectl create secret docker-registry) and reference it in spec.imagePullSecrets so the kubelet can pull from a private registry.
- Secrets are stored base64-encoded (not encrypted by default) in etcd; treat base64 as encoding, not security, and rely on RBAC and encryption-at-rest for real protection.
- RBAC authorizes via Roles/ClusterRoles bound by RoleBindings/ClusterRoleBindings to the pod's ServiceAccount; a pod can only perform API actions its ServiceAccount is granted.
Domain 5: Services and Networking
- kubectl expose deployment web --port=80 --target-port=8080 creates a Service where --port is the ClusterIP listening port and --target-port is the pod port traffic is forwarded to; type defaults to ClusterIP.
- Service types: ClusterIP (internal virtual IP, default), NodePort (a port in 30000-32767 on every node, reachable at http://<node-IP>:<nodePort>), LoadBalancer (provisions an external LB), and ExternalName (a CNAME alias to spec.externalName like db.corp.local with no proxying).
- CoreDNS gives every Service the FQDN <service>.<namespace>.svc.<cluster-domain>; a Service 'db' in namespace 'data' resolves to db.data.svc.cluster.local, with 'svc' always following the namespace.
- A headless Service (clusterIP: None) creates a DNS A record per ready pod instead of one virtual IP; used as a StatefulSet's serviceName it yields stable names like <pod>.<service>.<namespace>.svc.cluster.local.
- A Service with no selector plus a manually created Endpoints/EndpointSlice object lets you route to fixed external IPs and ports; a Service whose selector does not match any Ready pod labels has empty endpoints and refuses connections.
- When a Service exposes multiple ports, each entry in spec.ports must have a unique name; targetPort can reference a named container port (e.g. targetPort: http) instead of a number.
- spec.sessionAffinity: ClientIP pins requests from the same client IP to the same backend pod; the default None load-balances roughly evenly across all matching endpoints (about 10% each across 10 pods).
- spec.externalTrafficPolicy: Local preserves the original client source IP on NodePort/LoadBalancer Services but only routes to pods on the receiving node; Cluster (default) load-balances cluster-wide but SNATs the client IP.
- When no NetworkPolicy selects a pod, all ingress and egress is allowed (non-isolated); applying any policy that selects a pod switches it to default-deny for that direction, and policies are additive (union of allowed sources).
- A NetworkPolicy's top-level spec.podSelector chooses which pods the policy governs, while spec.ingress[].from[] lists allowed sources; a podSelector and namespaceSelector in the same 'from' entry are ANDed, but separate entries are ORed.
- In a networking.k8s.io/v1 Ingress, a rule backend uses the structured form backend.service.name with backend.service.port.number (or .name), replacing the old flat serviceName/servicePort fields.
- Ingress pathType prefix with path: /app matches /app and all subpaths; spec.defaultBackend provides a fallback Service for requests that match no rule.
- Ingress TLS is configured via spec.tls listing hosts and the secretName of a kubernetes.io/tls Secret (e.g. hosts: [shop.example.com], secretName: shop-tls).
- kubectl port-forward pod/web 8080:8080 tunnels a local port through the API server to a pod for debugging, requiring no Service; the syntax is local-port:remote-port.
CKAD exam tips
- Set up your shell first: alias k=kubectl, export do='--dry-run=client -o yaml', and learn export now='--force --grace-period=0' so you can scaffold manifests fast and delete pods instantly.
- Generate YAML imperatively instead of writing from scratch: 'k run', 'k create', and 'k expose' with $do produce a skeleton you edit, which is far faster than memorizing full manifests.
- Always confirm and switch context with 'kubectl config use-context' and use the right namespace (-n) for every task; many questions live in non-default namespaces and points are lost for acting in the wrong one.
- Use 'kubectl explain <resource>.<field> --recursive' to recall exact field paths and spelling during the exam since you cannot rely on memory under time pressure, and bookmark the official kubernetes.io docs allowed in the browser.
- Budget your time: skip and flag hard questions, do high-weight Configuration and Security and Networking items confidently, and verify each answer with 'kubectl get', 'describe', and 'logs' before moving on.
Study guide FAQ
Is the CKAD exam hands-on or multiple choice?
It is entirely hands-on and performance-based. You solve 15-20 tasks in a live, multi-cluster environment from a terminal within 2 hours, and you are scored on the actual cluster state you produce, not on selecting answers.
What score do I need to pass and how long is the exam?
You need a score of 66 (66%) to pass. The exam lasts 120 minutes, and your certification is valid for 2 years. The exam fee typically includes one free retake.
Can I use the documentation during the exam?
Yes. You may open the official Kubernetes documentation at kubernetes.io (including the blog) in a single additional browser tab. No other sites, personal notes, or bookmarks are allowed, so practice navigating the docs quickly beforehand.
How is CKAD different from CKA?
CKAD targets application developers who build, deploy, and observe workloads on Kubernetes, focusing on pods, deployments, configuration, and networking from a user's perspective. CKA targets administrators and covers cluster installation, etcd, control-plane maintenance, and troubleshooting the cluster itself.