Skip to content

Should You Do the CKS Certification? (My Experience & Honest Take)

Published: at 06:08 PM

alt text

I recently passed the Certified Kubernetes Security Specialist (CKS) certification 🎉 — but it wasn’t exactly smooth.

My first attempt?
👉 56% — failed

So this isn’t a polished “how-to guide” written after everything went perfectly. This is a realistic breakdown of what actually worked, what didn’t, and what I wish I had known earlier.


Table of Contents

Open Table of Contents

Why I Even Did CKS

Working in security, especially around Kubernetes, you inevitably run into the same problem: you kind of understand things, but not deeply enough to be confident.

You know how things work in theory, but when it comes to actually applying security controls, debugging policies, or understanding attack paths, there are gaps. That’s where CKS came in for me.

I mainly wanted a structured way to force myself to go deeper into Kubernetes security beyond surface-level knowledge.

And to be fair, CKS does exactly that.

PS: In the end, I learned so much that there are much more technical “gaps” that I could dive into now…


Should You Do It?

Whether CKS makes sense really depends on where you are.

If you already have a solid Kubernetes foundation (roughly CKA level) and you’re working in areas like security, DevOps, or platform engineering, it’s a very good way to deepen your understanding in a structured, hands-on way.

If you’re still new to Kubernetes, it’s probably too early. In that case, doing CKA first will give you much more value.

And if you’re purely focused on offensive security, there are other certifications (like OSCP or CPTS) that might align better with your goals.

My overall take:
CKS is not a game changer by itself, but it’s a very effective way to force real understanding of Kubernetes security.


How I Prepared (and What Actually Worked)

I didn’t follow a perfect plan. I tried different approaches, adjusted along the way, and eventually found what worked for me.

Start with a course — but don’t rely on it

I started with a YouTube course: https://www.youtube.com/watch?v=d9xfB5qaOfg&t=8416s

It’s a solid introduction and helps build a foundation. But you quickly notice that parts of it are outdated — which is pretty normal in the Kubernetes world.

So I made sure to complement it with more up-to-date topics, especially:

I also went through the KodeKloud course and their mock exams, which were definitely helpful, especially for structure and repetition.

Still, courses alone won’t get you there.


KillerKoda and KodeKloud labs made the biggest difference

The most valuable part of my preparation was hands-on practice, especially using KillerKoda and KodeKloud.

Instead of just going through labs once, I repeated them until things felt natural. The goal wasn’t just to understand what I was doing, but to be able to do it quickly and without overthinking.

I focused heavily on:

This is what really builds muscle memory — and you absolutely need that during the exam.


Practice exams

At some point, you need to simulate the real thing.

I used:

The key here is to treat them seriously:

This is where you start noticing your weaknesses:


Final notes before the exam

Before my second attempt, I created my own condensed notes.

Nothing fancy — just:

This turned out to be one of the most effective things I did. Writing things down in your own way forces clarity.

I will put my final notes into the appendix.


My First Attempt (What Went Wrong)

I underestimated the exam.

The biggest issue was losing around 25 minutes due to a battery/reconnection problem. That alone put me under pressure from the start.

On top of that:

Looking back, it wasn’t just a knowledge problem — it was a strategy problem.

That’s something people often overlook:
this exam tests how you work under pressure, not just what you know.


The Exam Experience (Real Talk)

The exam environment is not particularly forgiving.

You need to go through a fairly strict setup:

During the exam itself:

If you’re not comfortable working in a terminal, this will slow you down a lot.


Strategy That Made Me Pass

What made the biggest difference for me was changing how I approached the exam.

First, I went through all questions and immediately solved the ones I recognized. Everything else got flagged.

This gives you quick points, builds confidence, and removes pressure early.

After that, I went back to the harder questions and worked through them one by one.

The most important lesson here:

Don’t get stuck too early, and don’t forget your flagged questions.

(Yes, I actually made that mistake — even on my final attempt.)


The Most Underrated Skill: Linux

One thing that surprised me was how important general Linux skills are.

You don’t just need Kubernetes knowledge — you need to be efficient in the terminal.

Things that helped a lot:

A key insight for me: Don’t rely too heavily on patterns you memorized from practice environments.

The real exam expects you to be able to figure things out, not just repeat steps.


Common Mistakes

Looking back, these were the biggest pitfalls:


Final Verdict

CKS is a solid certification and a great structured way to learn Kubernetes security.

But its real value isn’t the certificate itself, it’s everything you learn while preparing for it.


Closing Thought

If you approach CKS as something you just want to “pass”, it can feel frustrating and exhausting.

But if you treat it as a way to actually get better at Kubernetes security, it’s absolutely worth it.


If you’re currently preparing and feel stuck at some point, feel free to hit me up! :)

PS: Here are my notes and useful commands, dont be overwhelmed just pick what you need.

# =========================
# SYSTEM / LUNTIME / DEBUG
# =========================
strace -c touch /tmp/error.log              # syscall summary
k exec -it <pod> -- strace -f -p 1          # trace process in container
ps aux | grep <name>                        # find process
pidof etcd                                  # get etcd PID
sudo strace -p <pid>                        # trace host process
lsof -i :6666                               # find PID by port

systemctl list-units --type service
systemctl status nginx
systemctl stop apache2
systemctl disable apache2
apt remove apache2
apt list --installed

netstat -an | grep LISTEN
cat /etc/services | grep 53
grep <string> -C 10

# =========================
# KUBERNETES BASICS
# =========================
k create secret generic my-secret1 --from-literal=password=admin
k label node node01 node-restriction.kubernetes.io/one=123
k rollout restart deployment -n <namespace>

kubectl label --overwrite ns my-existing-namespace \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=v1.35

export d="--grace-period=0 --force"

# =========================
# ETCD / SECRETS
# =========================
export ETCDCTL_API=3
export ETCDCTL_CACERT=/etc/kubernetes/pki/etcd/ca.crt
export ETCDCTL_CERT=/etc/kubernetes/pki/apiserver-etcd-client.crt
export ETCDCTL_KEY=/etc/kubernetes/pki/apiserver-etcd-client.key

etcdctl --endpoints=https://127.0.0.1:2379 get "" --prefix --keys-only
etcdctl --endpoints=https://127.0.0.1:2379 get /registry/secrets/restricted/secret1

# =========================
# LOGS / TROUBLESHOOTING
# =========================
service kube-apiserver status
journalctl -u kube-apiserver
crictl ps
crictl logs
docker ps
docker logs

/var/log/pods
/var/log/containers
/var/log/syslog

# =========================
# APPARMOR / SECCOMP / FALCO
# =========================
cat /sys/kernel/security/apparmor/profiles | sort
apparmor_parser -q <profile-file>

# falco config / variables / log path
/etc/falco/falco.yaml
file_output:
  enabled: true
  keep_alive: false
  filename: /opt/security_incidents/alerts.log

falco --list
sudo falco -U
cat /var/log/syslog | grep -a falco

# Falco example rules
- rule: Container Access to /dev/mem
  condition: fd.name="/dev/mem"
  output: "Container accessed /dev/mem"
  priority: WARNING

- rule: Write below binary dir
  desc: an attempt to write below binary directories
  condition: >
    bin_dir and evt.dir=< and open_write
    and not package_mgmt_procs
    and not user_known_write_below_binary_dir_activities
  output: >
    File below binary dir opened for writing
    (user_id=%user.uid file_updated=%fd.name command=%proc.cmdline)
  priority: CRITICAL
  tags: [filesystem, mitre_persistence]

# seccomp
kubectl run amicontained --image r.j3ss.co/amicontained amicontained -- amicontained

apiVersion: v1
kind: Pod
metadata:
  name: default-pod
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault   # or Localhost
      localhostProfile: profiles/test.json
  containers:
  - name: test
    image: hashicorp/http-echo:1.0
    securityContext:
      allowPrivilegeEscalation: false

# custom seccomp profile location
/var/lib/kubelet/seccomp/profiles/test.json

# =========================
# TRACING (TRACEE)
# =========================
docker run --rm --privileged --pid=host \
  -v /lib/modules:/lib/modules:ro \
  -v /usr/src:/usr/src:ro \
  -v /tmp/tracee:/tmp/tracee \
  aquasec/tracee:0.4.0 --trace comm=ls

docker run --rm --privileged --pid=host \
  -v /lib/modules:/lib/modules:ro \
  -v /usr/src:/usr/src:ro \
  -v /tmp/tracee:/tmp/tracee \
  aquasec/tracee:0.4.0 --trace container=new

# =========================
# IMAGES / SBOM
# =========================
trivy image --format cyclonedx --output sbom.json <image>
trivy image --format cyclonedx --output /opt/course/18/sbom.json registry.k8s.io/kube-controller-manager:v1.31.0

bom generate -i registry.k8s.io/kube-apiserver:v1.32.0 -o <outfile>

docker run --rm -it alpine:3.18 sh -c 'apk info -v'
k exec <pod> -- apk info -v

# =========================
# NETWORK POLICIES
# =========================
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: metadata-deny
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 192.168.100.21/32

# cilium network policies: don't forget namespace

# =========================
# KUBELET CONFIG
# =========================
apiVersion: v1
kind: ConfigMap
metadata:
  name: kubelet-config
  namespace: kube-system
data:
  kubelet: |
    apiVersion: kubelet.config.k8s.io/v1beta1
    kind: KubeletConfiguration
    containerLogMaxSize: 5Mi
    containerLogMaxFiles: 3
    volumeStatsAggPeriod: 0s

kubeadm upgrade node phase kubelet-config
service kubelet restart

# =========================
# AUDIT POLICY
# =========================
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- RequestReceived
rules:
- level: RequestResponse
  resources:
  - group: ""
    resources: ["pods"]

# apiserver flags
--audit-policy-file=/etc/kubernetes/audit-policy.yaml
--audit-log-path=/var/log/kubernetes/audit/audit.log

# mount audit files into apiserver
volumeMounts:
- mountPath: /etc/kubernetes/audit-policy.yaml
  name: audit
  readOnly: true
- mountPath: /var/log/kubernetes/audit/
  name: audit-log
  readOnly: false

volumes:
- name: audit
  hostPath:
    path: /etc/kubernetes/audit-policy.yaml
    type: File
- name: audit-log
  hostPath:
    path: /var/log/kubernetes/audit/
    type: DirectoryOrCreate

# =========================
# ADMISSION CONTROLLERS
# =========================
--enable-admission-plugins=NodeRestriction
# ImagePolicyWebhook: put AdmissionConfiguration in mounted dir from apiserver

# =========================
# CONTAINERS
# =========================
docker run -e "TOKEN=xxx" image
podman run -e TOKEN=xxx image

# Dockerfile hardening
RUN rm /usr/bin/bash

# =========================
# LINUX HARDENING (CIS)
# =========================
modprobe pcspkr
lsmod
cat /etc/modprobe.d/blacklist.conf
# blacklist sctp
# blacklist dccp

shutdown -r now
lsmod | grep sctp
lsmod | grep dccp

# =========================
# MISC
# =========================
/etc/docker/daemon.json
--kubernetes-service-node-port=31000   # remove option + svc recreated as ClusterIP
# use SA token as projected volume
# ingress TLS config
# ip/32 for single host