How I Passed the CKAD Exam

Updated 12/2020

Last year, I decided that since I’d been working on pushing my employer to embrace a Cloud-native mentality that I should lead by example and get certified. We had chosen Kubernetes as our base container management platform and ultimately inked a deal with Red Hat to roll out OpenShift throughout the enterprise.

If you’ve read the blog for any time, you’ll remember that I was originally pursuing RHEL certification. At the time, even though I was in a development role, I was finding myself needing more and more Linux skills. However, my role transitioned to a dedicated focus on Cloud technologies. I abandoned RHEL and began focusing on Kubernetes. However, it wasn’t until about November of this year that I really buckled down to prepare.

In the spirit of openness, I’ll confess that I’ve had the benefit of spending the past several months working in a dedicated fashion deploying multiple OpenShift clusters. This has involved a great deal of tweaking, testing, and troubleshooting which has provided invaluable real-world, hands-on experience working with the Kubernetes system. Now, with that out of the way, I thought I’d share my personal take on how I prepared…and passed…the CKAD exam.


I’ve been a Linux Academy member since August of 2015. I’ve been very pleased with the vast selection of training courses they offer, but one of the greatest benefits in my opinion is the Cloud Playground. It’s changed a little since it was first offered, but now you have credits you can use to build infrastructure. You use the credits to build your systems out based on the requirements you need. For the CKAD exam, I built out three Ubuntu servers at a cost of three credits each. This gave me one master and two worker nodes which was perfectly suited for a solid training environment.

Training Courses

I took three different courses towards completing this certification:

Let’s take a look at each of these individually.

Linux Foundation LFS-259

I purchased the certification bundle from the Linux Foundation so the course and exam came together. The course is pretty in-depth and offers the most labs of any of the courses. Unfortunately, I wasn’t able to get a number of them to work right since there was some setup that had to be done. I had the opportunity to meet the instructor at KubeCon and he’s a very nice guy, but unfortunately, the material was presented in a very dry manner and I questioned whether or not I’d finish it.

Linux Academy CKAD Course

The LA course was very good. The content was engaging and the exercises did a good job of reinforcing the skills as they were presented. There are also plenty of links provided to supplemental resources to further expand your understanding. I highly recommend this route if you’re a LA subscriber (or consider joining if you’re not).

KodeKloud CKAD Course

I stumbled upon KodeKloud through Udemy and it was the last course I took. I have to admit that I did initially skip through some of the presentations since I’d already been through two other courses, but I’ve watched them all now. The videos are very good and the graphics used do a great job of supporting the message. However, I think the real area this course shines is in the labs.

The KK labs are, by far, the best. It’s the only lab series I found which provides real-time feedback so you know right away whether you’re on the right track or not. There is an excellent lab which is very complete and throws a lot of tasks into a single lab so while the presentation is different from the real exam, the concept is the same. And if you don’t have a working system at the end, you don’t pass the lab. There are also two mock exams included.

Online Resources

If you found yourself reading this as the result of an internet search, I’m sure you’ve also come across some of the same resources I used. I’ll try my best to reference them here and I’ll probably be repeating what some of the others have said. Let me say that my intention isn’t to be yet another repeat of the same material. My goal is to explain how I used the great resources that are already available along with providing some tips and tricks that I either haven’t seen or haven’t seen enough.

Again, I won’t belabor things here. Do the exercises until you can do them in your sleep. And then keep doing them. I’ve read suggestions to do them 4-5 times. Don’t do that. Do them until you can do them without thinking.

Preparation Tips

Muscle Memory

While two hours seems like a long time to answer just 19 questions, but trust me, this isn’t the case. I went through half of the exam in 30 minutes. When I ended it, I had 1 minute left.

  • You need to do the exercises until you have solid muscle memory.
  • Your fingers need to glide across the keyboard without hesitation. You won’t have enough time if you have to keep stopping to remember how to do things.
  • Do things by rote. For example, except for very simple tasks (e.g. creating a serviceaccount) get used to saving files for everything you do in case you bugger something up because you’re in a hurry. --dry-run -o yaml > somefile.yaml I saved everything as q1.yaml, q2.yaml, etc.

Vim Setup

The first thing I did when I started the exam was configure Vim. The following command sets all spacing (tab stop, soft tab stop, shift width) to two spaces, expands tabs to spaces, and turns on line numbering.

vi ~/.vimrc
set ts=2 sts=2 sw=2 et number

Save and exit and then reload the file: . /~.vimrc

Update: I don’t do this anymore. Time is important and this is too many steps. New method:
echo "set ts=2 sts=2 et nu" > ~/.vimrc

Note: I haven’t experienced this, but if you have pasting issues, you may want to set paste from the command operator (:).


Something I didn’t see a lot in my studies was the use of command aliases. In my mind, every character I don’t have to type is time saved. Since you do have a time limit, you don’t want to spend a lot of time configuring your environment. However, if you build muscle memory in this area as well, you can knock it out very quickly and it’ll save you a lot of time. Here is my very brief list:

alias k=kubectl
alias kgp='k get po'
alias kdp='k delete po --force ~~--grace-period=0~~'

Update: A nice addition to 1.19 is that you no longer need --grace-period=0to quickly delete a pod forcibly.

That’s all I used for the exam. However, in my test environment, I also included these and I wish I would’ve taken the seconds to include them on test day:

alias kaf='k apply -f'
alias kdf='k delete -f'
alias kex='k explain'
alias kexr='k explain --recursive'

It’s so much easier and faster to type kgp nginx rather than kubectl get po nginx. Seconds count.

Muscle Memory Tip Get used to including the namespace in your commands. The last thing you want to do is create the right resources in the wrong location.


According to numerous resources, auto-completion is enabled in the exam environment. I did not find this to be the case. Fortunately, you are allowed to have one tab open to the official documentation so I had the auto-completion process bookmarked. Once I’d updated Vim and added my aliases, I tested auto-completion and it failed so I quickly enabled it.

Alias Tip Auto-completion does not work with aliases unless you make it. In order to get it to work, you need to run this command: complete -F __start_kubectl k

Muscle Memory Tip If you plan on using auto-completion during the exam, which I highly recommend due to some of the long namespace names if nothing else, practice, practice, practice. I never used auto-completion during my studies so despite taking the time to enable it during the exam, it never once crossed my mind to use it. There were many times I grumbled to myself about all the typing I was doing when all I had to do was hit the tab key. But I didn’t have the muscle memory ingrained so it was lost on me.

[Think Imperatively]

You can accomplish a lot with imperative commands. Be forwarned that you’ll see deprecation errors running these, but at least for now, they work, and they work very well. Let’s look at kubectl run…I mean, k run. 😉

# Note: All examples have aliased kubectl to k --> alias k=kubectl
# Create a NGINX pod
k run nginx --image=nginx --restart=Never
# Create a NGINX deployment with 3 replicas
k run nginx --image=nginx --replicas=3
# Create a Job based on the busybox image. Execute the command "sleep 4800"
k run bb-job --image=busybox --restart=OnFailure -- /bin/sh -c "sleep 4800"
# Create a CronJob based on the busybox image. Write the date to stdout every minute.
k run bb-cj --image=busybox --restart=OnFailure --schedule="*/1 * * * *" -- date

Let’s look at some of the things you can do imperatively.

Create a NGINX deployment with three replicas and create a service listening on port 80
You might be inclined to do this:

# Deprecated - This doesn't work anymore.
$ k run nginx --image=nginx --replicas=3
$ k expose deploy/nginx --port=80

Update: For the current exam (1.19), the --replicas flag has been removed from the run command. To create a deployment, use k create deploy <name> --image=<name> --replicas=<num>.

While this will work, it can be streamlined. We also didn’t save the file so we can fix it if necessary. Let’s try again.

# Deprecated
$ k run nginx --image=nginx --replicas=3 expose --port=80 --dry-run -o yaml > nginx.yaml
$ kaf nginx.yaml

Update: The --dry-run flag has also changed. The new format is now --dry-run=client. You also can’t expose the deployment with the imperative create.

$ k create deploy nginx --image=nginx --dry-run=client -o yaml > nginx.yaml
$ k expose deploy nginx --port=80

Optionally, you can include --name=<name> to give the service a custom name.

Create a WordPress pod with requests of 200m cpu, 250Mi memory and limits of 400m cpu, 500Mi memory
$ k run wordpress --image=wordpress --restart=Never --requests=cpu=200,memory=250Mi --limits=cpu=400m,memory=500Mi

Upgrade the image version in a Deployment to nginx:1.9.7
While you can edit the Deployment: k edit deploy nginx I prefer to use this method:
k set image deploy nginx nginx=nginx:1.9.7 --record

The nginx=nginx:1.9.7 defines the container and image names respectively. You can see that the Deployment has two containers per Pod:

nginx-5964cb8f4d-6x9pz   2/2     Running     0          17s
nginx-5964cb8f4d-7xwvh   2/2     Running     0          17s
nginx-5964cb8f4d-n7hgx   2/2     Running     0          17s
      - image: nginx:1.7.9
        name: nginx
        resources: {}
      - image: busybox
        name: busybox
        - /bin/sh
        - -c
        - sleep "4800"

After running the command above, we can watch the rollout occur as our new Pods spin up and the existing ones get terminated.

$ k rollout status deploy nginx
Waiting for deployment "nginx" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "nginx" rollout to finish: 1 old replicas are pending termination...
deployment "nginx" successfully rolled out

We can also see the history. One benefit to using this method rather than simply editing the Deployment is that the history is updated with what was actually done.

$ k rollout history deploy nginx
1         <none>
2         kubectl set image deploy nginx nginx=nginx:1.9.7 --record=true

Let’s update to the latest version, 1.17.6. This time we’ll just do a simple edit with k edit deploy/nginx.

Notice the history repeats what we did in the previous step:

$ k rollout history deploy nginx
1         <none>
2         kubectl set image deploy nginx nginx=nginx:1.9.7 --record=true
3         kubectl set image deploy nginx nginx=nginx:1.9.7 --record=true

If we use the described method, though, it’s reflected correctly.

$ k set image deploy nginx nginx=nginx:1.17.6 --record
deployment.extensions/nginx image updated
$ k rollout history deploy nginx
1         <none>
2         kubectl set image deploy nginx nginx=nginx:1.9.7 --record=true
3         kubectl set image deploy nginx nginx=nginx:1.17.6 --record=true

Add the label tier=frontend to the nginx Deployment
k label deploy nginx tier=frontend

Add label while creating the Deployment
k run nginx --image=nginx --replicas=3 --labels=tier=frontend

Remove the tier label from the Deployment
k label deploy nginx tier-

It’s a Secret
While it’s easy to create a Secret resource imperatively, there’s one bit which might trip you up. When creating a secret using --from-literal or --from-file, you need to specify generic in the command.
k create secret generic my_secret --from-literal=password=letmein

You can also create named secrets. For example, let’s say you have a config app which pulls its data from Git. You can store the ssh key as a Secret and name it.
k create secret generic my_secret --from-file=git-ssh=~/.ssh/

You can create a Secret from an environment file as well.
k create secret generic my-secret --from-env-file=myvars.env

Given a myvars.env like this:

Your Secret will look like this:

$ k get secret/my-secret -o yaml
apiVersion: v1
  HOST: bXlob3N0LmNvbQ==
  TIER: YmFja2VuZA==
kind: Secret
  creationTimestamp: "2020-01-10T04:52:26Z"
  name: my-secret
  namespace: default

Speed Tips

Forced Deletion

Remember the alias we created at the beginning of this article for deleting pods? Maybe you asked yourself why. Here are a couple of examples. Notice the time difference between allowing kubectl to delete the pod gracefully as opposed to forcing it.

Busybox Pod

Graceful Forced
32.114s 0.095s

Ubuntu Pod

Graceful Forced
41.348s 0.335s

I’ve seen it take less time to kill a pod, but keep in mind that the default grace period is 30 secs.

Quick Templates

Kubernetes is just too complex to allow everything to be done imperatively but this capability can do wonders for kickstarting your descriptors. Here are two examples off the top of my head.

Environment Variables

This one is a little odd, and something I’m inclined to work on as an enhancement. While you can add environment variables to an imperative declaration (e.g. env=MY_VAR=some_value) you can’t do the same with envFrom to use a ConfigMap or Secret. However, there’s nothing stopping you from using the former method to create a placeholder.

For example, suppose you are instructed to create a pod based on nginx which loads the nifty-info ConfigMap as environment variables.

Create a pod template with a placeholder
k run cm-pod --image=nginx --restart=Never --env=place=holder --dry-run -o yaml > cm-pod.yaml

This generates the following template:

apiVersion: v1
kind: Pod
  creationTimestamp: null
    run: cm-pod
  name: cm-pod
  - env:
    - name: place
      value: holder
    image: nginx
    name: cm-pod
    resources: {}
  dnsPolicy: ClusterFirst
  restartPolicy: Never
status: {}

If you have line numbering turned on, it’s very easy to jump directly to the env line and change it to envFrom. Then it’s a quick matter of replacing the next two lines as follows:

  - envFrom:
    - configMapRef:
        name: nifty-info

NodePort Services

Unlike the containerPort and targetPort for a Service, you cannot assign a nodePort from the command line. However, it’s incredibly fast to do this with a template that can easily be edited by adding one line.

$ k expose deploy/np-nginx --port=80 --type=NodePort --dry-run -o yaml > np-svc.yaml

This produces the following template:

apiVersion: v1
kind: Service
  creationTimestamp: null
    run: np-nginx
  name: np-nginx
  - port: 80
    protocol: TCP
    targetPort: 80
    run: np-nginx
  type: NodePort
  loadBalancer: {}

If you’ve built up adequate muscle memory (and your line numbering), you can quickly jump to line 12 (targetPort: 80), jump into insert mode, and add an assigned port value.

  - port: 80
    protocol: TCP
    targetPort: 80
    nodePort: 30082

High-Value Targets

Remember that you only have two hours. While you have to decide your personal course of action, I’ll share my approach to tackling the tasks. I knew that I wanted to a) tackle easy tasks first and b) high-percentage tasks second. If there was any hesitation that I couldn’t knock it out immediately (e.g. create a ConfigMap) and it was worth say 3-4%, it got noted in the exam notepad.

After the first run through the tasks I had a list of tasks I’d skipped and tasks I wanted to review. I then repeated the process going through the skipped listed. If it looked like it was going to take any time at all, it got skipped again. Eventually, I’d whittled away the skipped list and was able to jump back to the review list. This gave me the opportunity to complete every question and still have time to review the tougher tasks for accuracy and completeness.

The key is to not spend a lot of time evaluating. Read the task and make a quick judgement of its complexity. If you have to think about it, mark it and move on.

General Tips

Confirm image used for a Pod or Deployment
$ k get deploy nginx -o yaml | grep -i image

Display error and warning events
$ k get ev | grep -v Normal

Become root user
While not a best practice in the real world, for the exam, it is allowed and recommended to do everything as the root user to avoid permission issues.

sudo -i


In addition to the exam portal, you are allowed one other browser tab which can be used for the official Kubernetes documentation. If you have done the exercises at the sites mentioned previously enough to have built that muscle memory, you shouldn’t need the documentation much. Whatever you do, don’t rely on it or you’ll definitely run out of time.

I found my best strategy was to only rely on things I didn’t want to remember, and couldn’t create with kubectl commands. For example, while I was studying, I did a lot of exercises with PersistentVolumes and PersistentVolumeClaims. The official docs provide near perfect examples you can easily copy and paste to complete your task. I can’t tell you what I had to do during my exam, but what I can tell you is that there are examples available for a NFS PV as well as a hostPath PV.

Here’s a pro tip for you which is especially useful if you find yourself using NFS. This is a real-world example that I faced at work, actually. NFS is a pain. Once a PVC binds to a NFS PV, you can’t rebind without destroying and re-creating the PV. When you have multiple PVs with similar characteristics, you should label them and use a selector to ensure you bind to the correct one. This is another one of those muscle memory things. While it’s not necessarily required, it can save you heartache down the road. I ran into this problem more than once while practicing before I made it part of my standard PV routine.

      release: "stable"

Something else I practiced with but didn’t want to remember during the exam is the NetworkPolicy. It might be helpful to review the documentation’s example to understand how you can test pod access with a NetworkPolicy in place. Remember to delete any NetworkPolicy resources when you’re practicing or you might run into connectivity issues and wonder why pods can’t talk to one another. Or so I’ve been told. 😉


  • The exam takes place over several clusters. Each question provides the command to ensure you are on the correct cluster. Use this every time you change questions.
  • Ensure that you specify the namespace every time one is specified or you will create resources in the wrong place. Yes, I’ve already mentioned this, but it bears repeating.

Personal Care

  • Stay hydrated. One of the things I wish I would’ve done is had more water available. It’s one of the few things you’re allowed to have on the desk (remove the label) and one bottle barely lasted the exam.
  • Test rested. This doesn’t just mean get enough sleep beforehand. It also means go into the exam calm. Worst case, you get a free retake, so don’t let it overwhelm you with stress.


Well, I think that about sums it up. Check back periodically since I’ll add tips as I remember them or as I come up with new ideas which I feel may have helped me. I hope you find at least one nugget in this rather lengthy post which helps guide you towards the successful completion of the certification exam. If that’s the case, please let me know in the comments below. I’d love to celebrate with you.


Post to Twitter

6 thoughts to “How I Passed the CKAD Exam”

  1. Hello

    I successfully passed the CKAD exam today with 78, Really thank you and deep gratitude for your suggestions, It helped a lot. I would like to connect with on linked in please write to me your full name or add me Ramakrishnanada Karidas.

  2. I wonder if a function would be more help. For example:
    kf() {
    k ${@:2} –dry-run -o yaml > $1.yaml

    kf cm-pod run cm-pod –image=nginx –restart=Never –env=place=holder

    I think this would dump to files automatically and save typing. This gives you easy repeatability and less boilerplate.

  3. I would suggest the following change. A function to add the dry run and file piping.

    kdr() {
    eval “k ${@:2} –dry-run=client -o yaml > $1.yaml”

    Example usage:
    kdr q1 run cd-pod –image=nginx –restart=Never –env place=holder

    result: q1.yaml
    apiVersion: v1
    kind: Pod
    creationTimestamp: null
    run: cd-pod
    name: cd-pod
    – env:
    – name: place
    value: holder
    image: nginx
    name: cd-pod
    resources: {}
    dnsPolicy: ClusterFirst
    restartPolicy: Never
    status: {}

    1. I have failed my CKAD exam…. I am planning to tale up free retake after enough preparation… Will they ask same questions from as from 1st attempt….

      1. I would expect similar, not identical questions.

        Have you determined where your weaknesses were? Did you attempt each task? Was there anything you hadn’t seen before?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.