Supply chain and admission control stop bad things from entering your cluster. Runtime security is about everything that happens after a pod is already running — and on the Certified Kubernetes Security Specialist (CKS) exam, that’s one of the heaviest domains. Monitoring, Logging and Runtime Security carries roughly 20% of the exam weight, tied with Cluster Hardening and Supply Chain Security as one of the big three.
This guide walks the entire domain from a practitioner’s point of view: detecting suspicious behavior with Falco, writing and tuning custom rules, enforcing immutability on running containers, and wiring up Kubernetes API audit logging. You’ll get the concepts, the exact commands, and the exam-day reflexes that let you finish these tasks before the two-hour clock runs out.
If you still need the big picture of the exam first, start with the CKS Exam Guide 2026 and the CKS Exam Topics breakdown, then come back here to go deep on runtime security.
What “Runtime Security” Actually Covers
Runtime is the live attack surface. A pod that passed every admission check can still be compromised by a vulnerable dependency, a leaked credential, or a misconfiguration that lets an attacker get a shell. Runtime security answers two questions: Is something malicious happening right now? and Can I prove what happened after the fact?
For the CKS exam, this domain breaks down into four practical skills:
| Skill | What you’ll do on the exam |
|---|---|
| Behavioral analytics & threat detection | Deploy Falco, write rules, find triggered alerts |
| Container immutability | Lock down securityContext so containers can’t be modified |
| Kubernetes audit logging | Configure an audit policy on the API server |
| Investigate attacks | Read Falco output and audit logs to trace activity |
Each maps to a tool or a Kubernetes mechanism. Let’s take them in order.
Detecting Threats with Falco
Falco is the CNCF runtime security project and the tool the CKS exam expects you to know. It watches the kernel’s system call stream — open, execve, connect, setuid, and so on — and raises an alert whenever activity matches one of its rules. Because it operates at the syscall layer (via an eBPF probe or a kernel module), it sees what a container is actually doing, not just what its manifest claims.
How Falco is structured
Falco’s configuration lives in a few files you should be able to find instantly:
/etc/falco/falco.yaml # main config: outputs, drivers, log level
/etc/falco/falco_rules.yaml # default shipped rules (don't edit these)
/etc/falco/falco_rules.local.yaml # your custom & override rules
/etc/falco/rules.d/ # additional rule files
The golden rule on the exam: never edit falco_rules.yaml directly. Put custom rules and overrides in falco_rules.local.yaml, which is loaded last and wins.
Rules, macros, and lists
A Falco ruleset has three building blocks:
- Lists — reusable collections of values (e.g. a set of binaries).
- Macros — named, reusable condition snippets.
- Rules — the actual detections, each with a condition, output, and priority.
Here’s a minimal custom rule that fires whenever a shell is spawned inside a container — a classic indicator of an interactive intrusion:
# /etc/falco/falco_rules.local.yaml
- rule: Terminal shell in container
desc: A shell was spawned inside a container
condition: >
spawned_process and container
and shell_procs and proc.tty != 0
output: >
Shell opened in container
(user=%user.name container=%container.name
image=%container.image.repository proc=%proc.cmdline)
priority: WARNING
tags: [container, shell, runtime]
The condition is a filter expression over syscall fields. output is the human-readable alert, with %-prefixed fields interpolated at fire time. priority is one of EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, NOTICE, INFORMATIONAL, DEBUG — and you’ll often be asked to filter alerts by priority, so know the order.
Running Falco and reading its output
On a node where Falco is installed as a service:
# Check Falco is running and see its alerts
systemctl status falco
journalctl -u falco -f
# Validate a rules file before relying on it
falco -V -r /etc/falco/falco_rules.local.yaml
# Run Falco with a specific rule file and human output
falco -r /etc/falco/falco_rules.local.yaml -o json_output=false
A very common exam task: “Falco is running on the node. A rule has detected a process. Find the alert and write the container name / time / details to a file.” The fastest path is journalctl -u falco (or reading the configured log file) and grepping for the rule name or priority:
# Find every CRITICAL-or-higher Falco alert and save it
journalctl -u falco --no-pager | grep -i "critical" > /opt/falco-incident.log
Falco is frequently deployed in-cluster as a DaemonSet so every node is covered:
kubectl get pods -n falco -o wide
kubectl logs -n falco ds/falco | grep "Terminal shell"
Tuning a noisy rule
You’ll sometimes be asked to change an existing rule rather than write a new one — for example, raise its priority or narrow its condition. Override by redefining the rule with the same name in falco_rules.local.yaml:
# Override the default to only alert for non-allowed images
- rule: Terminal shell in container
condition: >
spawned_process and container and shell_procs
and proc.tty != 0
and not container.image.repository in (allowed_images)
priority: CRITICAL
override:
condition: replace
priority: replace
After any rules change, restart Falco and confirm it loaded cleanly:
systemctl restart falco
journalctl -u falco -n 20 --no-pager # look for "Loading rules" with no errors
Enforcing Container Immutability
The second pillar of this domain is making sure a running container cannot be tampered with. An immutable container has no writable filesystem, runs as a non-root user, and carries only the Linux capabilities it truly needs. If an attacker lands inside it, there’s nothing to write, no package manager to abuse, and no privilege to escalate.
This is all done in the pod’s securityContext:
apiVersion: v1
kind: Pod
metadata:
name: hardened-app
spec:
containers:
- name: app
image: myapp:v1
securityContext:
readOnlyRootFilesystem: true # filesystem can't be modified
runAsNonRoot: true # refuse to run as root
runAsUser: 1000
allowPrivilegeEscalation: false # no setuid escalation
privileged: false
capabilities:
drop: ["ALL"] # start from zero capabilities
add: ["NET_BIND_SERVICE"] # add back only what's needed
volumeMounts:
- name: tmp
mountPath: /tmp # writable scratch space if required
volumes:
- name: tmp
emptyDir: {}
The exam mindset here is to take an insecure pod and harden it in place. A useful checklist you should be able to apply from memory:
| Setting | Why it matters |
|---|---|
readOnlyRootFilesystem: true | Stops malware/dropper writing to disk |
runAsNonRoot + runAsUser | Removes root inside the container |
allowPrivilegeEscalation: false | Blocks setuid privilege gain |
capabilities.drop: ["ALL"] | Least privilege at the kernel level |
privileged: false | No host-level access |
To enforce these org-wide rather than per pod, you use Pod Security Admission (the built-in restricted profile covers most of the above) or a policy engine — concepts that overlap with the Kubernetes security best practices for CKS and the admission-control work covered in the CKS supply chain security guide.
seccomp and AppArmor at runtime
Two more runtime controls the exam loves:
-
seccomp filters which syscalls a container may make. The safest practical setting is the runtime default profile:
securityContext: seccompProfile: type: RuntimeDefaultCustom profiles live under
/var/lib/kubelet/seccomp/profiles/and are referenced withtype: LocalhostandlocalhostProfile: profiles/audit.json. -
AppArmor confines what files and capabilities a process can touch. In current Kubernetes you set it via the security context (older versions used an annotation):
securityContext: appArmorProfile: type: Localhost localhostProfile: k8s-apparmor-restrictive
Know how to confirm a profile is active — kubectl exec into the pod and try a denied action (e.g. write to a read-only path) and watch it fail.
Kubernetes Audit Logging
Falco watches the kernel; audit logging watches the API. Every request to kube-apiserver — who did what, to which resource, and whether it was allowed — can be recorded. On the exam you’re typically asked to create or modify an audit policy and wire it into the API server.
Audit levels and stages
An audit policy decides how much detail to record for each request:
| Level | What it records |
|---|---|
None | Nothing — used to drop noisy requests |
Metadata | Who, what, when (no request/response bodies) |
Request | Metadata + request body |
RequestResponse | Metadata + request and response bodies |
A minimal policy that captures secret access in detail but ignores noise:
# /etc/kubernetes/audit/policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Log secret access at the most detailed level
- level: RequestResponse
resources:
- group: ""
resources: ["secrets"]
# Log everything else at metadata level
- level: Metadata
omitStages:
- "RequestReceived"
Wiring it into the API server
The API server is a static pod, so you edit its manifest and add the audit flags plus the volume mounts for the policy and log file:
# /etc/kubernetes/manifests/kube-apiserver.yaml (excerpt)
spec:
containers:
- command:
- kube-apiserver
- --audit-policy-file=/etc/kubernetes/audit/policy.yaml
- --audit-log-path=/var/log/kubernetes/audit/audit.log
- --audit-log-maxage=30
- --audit-log-maxbackup=5
- --audit-log-maxsize=100
volumeMounts:
- name: audit-policy
mountPath: /etc/kubernetes/audit/policy.yaml
readOnly: true
- name: audit-logs
mountPath: /var/log/kubernetes/audit/
volumes:
- name: audit-policy
hostPath:
path: /etc/kubernetes/audit/policy.yaml
type: File
- name: audit-logs
hostPath:
path: /var/log/kubernetes/audit/
type: DirectoryOrCreate
Because the API server is a static pod, saving the manifest makes the kubelet restart it automatically. Watch it come back:
# The apiserver container will restart; wait for it
watch crictl ps | grep kube-apiserver
# Then confirm audit events are landing
tail -f /var/log/kubernetes/audit/audit.log
A frequent exam trap: an invalid audit policy crashes the API server and kubectl stops responding. Always kubectl apply --dry-run=client your policy logic mentally, double-check the YAML indentation, and keep a backup of the original manifest before editing.
Investigating an Attack
The final skill ties the others together: given Falco alerts and audit logs, reconstruct what happened. A practical investigation flow:
- Triage the Falco alert — which rule, which container, which process, what time?
- Pivot to the audit log — search for the same timeframe and the service account or user involved.
- Identify the phase — was this reconnaissance (listing secrets), execution (a shell), or persistence (a new pod/role)?
- Contain — cordon the node, delete the pod, revoke the credential or RBAC binding.
# Correlate: find what a suspicious service account did
grep "system:serviceaccount:prod:deployer" /var/log/kubernetes/audit/audit.log \
| grep '"verb":"create"'
Speed here comes from having read these logs before — the formats are dense, and exam time is short.
A 7-Day Runtime Security Study Plan
| Day | Focus | Hands-on goal |
|---|---|---|
| 1 | Falco install & architecture | Locate all config files, read the default rules |
| 2 | Write custom Falco rules | Detect a shell, a write to /etc, an outbound connection |
| 3 | Tune & override rules | Raise a priority, add an exception, reload cleanly |
| 4 | Container immutability | Harden 3 insecure pods from memory under 5 min each |
| 5 | seccomp & AppArmor | Apply RuntimeDefault + a Localhost profile, prove a deny |
| 6 | Audit policy | Configure API server audit end-to-end, recover from a bad policy |
| 7 | Investigation drill | Correlate a Falco alert to an audit entry against the clock |
The thing that separates a pass from a fail in this domain is fluency, not theory. Reading about Falco rules is not the same as having written five of them and knowing exactly which file they go in.
Practice in a Real Exam-Like Environment
Runtime security tasks are muscle memory. You want to have already triggered a Falco rule and recovered a crashed API server before exam day — not be doing it for the first time under the two-hour clock.
Sailor.sh’s Certified Kubernetes Security Specialist (CKS) Mock Exam Bundle gives you a browser-based, exam-style terminal wired to a live Kubernetes cluster, with realistic runtime scenarios: writing Falco rules, hardening running pods, and configuring audit policies against a real API server. It mirrors the exam’s format and time pressure so the reflexes are there when it counts. If you want to try a free, open-source practice terminal first, the CKS practice environment guide walks through getting started, and how to practice CKS for free lists more options.
Pair the hands-on work with structured study using the CKS Study Plan, and make sure you’ve cleared the CKS prerequisites — you need a current CKA to even sit the exam.
Frequently Asked Questions
How much of the CKS exam is runtime security?
Monitoring, Logging and Runtime Security accounts for roughly 20% of the CKS exam — one of the three largest domains alongside Cluster Hardening and Supply Chain Security.
Do I need to memorize Falco rule syntax?
You should be fluent writing a rule with condition, output, and priority, and you must know where custom rules go (falco_rules.local.yaml). The exam gives documentation access, but fumbling for syntax costs time you don’t have. Write a dozen rules in practice until it’s automatic.
What’s the difference between Falco and audit logs?
Falco watches kernel system calls to detect what a container is doing (a shell, a file write, an outbound connection). Kubernetes audit logs record API server requests — who called the API and what they asked for. You need both: one sees workload behavior, the other sees control-plane activity.
What’s the single highest-impact runtime change I can make?
Setting readOnlyRootFilesystem: true together with runAsNonRoot and dropping all capabilities. It removes the writable disk, the root user, and the kernel privileges that most container attacks depend on — usually just a few lines of securityContext.
How do I avoid breaking the API server when adding an audit policy?
Back up /etc/kubernetes/manifests/kube-apiserver.yaml first, validate your policy YAML indentation, and make sure the host paths for the policy file and log directory actually exist. A malformed policy or missing mount will crash the API server and lock you out of kubectl.
Can I pass the CKS without a strong CKA foundation?
Unlikely. CKS assumes you already move fast in a cluster — editing static pod manifests, reading logs, fixing RBAC. If those feel slow, shore them up first. See CKA vs CKAD vs CKS: which certification first.
Conclusion
The runtime security domain rewards practitioners who actually do the work: deploy Falco and read its alerts, lock a container down so there’s nothing to exploit, and turn on audit logging so every API call leaves a trail. None of it is conceptually hard — but all of it is timed, and the gap between knowing it and doing it fast is hours in a real terminal.
Get Falco rules into your fingers, make securityContext hardening boring, and configure API server auditing until you can recover from a bad policy without panicking. The same skills that earn the certification are the ones that genuinely detect and contain attacks in production — which is the entire point of CKS.