DEV Community

Tolga Akkiraz
Tolga Akkiraz

Posted on

How I Passed the CKAD Exam with 99%

This post doesn't include any real exam questions or official exam content — it's a personal account of how I prepared, what I got wrong, and how I fixed it, with Claude as a study partner along the way.

I didn't pass CKAD with 99% by studying everything more thoroughly. I passed by finding the Kubernetes mistakes I kept repeating under pressure — and fixing them one by one.

Most of the applications I work on run on Kubernetes, and as a developer, understanding how those applications actually behave in the cluster matters more than I used to think.

I came to Kubernetes relatively late. My previous project deployed things via USB stick with huge configuration YAMLs and endless debugging sessions — then I moved to a project running on Kubernetes with proper GitOps, and it felt like a different industry. A Kubernetes workshop at DevOpsCon gave me my first real mental model: control plane, worker nodes, kubelet, API server, etcd, controllers watching state and reacting to changes.

A DevOps engineer at my company suggested CKAD as the natural next step for developers working with Kubernetes daily. I agreed, and signed up for the KodeKloud course on Udemy. I got through about 40% of it before taking a break.

My son was born, and I discovered there are more complex things in life than Kubernetes.

About a year later, I blocked off one full week and focused entirely on the exam.

The learning path

I finished the KodeKloud Udemy course and its mock exams, then moved into hands-on practice on Docker Desktop's built-in Kubernetes cluster. I had Claude generate small drills covering different areas of the curriculum, and worked through them one by one. This is where the real mistakes started — and where the actual learning happened.

Reading the Kubernetes docs alone never really stuck for me — the concepts only clicked once I broke something and had to figure out why. Practice is also how you actually learn to use the docs quickly, which matters more in the exam than knowing them by heart. Whenever I got something wrong, I asked Claude to explain the mistake rather than just hand me the fix. Understanding why something behaves a certain way generalizes; memorizing one correct answer doesn't. That's the difference between recognizing a pattern you've actually drilled and freezing the moment the exam phrases the same problem slightly differently.

The silent failure trap

One of the first things that broke was shell syntax inside command:

command: ["while true; do date; sleep $TIME_FREQ; done >> /opt/time/time-check.log"]
Enter fullscreen mode Exit fullscreen mode

This fails silently in a confusing way: shell features like loops, pipes, redirects, and variable expansion don't work unless you wrap them in /bin/sh -c. Without the wrapper, the whole string just gets interpreted as a binary name to execute. The fix:

command: ["/bin/sh", "-c", "while true; do date; sleep $TIME_FREQ; done >> /opt/time/time-check.log"]
Enter fullscreen mode Exit fullscreen mode

fsGroup was another one that stopped me cold. I'd set fsGroup: 0400 on a pod, thinking it would set file permissions on a mounted Secret volume — the same way chmod would. It didn't, and the real difference is easy to lose track of mid-exam:

Field Purpose Common mistake
fsGroup Sets the GID that owns files on a mounted volume (pod-level) Reaching for it when you actually want file permissions
defaultMode Sets the actual permission bits (volume-level) Not realizing this is where permission bits actually belong

fsGroup is a group ID, not octal permissions, so 0400 was read as the literal GID 256, which showed up right there in the ls -la output once I looked closely. Untangling the mistake meant relearning the actual permission values properly:

  • 4 = read
  • 2 = write
  • 1 = execute

So 6 is read+write, 7 is read+write+execute — exactly what defaultMode expects. I'd never touched Linux security internals like this before, and mixing up "a number that looks like permissions" with "a number that's actually a GID" is an easy trap if you haven't.

I also had an outdated mental model for sidecars. For a long time I treated a sidecar as just another regular container in the pod — which is still the classic sidecar pattern and perfectly valid. What I hadn't internalized is that Kubernetes now also supports native sidecar containers: defined under initContainers with restartPolicy: Always, which gives them different startup and lifecycle behavior. They start before the main app container but keep running for the pod's whole lifetime, instead of running once and exiting like a normal init container. The Kubernetes docs have a good walkthrough of this if you search for "sidecar containers."

The reflex errors

I also kept making reflex mistakes under pressure — like typing --field-selector app=... when I meant -l app=... for labels. The same happened with kubectl run for quick connectivity tests: adding -it when there's no interactive terminal to attach to just hangs the command, where plain -i runs it and returns cleanly. Neither mistake was about the concepts being hard — they were easy to mix up when moving fast. I also ended up learning only the Kustomize basics and going much deeper on image and container fundamentals instead, since the curriculum weights those more heavily — less a plan than something that happened naturally as I prioritized whatever kept showing up in practice.

The logic breakdowns

killer.sh, round one: 86/113. This first attempt is where things got concrete. A few standouts:

StorageClass defaults: I explicitly set storageClassName: "" on a PVC, assuming an empty string meant "no preference, use the default." It's the opposite: leaving the field off entirely gets you the default StorageClass, while setting it to "" explicitly opts out of dynamic provisioning altogether. An empty string isn't the same as an absent field, and that distinction is exactly what cost me points here.

NetworkPolicy AND/OR: I got this wrong in a way that's worth showing directly, because the fix is tiny but the consequence is completely different behavior:

# What I wrote (this is OR, not AND):
ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          namespace: watermelon
    - podSelector:              # separate "-" = OR
        matchLabels:
          app: api
Enter fullscreen mode Exit fullscreen mode
# What it should have been (AND):
ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          namespace: watermelon
      podSelector:              # no "-" = AND, same list item
        matchLabels:
          app: api
Enter fullscreen mode Exit fullscreen mode

Crucial rule: separate dashes (-) create OR logic. Nested keys in the same block create AND logic.

One dash. Completely different network policy. The same logic bit me again on an egress rule later — combining podSelector and ports in a single block accidentally blocked DNS lookups to kube-dns, because kube-dns doesn't carry the label the selector required. That one needs two separate egress rules, not one combined block.

Time management: I lost real points to time pressure alone — about 20 points across the last few questions. My fix afterward was blunt but effective: get through the first 15 questions in under 60 minutes, no exceptions, so the harder ones at the end still have room.

I built a custom mock exam afterward targeting exactly these gaps, using Claude Code — feeding it my mistakes, having it generate timed drills on the specific weak spots, and building a full mock with sample solutions to check myself against. The storageClassName mistake got its own targeted drill, and I got it right the second time around. I kept the cheat sheet updated the whole time with whatever I'd just gotten wrong.

killer.sh, round two: 110/113, with 14 minutes to spare. Worth knowing going in: killer.sh's two sessions use the same question set, so round two is also partly a memory test, not a fully independent measurement. Still, the gap — 86 to 110 — tracks directly with the list above. Fixing the specific things I knew were broken is what closed it, not just familiarity with the questions.

The day before the real exam, I went through my cheat sheet once and spent the rest of the time with friends and family.

I passed with 99%, 40 minutes left on the clock — the same margin I'd been chasing since that first 86/113.

General exam tips

A few smaller things that made a real difference on exam day itself, separate from the Kubernetes content:

  • Get comfortable with vim basics beforehand — it directly affects your speed. No need for a custom .vimrc; basic movement, insert mode, indentation, and saving/exiting cleanly is enough. I had a head start from using neovim daily for JS/TS work and the vim plugin in IntelliJ for JVM projects.
  • Use Ctrl + R in the terminal to search command history — I used this constantly.
  • Each exam question connects you to its own separate SSH host with no persistent environment, so instead of setting up aliases fresh every time, just open a new file in VSCodium and collect useful commands there to copy-paste from.
  • For special characters your keyboard layout doesn't produce, stage them via the virtual keyboard first — I had trouble with | and ~.
  • Time isn't tight if you manage it well — I had 40 minutes left at the end. It only gets tight if you get properly stuck on one question.

Reference repos I used throughout:

  • CKAD-exercises — practice tasks sorted by topic, useful for drilling one concept area at a time instead of full mock exams
  • CKAD-2026 — very close to the real exam format, good for getting used to the time pressure and task style

None of the individual mistakes above were hard. What mattered was that they were the exact mistakes I made under pressure. The improvement came from identifying those weak spots early and fixing them one by one.

Looking back, the learning loop that helped me most felt a lot like a Kubernetes reconciliation loop: identify the current state, compare it to the state you want, take corrective action, and keep repeating until the gap closes.

Learning loop: desired state, observed gap, targeted drill, retest, repeat

If there's interest, I can publish the cheat sheet and the drill format I used with Claude in a follow-up post. I won't share anything about specific exam questions, but I'm happy to share the study process.

Top comments (0)