CKAD: build, ship, and diagnose applications
CKAD rewards your ability to turn a requirement into a working application on Kubernetes. Learn the purpose of each object, choose the smallest change that meets the task, and prove the result from inside the cluster.
This guide follows the five published CKAD domains. The certification page currently lists a two-hour, performance-based exam using Kubernetes v1.35. Check it again before your appointment; lab time targets here are practice goals, not official task timings. Official CKAD scope and exam details ↗.
| Domain | Weight | The question you should be able to answer |
|---|---|---|
| Application Design and Build | 20% | What should run, how should it be packaged, and how long should it live? |
| Application Deployment | 20% | How do I release a change and recover from a bad one? |
| Application Observability and Maintenance | 15% | What evidence explains the application's behavior? |
| Application Environment, Configuration and Security | 25% | What configuration, resources, identity, and permissions does it need? |
| Services and Networking | 20% | How does traffic reach the right healthy workload? |
Use Concepts → Playbook → CLI drill. First explain a concept in one sentence. Then practice the commands and checks. Finally solve an exercise without its solution. These are original practice tasks aligned to public objectives, not reproduced exam questions.
Build the command-to-evidence habit
Coach Caz: Make one change, watch one result. Changing six things at once is a circuit class for your debugger.
Use each Practice loop below as a short rehearsal. Read its starting state, predict the result of the first command, then compare that prediction with the evidence. The outputs are illustrative: names, timestamps, addresses, and counts vary. Commands use the example's names; substitute the actual task's namespace, resource, host, and file names when transferring the pattern to a drill.
- Recognize: say which symptom puts you on this path.
- Inspect: run the smallest check that separates two plausible causes.
- Act: change the field or configuration supported by that evidence.
- Prove: repeat the failed operation and check a constraint that must still hold.
- Repeat: hide the commands, change one input, and rebuild the sequence from memory.
Use a disposable practice environment for changes. A case that assumes an installed controller, tool, or prepared resource says so; it does not install those prerequisites for you. Linux host commands belong inside a practice Linux VM or the specified lab node. These examples teach the investigation pattern; use the CLI's assigned task and grader for recorded reps.
1. Application Design and Build — 20%
Explore this section
Choose a controller by the work's lifetimeBuild an image that can actually runCommands, arguments, and the process that receives signalsContainers share a Pod, not a filesystemTell the scheduler where replicas may not share a nodePractice loop: choose a Job and prove that it finishedChoose a controller by the work's lifetime
| Resource | Use it for | Evidence of success |
|---|---|---|
| Pod | A directly managed group of tightly coupled containers | Required containers running and ready |
| Deployment | Replaceable application replicas and rolling releases | Desired replicas available; rollout complete |
| StatefulSet | Stable per-pod identities and associated storage | Expected identities, volumes, and readiness |
| DaemonSet | A copy on each eligible node | Desired and ready counts agree |
| Job | Work that finishes | Required successful completions |
| CronJob | Work that starts on a schedule | Correct schedule and a successful test Job |
Remember: keep serving → Deployment; finish work → Job; schedule work → CronJob. A Deployment replaces exited application containers; it is a poor choice for a batch command intended to finish. StatefulSet identity does not make an application automatically highly available. Workload management ↗.
For Jobs, completions is the success target and parallelism limits concurrent work. restartPolicy: Never prevents container restarts within a Pod; the Job can still create replacement Pods. backoffLimit limits retries and activeDeadlineSeconds limits the Job's active duration. Inspect both status and logs. Jobs ↗.
A CronJob's startingDeadlineSeconds controls late starts, while its Job template's active deadline controls execution duration. concurrencyPolicy: Forbid governs Jobs scheduled by that CronJob, not manual Jobs. suspend: true prevents future scheduling without stopping existing Jobs. Use timeZone: Etc/UTC when UTC is required. CronJobs ↗.
Build an image that can actually run
An image packages the application; a container is a running instance. In a multi-stage Dockerfile, compile in one stage and copy only runtime files into the final stage. COPY adds files, RUN executes build steps, and exec-form ENTRYPOINT starts the program directly. EXPOSE documents a port; it does not publish it. Keep credentials and unnecessary files out of the build context. Docker multi-stage builds ↗, Dockerfile reference ↗.
Build → inspect → load or push → deploy → request. A successful build does not prove the architecture, user, entrypoint, or application response is correct. KubeFit's image exercise loads into kind: the host's Docker images and each node's image store are separate. Use imagePullPolicy: Never only when the image has been loaded into those nodes. Other environments may require a registry and imagePullSecrets. Kubernetes images ↗, kind image loading ↗.
Practice: Build and deploy a container image (kubefit drill ckad-08-container-image-build), Jobs and CronJobs (kubefit drill ckad-09-jobs-cronjobs).
Commands, arguments, and the process that receives signals
Pod command replaces an image's entrypoint; args replaces its default arguments. They are arrays, not an implicit shell command. Pipes, redirects, and && need an explicitly selected shell, and the image must contain it. A minimal image may have no shell at all.
An application running as PID 1 must handle termination correctly. A shell wrapper should forward signals or use exec to replace itself. Prove it: the intended process starts with the correct arguments, emits useful output, and exits within its grace period when the Pod terminates. Commands and arguments ↗ · Pod termination ↗
Containers share a Pod, not a filesystem
Containers in one Pod share networking and can communicate through localhost. They share files only through volumes mounted into each container. A regular init container completes before application containers start. A sidecar supports the application while it runs. Kubernetes also supports native sidecars: entries in initContainers with restartPolicy: Always; these do not prevent a Job from completing. The sidecar exercise explicitly asks for two regular containers. Read the task's lifecycle requirements before choosing a pattern. Init containers ↗, Native sidecars ↗.
emptyDir survives a container restart but disappears when its Pod is removed. A PVC requests persistent storage independently of a particular Pod. Mount the claim with persistentVolumeClaim.claimName and check binding, mount paths, permissions, and actual reads/writes. A volume's access mode is a storage capability, not a substitute for application coordination. Volumes ↗, Persistent volumes and claims ↗.
Practice: Share logs with a sidecar (kubefit drill ckad-01-multi-container-sidecar). Recall cue: same volume name, useful mount paths, correct container selected in logs.
An init container is the right tool when something must exist before the application starts: it runs to completion, in order, and the Pod shows Init:0/1 until it exits 0. Files it writes are visible to the application only through a shared volume, so both containers name the same emptyDir and each mounts it where it needs it. A PersistentVolumeClaim adds a second, durable path: with this fixture's WaitForFirstConsumer class, the claim can remain Pending until scheduling a consumer allows binding, then become Bound. Other StorageClasses can bind immediately. Recognition cues: Init:CrashLoopBackOff means the init container is failing, Init:0/1 that it has not finished; proof is the PVC status, the init container's Completed state, and a request that returns the file the init container generated rather than the image default. Init Container and Volumes (kubefit drill ckad-12-init-container-volumes).
Tell the scheduler where replicas may not share a node
Two replicas on one node are one failure away from zero. podAntiAffinity with requiredDuringSchedulingIgnoredDuringExecution is a hard rule about existing Pods: "do not place me on a node (the kubernetes.io/hostname topology) that already has a Pod matching this labelSelector." A topologySpreadConstraints entry with maxSkew: 1 and whenUnsatisfiable: DoNotSchedule is a rule about the distribution across the topology, and it keeps working past the number of nodes where required anti-affinity would leave replicas Pending. The labelSelector in both must match the Deployment's own Pod labels or the rule matches nothing. Proof of success is kubectl get pods -o wide showing distinct NODE values, and the fields themselves on the template; a Pending Pod with "didn't match pod anti-affinity rules" is the message to recognise when the cluster is too small for the rule. Spread Pods Across Nodes (kubefit drill ckad-20-pod-anti-affinity). Assigning Pods to nodes ↗, Topology spread constraints ↗.
Practice loop: choose a Job and prove that it finished
Starting state: create namespace concepts-ckad on a disposable cluster. This example runs a finite command with no external files.
kubectl create namespace concepts-ckad
kubectl -n concepts-ckad create job count --image=busybox:1.36 -- sh -c 'for n in 1 2 3; do echo "rep $n"; done'
kubectl -n concepts-ckad wait --for=condition=Complete job/count --timeout=90s
kubectl -n concepts-ckad logs job/count
kubectl -n concepts-ckad get job count
Look for: rep 1, rep 2, rep 3, and 1/1 completions. A successful process is expected to exit. Wrapping it in a Deployment would make that exit look like a restart problem.
If it fails: find the Job's Pod with kubectl -n concepts-ckad get pods -l job-name=count, then describe that Pod. ErrImagePull means the command never ran; a nonzero exit with application output means it did. Read all relevant attempts when the Job has retried, not just the last Pod you noticed.
Act: repair the image or command according to the evidence. A Job's Pod template is generally immutable; in this throwaway example delete and recreate the failed Job rather than trying to edit its command in place.
Repeat: make the command exit 1. Predict the difference between Pod restart policy and Job retry behavior before looking at the resulting Pods. Clean up with kubectl -n concepts-ckad delete job count.
Coach Caz: Three reps, then stop. A Deployment that keeps restarting your finished Job is the trainer who never learned to count. Jobs ↗
2. Application Deployment — 20%
Explore this section
A rollout changes a Pod templatePractice loop: observe a rollout before choosing a rollbackSeparate release identity from routingRender configuration before applying itHelm: release settings survive only when you preserve themKustomize: render the overlay, preserve the basePractice loop: inspect the values that Helm will actually useA rollout changes a Pod template
Changing a Deployment's image or template configuration creates a new ReplicaSet. Scaling alone does not create a rollout revision. maxSurge controls extra Pods during an update; maxUnavailable controls how many can be unavailable. Readiness probes help keep unready replicas out of normal Service traffic. maxUnavailable: 0 needs sufficient capacity and suitable probes; it cannot guarantee application-level continuity by itself. A rollback restores a previous Pod template, not external data or database changes. Deployments ↗.
Prove the release with kubectl rollout status, the actual image on Pods, ready EndpointSlices, and an application request. A command returning “configured” proves only that the API accepted a change.
Each template change creates a ReplicaSet with a deployment.kubernetes.io/revision annotation; kubectl rollout history lists them, and the CHANGE-CAUSE column is the kubernetes.io/change-cause annotation copied from the Deployment onto the ReplicaSet. A release that cannot become ready (a tag that does not exist, so ImagePullBackOff) stalls rather than fails: with maxUnavailable: 0 the old replicas keep serving while one surge Pod waits. kubectl rollout undo re-uses the previous ReplicaSet's template and gives it a new, higher revision number, so a history of 1, 3, 4 after one rollback is correct, and the broken ReplicaSet stays in the list at zero replicas. Proof: the current image, readyReplicas, the Progressing condition reason NewReplicaSetAvailable, and no Pod waiting on a pull. Rolling Update and Rollback (kubefit drill ckad-16-rolling-update-rollback).
Practice loop: observe a rollout before choosing a rollback
Starting state: use the example namespace concepts-ckad. This creates a Deployment, then deliberately requests a nonexistent image tag. It does not need a Service.
kubectl -n concepts-ckad create deployment web --image=nginx:1.27 --replicas=2
kubectl -n concepts-ckad rollout status deployment/web --timeout=90s
kubectl -n concepts-ckad set image deployment/web nginx=nginx:kubefit-missing-tag
kubectl -n concepts-ckad get pods
kubectl -n concepts-ckad rollout history deployment/web
Look for: new Pods in ErrImagePull or ImagePullBackOff, with old replicas still available while the rollout stalls. Describe a new Pod and read the registry error. Authentication failure and a missing tag can both cause pull failures; the waiting reason alone cannot distinguish them.
Act → prove: once the deliberately bad tag is confirmed, restore the preceding template:
kubectl -n concepts-ckad rollout undo deployment/web
kubectl -n concepts-ckad rollout status deployment/web --timeout=90s
kubectl -n concepts-ckad get deployment web -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}{.status.availableReplicas}{"\n"}'
Expect nginx:1.27 and 2 available replicas. History numbers need not return to their old values: rollback itself changes release history. In a real task, also test the application's response; template recovery alone does not repair external data.
Repeat: hide the commands and explain which evidence would make you fix registry credentials instead of rolling back. Cleanup: kubectl -n concepts-ckad delete deployment web.
Coach Caz: “Configured” is signing into the gym. rollout status tells you whether anyone actually lifted. Deployment rollouts ↗
Separate release identity from routing
For a simple canary, both Deployments share an application label; a second label identifies the stable or canary track. A Service selecting only the application label includes both. Four ready stable Pods and one ready canary Pod approximate a 4:1 allocation; connection reuse and random selection mean 50 requests need not split exactly 40:10.
For blue/green, keep two versions available and change the Service selector to the desired track. Existing connections may persist during the switch. Plan capacity and rollback before directing traffic. These are practice patterns built from Deployments and Services; labels themselves do not assign traffic weights. Services and selectors ↗.
Practice: Release a canary (kubefit drill ckad-05-canary). Prove replicas + selection + both responses, not just the Deployment's existence. For the cut-over pattern, Blue/Green Service Switch (kubefit drill ckad-13-blue-green): bring the green Deployment to full readiness first, then change one selector field on the existing Service (a patch or kubectl set selector, never delete and recreate), and keep blue running for an instant rollback. Proof is the selector, the ready endpoint addresses being exactly the green Pod IPs, and repeated in-cluster requests all answering green.
Render configuration before applying it
Helm and Kustomize both produce Kubernetes resources, but they manage inputs and lifecycle differently. Inspect the rendered result before asking the API server to accept it.
Helm: release settings survive only when you preserve them
Helm installs a chart as a named release. Inspect chart values, supply explicit overrides, then check release status and workloads. Know helm show values, template, upgrade --install, history, and rollback. Chart version and application image version are separate choices. Helm usage ↗.
Helm stores supplied values with each revision. Upgrade value handling depends on the flags and whether new values are supplied; preserve the complete intended configuration explicitly or deliberately use --reuse-values. Inspect helm get values before and after the change. A failed upgrade still consumes a revision. Proof of a Helm task is helm list (status and revision), helm history (one install, one upgrade), helm get values, and the rendered Deployment carrying app.kubernetes.io/managed-by: Helm; direct kubectl changes are not recorded as Helm values and may be overwritten by a later release change. Helm Install and Upgrade (kubefit drill ckad-10-helm-release).
Kustomize: render the overlay, preserve the base
Kustomize transforms a base into environment-specific resources. An overlay can change namespace, prefix, image tags, replicas, labels, and configuration without editing the base. kubectl kustomize DIR renders locally; kubectl apply -k DIR persists the result. Generated ConfigMaps normally get a content hash, and recognized references are rewritten. Check the rendered name and envFrom reference instead of guessing the suffix. Kustomize ↗.
Practice: Build a production overlay (kubefit drill ckad-06-kustomize).
Practice loop: inspect the values that Helm will actually use
Starting state: a practice release web exists in namespace concepts-helm; its chart is in a provided directory ./web-chart. The chart documents a replicaCount value. These commands require that chart and release; the path is an example input, not a file shipped with this page.
helm list -n concepts-helm
helm get values web -n concepts-helm -o yaml
helm show values ./web-chart
helm history web -n concepts-helm
Look for: the difference between supplied values and chart defaults. Suppose the release currently has a custom image tag and two replicas. Changing only replicas must not silently discard the intended image configuration.
Act: save the release's non-secret supplied values in this disposable example, edit replicaCount to 3, then render before upgrading. Treat exported values as sensitive if your actual release stores credentials in them.
helm get values web -n concepts-helm -o yaml > practice-values.yaml
# Edit practice-values.yaml: preserve the required values; set replicaCount: 3.
helm template web ./web-chart -n concepts-helm -f practice-values.yaml
helm upgrade web ./web-chart -n concepts-helm -f practice-values.yaml --wait --timeout 2m
helm get values web -n concepts-helm -o yaml
helm status web -n concepts-helm
Prove: the rendered Deployment has three replicas and the intended image; the upgrade becomes ready; live resources match. If replicaCount is not a value used by this chart, adding it may do nothing. Read templates or documentation rather than trusting a plausible key name.
Repeat: name three distinct versions: chart version, image version, release revision. Then perform a deliberate change and identify each in the evidence.
Coach Caz: Copying someone else's values file is borrowing their training plan. Check that you have the same equipment. Helm upgrade options ↗
3. Application Observability and Maintenance — 15%
Three probes answer three different questions
| Probe | Question | Effect after its failure threshold |
|---|---|---|
| Startup | Has this container finished starting? | Container is terminated; restart follows its policy |
| Readiness | Can this container accept traffic now? | Pod becomes unready for normal Service routing |
| Liveness | Is this container stuck and needs recovery? | Container is terminated; restart follows its policy |
While startup has not succeeded, liveness and readiness checks wait. Readiness failure alone does not restart the container. Use a startup budget suitable for boot time; avoid a liveness check that restarts every replica merely because a shared dependency is temporarily unavailable. Check the action, path, port, delays, periods, and thresholds independently. Configure probes ↗.
Practice: Configure health probes (kubefit drill ckad-02-probes).
Diagnose from evidence
Describe → events → logs → runtime test. A Pod can be Running while an application container is unready. CrashLoopBackOff describes repeated restart backoff, not the underlying cause. Inspect termination reason, exit code, and logs --previous. In multi-container Pods, choose -c NAME. Use exec when the image has the needed tool; ephemeral debugging containers can help with minimal images when permissions allow. kubectl top requires a working metrics API and reports usage rather than resource requests. Debug Pods ↗, Debug running Pods ↗, Resource metrics pipeline ↗.
The status column tells you which layer to read. CreateContainerConfigError means the kubelet could not assemble the container: a missing ConfigMap or Secret key, named in the events. Fixing that can reveal a second fault, because a container that never started was never probed; a container that starts and is then killed every few seconds by a liveness probe on the wrong port logs a clean shutdown (SIGQUIT) in logs --previous, and the events say Liveness probe failed. Fix the probe's target, not the probe's existence. Proof: the Pod Ready, the value visible with printenv inside the container, and a restart count that is the same in two samples ten seconds apart. Fix a Crashing Deployment (kubefit drill ckad-15-crashloop-configmap).
OOMKilled is evidence of an out-of-memory termination. Exit code 137 commonly represents SIGKILL, but the number alone does not prove an out-of-memory kill. Inspect container limits, node memory pressure, termination details, and usage before deciding whether the application needs more memory or has a leak. The evidence is in kubectl describe pod under Last State (Reason: OOMKilled), and the fix is on the Deployment's resources: a limit that fits the real working set and a request that is a realistic floor for the scheduler, set with kubectl set resources or a patch. A fresh Pod after the rollout starts at restart count 0 with an empty Last State; removing the limit also stops the kills but is not the requested state. Diagnose an OOMKilled Container (kubefit drill ckad-18-oom-limits).
Debug a minimal image without changing its workload
Use kubectl logs POD -c CONTAINER --previous for the prior crashed instance; use current logs for the current one. Events explain image pulls, mounts, scheduling, and probe failures. A missing shell is an image property, not proof that the application is broken.
An ephemeral container added with kubectl debug can supply diagnostic tools when the cluster and your permissions allow it. Its process visibility depends on runtime support and targeting; it does not automatically inherit the application's filesystem mounts or credentials. A copied debug Pod is a separate workload and may have different traffic or storage behavior. Prove it: distinguish what was observed in the original container from what was only reproduced in a copy. Debug running Pods ↗
Migrate the schema as well as the version
Use kubectl api-resources, api-versions, and explain against the target cluster. For old Ingress manifests, changing to networking.k8s.io/v1 also requires the current backend structure and pathType. CronJob uses batch/v1; PodDisruptionBudget uses policy/v1. Preserve selectors carefully: an empty PDB selector in v1 selects all Pods in its namespace, unlike the old beta behavior. Server-side dry-run validates the new shape before persistence. API migration guide ↗.
Practice: Migrate removed APIs (kubefit drill ckad-07-api-deprecations). This exercise validates migration; the separate Ingress exercise verifies real HTTP routing.
Practice loop: Running does not mean ready for traffic
Starting state: Deployment web in concepts-ckad has one container named web, listening on port 8080. Its documented health path is /healthz, but readiness incorrectly requests /ready. Use a prepared practice workload with those properties.
kubectl -n concepts-ckad get pods
kubectl -n concepts-ckad describe pod WEB_POD
kubectl -n concepts-ckad get deployment web -o yaml
kubectl -n concepts-ckad logs WEB_POD -c web --tail=30
Replace WEB_POD with the observed name. Expected clues: 0/1 Running, Readiness probe failed ... 404, and no repeated container terminations. That is different from a liveness failure that restarts the container or a process that crashes on its own.
Act: fix the documented path in the controller template, preserving the required probe:
kubectl -n concepts-ckad patch deployment web --type=strategic -p '{"spec":{"template":{"spec":{"containers":[{"name":"web","readinessProbe":{"httpGet":{"path":"/healthz","port":8080}}}]}}}}'
kubectl -n concepts-ckad rollout status deployment/web --timeout=90s
kubectl -n concepts-ckad get pods
kubectl -n concepts-ckad get endpointslices -l kubernetes.io/service-name=web -o yaml
Prove: the replacement is Ready; if a matching Service web exists, its endpoint is ready too. Test a real application request from the intended client. If the probe reports connection refused rather than 404, check the listener and port before editing a path.
Repeat: change only the probe port in a fresh rehearsal. Use the failure message to decide whether to inspect path, port, or startup budget.
Coach Caz: A green Pod is a pulse, not a fitness test. Check readiness, then make the request. Probe behavior ↗
4. Application Environment, Configuration and Security — 25%
Configuration belongs outside the image
Use a ConfigMap for ordinary configuration and a Secret for sensitive values. envFrom imports a source's keys; env[].valueFrom maps a specific key to a named variable. Volume projections provide files. Environment variables do not change in existing containers when their source changes. Ordinary projected ConfigMap volumes update eventually, but subPath mounts do not receive those updates; the application must also reload the file. ConfigMaps ↗.
A whole-ConfigMap volume is updated in place: the kubelet writes a new timestamped directory and swaps the ..data symlink, so after its configured sync and cache propagation delay a running container reads the new file with no restart. Two consumers never update: a subPath mount, which binds one file and bypasses the symlink, and an environment variable, which the kubelet resolves once when it creates the container. The proof pattern is therefore two-sided: cat the mounted file and printenv the variable in the same container, watch the file change while the variable does not, then kubectl rollout restart so a new container picks up the variable. ConfigMap Volume Live Reload (kubefit drill ckad-19-configmap-volume-reload).
Secret data is base64-encoded, which is not encryption. Protect access and avoid printing values during diagnosis; verifying a key or mounted file exists is often enough. Use only the supplied dummy values in this practice environment. Secret practices ↗.
Requests, limits, and quotas act at different levels
Request: schedule it. Limit: constrain it. Quota: admit it. CPU requests use units such as 100m; memory uses quantities such as 64Mi. CPU limits can throttle; memory limit breaches can lead to OOM termination. If the scheduler cannot satisfy requests, inspect a Pending Pod's events. Container resources ↗.
A ResourceQuota caps namespace consumption or object counts. A LimitRange can set defaults and per-object bounds. Required resource declarations depend on the quota's keys: a request quota and a limit quota are not interchangeable. Rejected Pod creation may appear on the ReplicaSet because no Pod was admitted. ResourceQuota ↗, LimitRange ↗.
Practice: Configuration, Secrets, and resource controls (kubefit drill ckad-03-config-secrets-resources).
Identity, permission, and process privileges are separate
Authentication establishes identity; authorization decides permitted operations; admission checks or modifies a request before storage. A ServiceAccount is a workload identity. Assign it with serviceAccountName; use Roles and bindings for necessary API permissions. Do not assume choosing a ServiceAccount grants access. Modern Pods normally use projected, time-limited tokens rather than automatically generated long-lived Secret tokens. ServiceAccounts ↗, API access control ↗, RBAC ↗.
The least-privilege shape is a ServiceAccount, a namespaced Role that lists only the verbs and resources needed (get, list on configmaps), a RoleBinding joining them, and a Deployment that runs as that account; automountServiceAccountToken: false on the Pod spec keeps the token out of a container that never calls the API. Proof is kubectl auth can-i ... --as=system:serviceaccount:NAMESPACE:NAME, answering yes for the granted verbs in that namespace and no for other verbs, other resources, and the same resource in another namespace, plus the absence of /var/run/secrets/kubernetes.io/serviceaccount/token inside the container. ServiceAccount and Role Binding (kubefit drill ckad-11-service-account-rbac).
A security context controls process behavior: user/group IDs, privilege escalation, capabilities, filesystem access, and seccomp. Pod-level and container-level fields have different scopes. runAsNonRoot must match the image's user; a read-only root filesystem needs writable volumes for paths the application writes. Test the process after hardening it. Security contexts ↗.
The full hardened set is runAsUser/runAsGroup/runAsNonRoot, readOnlyRootFilesystem: true, allowPrivilegeEscalation: false, capabilities.drop: [ALL], and seccompProfile.type: RuntimeDefault; the first three and seccomp are valid at Pod or container level, the rest are container-only. The recognition cue for a read-only root filesystem is a crash loop whose log says it cannot open or write a pid, cache, or temp path; the fix is an emptyDir mounted there, not removing the restriction. Proof: id -u inside the container prints the requested UID, a write to / fails, the application still answers on its port, and the restart count stops climbing. Hardened Security Context (kubefit drill ckad-14-security-context).
Discover a custom resource before creating an instance
A CRD adds an API resource type. An operator reconciles resources into application behavior. Discover the installed type and inspect its schema before creating a custom resource; accepting an object does not prove an operator is installed or healthy. Custom resources ↗, Operator pattern ↗.
Use kubectl api-resources to find the resource's name, API group, and scope; use kubectl explain RESOURCE --api-version=GROUP/VERSION for its served schema. Replace the uppercase placeholders with the installed type. Check required fields, then use server-side dry-run on your manifest before creation. If the type is absent, applying an instance cannot install it.
Prove it: the instance is accepted, the responsible controller is healthy, and its status and created resources show the requested outcome. A stored custom resource with no controller is unfinished work. This is a concepts workflow; the current CKAD pack does not contain an operator-installation drill.
Practice loop: trace a missing configuration key
Starting state: Deployment web in concepts-ckad expects environment variable APP_MODE from key mode in ConfigMap web-settings. The ConfigMap mistakenly contains app-mode. The application has a shell for the verification command.
kubectl -n concepts-ckad get pods
kubectl -n concepts-ckad describe pod WEB_POD
kubectl -n concepts-ckad get configmap web-settings -o yaml
kubectl -n concepts-ckad get deployment web -o yaml
Look for: CreateContainerConfigError and an event naming the missing key. The chain is environment name → source object → source key. APP_MODE, web-settings, and mode serve different purposes; they do not all need the same name.
Act: if the task contract really requires key mode, add it without deleting unrelated keys:
kubectl -n concepts-ckad patch configmap web-settings --type=merge -p '{"data":{"mode":"practice"}}'
kubectl -n concepts-ckad rollout status deployment/web --timeout=90s
kubectl -n concepts-ckad exec deployment/web -- printenv APP_MODE
Prove: the container starts and prints the non-sensitive value practice. A container that never started can retry after the source is repaired. An already running container will not refresh its environment when you edit a ConfigMap; replace it through the controller when required. File projections have a different refresh path.
Repeat: sketch the same chain for a Secret without printing its value. Then explain why a missing key belongs in events rather than application logs.
Coach Caz: Read the label on the bottle before adding more pre-workout. Variable name and ConfigMap key are not interchangeable. ConfigMap consumers ↗
5. Services and Networking — 20%
Follow traffic one boundary at a time
Client → Service port → ready endpoint → target port → listening process. Compare the Service selector with Pod labels, then inspect EndpointSlices. A declared containerPort does not start a listener. ClusterIP serves cluster-internal clients; NodePort exposes a node port; LoadBalancer needs an implementation. DNS names include namespace, so a same-name Service in another namespace is a different destination. Services ↗, Service debugging ↗.
Ingress routes HTTP(S) by host and path through an installed controller. Match ingressClassName, backend Service name and Service port, and pathType. Prefix matches path segments: /catalog matches /catalog/items, not /catalogue. A host-scoped test needs the correct Host header. Ingress ↗.
Practice: Route traffic with Ingress (kubefit drill ckad-04-ingress). The drill installs a maintained NGINX controller with class nginx; use the supplied internal controller Service to test traffic.
NetworkPolicy selects and allows
A policy selects Pods in its own namespace. Once a Pod is isolated for a direction, only allowed traffic in that direction passes. Policies are additive; a connection must satisfy both source egress and destination ingress when both are isolated. A namespaceSelector and podSelector in the same peer mean both must match; separate peer entries are alternatives. Plan DNS egress as well as application traffic. A CNI that enforces NetworkPolicy is required. NetworkPolicy ↗.
Reading a policy is a checklist: which Pods it selects (podSelector, where {} means every Pod in the namespace), which directions it isolates (policyTypes, which is inferred from the rules present unless you write it, and the only way to get [Egress] alone is to write it), and for each rule the peers and ports. A peer with a podSelector alone means "Pods in this namespace"; a namespaceSelector and podSelector under the same - mean both must match, and the automatic label kubernetes.io/metadata.name is how you name a namespace. A default-deny egress policy must allow DNS (UDP and TCP 53 to kube-system) or nothing else you allow later will resolve. Proof on an enforcing CNI is a request from an allowed source and a denied one; on kind's default CNI, which does not enforce policies, the proof is the spec itself, read field by field with kubectl describe networkpolicy. NetworkPolicy Ingress and Egress (kubefit drill ckad-17-network-policy-spec) grades exactly that and says so in the task.
The CLI drills run on kind's default networking and do not assess NetworkPolicy enforcement. Use a policy-capable practice cluster for allow/deny tests; successfully creating a policy object is insufficient proof.
Practice loop: follow port numbers all the way to the process
Starting state: Service web in concepts-ckad exposes port 80; its selected, ready Pods actually listen on 8080. A prepared client Pod named client has wget. The Service incorrectly uses numeric targetPort: 80.
kubectl -n concepts-ckad get service web -o yaml
kubectl -n concepts-ckad get endpointslices -l kubernetes.io/service-name=web -o yaml
kubectl -n concepts-ckad get pods -l app=web -o wide
kubectl -n concepts-ckad exec client -- wget -qO- -T 5 http://web:80
Look for: ready addresses but EndpointSlice port 80, while the application configuration/logs establish listener 8080. The Service's port is the client-facing door; targetPort is the destination. A containerPort declaration documents a port but does not cause the process to listen there.
Act → prove: for this one-port Service, use a merge patch with the complete intended port entry:
kubectl -n concepts-ckad patch service web --type=merge -p '{"spec":{"ports":[{"name":"http","port":80,"protocol":"TCP","targetPort":8080}]}}'
kubectl -n concepts-ckad get endpointslices -l kubernetes.io/service-name=web -o yaml
kubectl -n concepts-ckad exec client -- wget -qO- -T 5 http://web:80
Wait for the EndpointSlice to show a ready address at port 8080, then expect the application's response through Service port 80. Controllers and the Service data plane update asynchronously; if the first request races that update, recheck the evidence and repeat the same bounded request. Persistent failure still needs diagnosis. This patch replaces the port list; do not copy it onto a multi-port Service without preserving its other entries. If direct Pod-IP requests to port 8080 also fail, inspect listener, application health, and policy before blaming Service translation.
Repeat: replace the numeric target with a named target and trace that name to the selected Pods. Say client port → Service selection → endpoint port → listener before typing.
Coach Caz: The door says 80. The squat rack is at 8080. Changing the sign does not move the rack. Service ports ↗
Know what you can prove
These guides cover all five domains. The 25 CLI drills assess the topics linked above, including Helm, persistent and ephemeral volumes, RBAC, security contexts, rollbacks, resource limits, and NetworkPolicy specs; additional practice is still needed for operators and custom resources, and for NetworkPolicy behaviour on an enforcing CNI. Use the Playbook to rehearse those workflows, then test yourself: can you explain the choice, make the change, and demonstrate the requested behavior without opening the solution?
Check the exam allowed-resources policy ↗ before your sitting. A useful study reference is not automatically permitted during the exam.
Further applied practice
Combine configuration in a projected volume
A projected volume combines sources such as ConfigMaps, Secrets, Downward API fields, and ServiceAccount tokens under one mount. Each item's path chooses a relative filename; it is not the source key's name unless you make them equal. Required missing sources or keys can prevent startup. Keep token audience and expiry appropriate for the receiving service.
The Downward API exposes selected Pod information, such as its name or labels, without an API client. It does not grant arbitrary reads of cluster objects. Prove it: inspect filenames, permissions, and non-sensitive expected values inside the intended container; verify token presence without printing credentials. Source updates and application reload are separate concerns.
Trace a Service port to its container
A Service port is the client contract; targetPort resolves to a port on selected Pods. A healthy Pod does not prove the Service forwards to its listener. Repair the indirection and then test from the client.
Give a slow application time to start
Startup probes gate the other probes until startup succeeds. Increasing a readiness delay alone cannot stop a startup probe from repeatedly killing a slow process. Preserve the application and correct the probe budget.
configure liveness readiness startup probes ↗ pod lifecycle ↗
Prove the order of graceful shutdown
The preStop hook consumes part of the termination grace period and runs before the application receives TERM. A persistent event log lets you observe that sequence after the application Pod has gone.
Run and verify indexed parallel work
An Indexed Job assigns stable completion indexes to pieces of work. Parallelism controls concurrency; completions controls how many successful pieces are required.