
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:
- newer NetworkPolicy and Cilium patterns
- runtime security (Falco, seccomp, AppArmor)
- image scanning (Trivy)
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:
- NetworkPolicies (including Cilium)
- Admission Controllers
- RBAC
- Security contexts
- Writing and understanding Falco rules
- Audit Policies
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:
- KillerKoda exams for additional practice
- Killer.sh as the closest approximation of the actual exam
The key here is to treat them seriously:
- don’t look things up unnecessarily
- stick to time limits
This is where you start noticing your weaknesses:
- where you’re too slow
- where you get stuck
- what you thought you understood, but actually didn’t
Final notes before the exam
Before my second attempt, I created my own condensed notes.
Nothing fancy — just:
- commands I kept forgetting
- useful YAML snippets
- small patterns and reminders
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:
- I didn’t manage my time well
- I got stuck on harder questions too early
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:
- clean room
- no interruptions
- no leaving
- everything properly checked beforehand
During the exam itself:
- there can be slight lag
- switching between contexts can be annoying
- you need to stay focused for a long time
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:
- using tools like
grep,awk,sed,find - navigating quickly
- editing YAML without thinking too much
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:
- focusing too much on theory
- not practicing under time pressure
- underestimating Linux fundamentals
- getting stuck on single questions for too long
- forgetting to revisit flagged questions
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