The CKS Practice Clusters
This path runs on three clusters rather than one, because CKS work is meant to damage things and a cluster you are about to break should not be the one another path depends on. This guide reads all three, shows what is installed on them - nothing - and finds the one setting that defines the whole path.
Orientation Guide 3 of 40 Beginner
- Kubernetesapiserver v1.36.4, kubelet v1.36.3
- Runtimecontainerd 2.2.6
- CNICilium 1.18.1 - tunnel/VXLAN, with Hubble relay and UI
- Host OSUbuntu 26.04 LTS, kernel 7.0.0-29
- Built withkubeadm v1.36.3 - podSubnet 10.244.0.0/16, serviceSubnet 10.96.0.0/12
- TimeAbout 14 min
- Reviewed25 August 2026
Written against the versions above. All three were rebuilt from scratch with kubeadm and Cilium 1.18.1, and verified afterwards rather than taken from a build sheet. Every command here is read-only.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| CKA6001 | 192.168.0.46 | Ubuntu 26.04 LTS | Control Plane Node (tainted NoSchedule) | 2 Core | 4 GB | 50 GB |
| CKA6001-NODE01 | 192.168.0.47 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
| CKA6001-NODE02 | 192.168.0.48 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
| CKA7001 | 192.168.0.51 | Ubuntu 26.04 LTS | Control Plane Node (tainted NoSchedule) | 2 Core | 4 GB | 50 GB |
| CKA7001-NODE01 | 192.168.0.52 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
| CKA8001 | 192.168.0.56 | Ubuntu 26.04 LTS | Single Node (control plane, untainted) | 2 Core | 4 GB | 50 GB |
Before you start
- guide 1 - the domains these clusters have to support.
-
The cluster most of this path runs on
Nodes, roles and the taint, then the version skew.
bash Example session kubectl get nodes -o custom-columns='NAME:.metadata.name,STATUS:.status.conditions[-1].type,ROLES:.metadata.labels.node-role\.kubernetes\.io/control-plane,KUBELET:.status.nodeInfo.kubeletVersion,TAINT:.spec.taints[0].key' --no-headers; echo "--- one control plane and two schedulable workers. Two is the minimum for anything that has to prove traffic crossed a node boundary"cka6001 Ready v1.36.3 node-role.kubernetes.io/control-planecka6001-node01 Ready <none> v1.36.3 <none>cka6001-node02 Ready <none> v1.36.3 <none>--- one control plane and two schedulable workers. Two is the minimum for anything that has to prove traffic crossed a node boundaryecho -n "apiserver "; kubectl get --raw /version | tr -d ' ' | grep -o 'gitVersion":"[^"]*' | cut -d'"' -f3; echo -n "kubelet "; kubectl get nodes -o jsonpath='{.items[0].status.nodeInfo.kubeletVersion}'; echo; echo "--- the kubelet is one patch BEHIND the apiserver, which is the supported direction. The reverse is not"apiserver v1.36.4kubelet v1.36.3--- the kubelet is one patch BEHIND the apiserver, which is the supported direction. The reverse is notExpected result
cka6001taintednode-role.kubernetes.io/control-planewith two schedulable workers, thenapiserver v1.36.4againstkubelet v1.36.3.Success conditionYou know the shape of the cluster the examples run on.
-
The other two, and why there are three
Two smaller clusters, and one field worth noticing.
bash Example session kubectl get nodes -o custom-columns='NAME:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,TAINT:.spec.taints[0].key' --no-headers; echo "--- one control plane and exactly one worker, for anything that needs a second node and no ambiguity about which one"cka7001 v1.36.3 node-role.kubernetes.io/control-planecka7001-node01 v1.36.3 <none>--- one control plane and exactly one worker, for anything that needs a second node and no ambiguity about which oneExpected result
cka7001with one control plane and exactly one worker.Success conditionYou know which cluster an exercise will name and why.
-
The single node, and the taint it does not have
One machine, and the field that makes it usable.
bash Example session kubectl get nodes -o custom-columns='NAME:.metadata.name,KUBELET:.status.nodeInfo.kubeletVersion,TAINT:.spec.taints[0].key' --no-headers; echo "--- a SINGLE node, and note the taint column. A one-node cluster that refuses to schedule is no use, so this control plane is deliberately untainted"cka8001 v1.36.3 <none>--- a SINGLE node, and note the taint column. A one-node cluster that refuses to schedule is no use, so this control plane is deliberately untaintedExpected result
cka8001 v1.36.3 <none>- a single node whose control plane carries no taint.Success conditionYou can explain why one control plane in three is untainted.
-
What is installed on them, which is nothing
Four counts, and an inventory.
bash Example session echo -n "namespaces: "; kubectl get ns --no-headers | wc -l; echo -n "network policies: "; kubectl get netpol,cnp -A --no-headers 2>/dev/null | wc -l; echo -n "admission webhooks:"; kubectl get validatingwebhookconfigurations,mutatingwebhookconfigurations --no-headers 2>/dev/null | wc -l; echo -n "storage classes: "; kubectl get storageclass --no-headers 2>/dev/null | wc -l; echo "--- nothing has been installed on top. Every control this path teaches is one it puts there"namespaces: 5network policies: 0admission webhooks:0storage classes: 0--- nothing has been installed on top. Every control this path teaches is one it puts thereecho -n "pods running: "; kubectl get pods -A --no-headers | wc -l; echo "static control-plane pods:"; kubectl get pods -n kube-system --no-headers | grep -E 'etcd|kube-apiserver|kube-controller|kube-scheduler' | awk '{printf " %s", $1}'; echo; echo "everything else is one of these controllers:"; kubectl get ds,deploy -A --no-headers | awk '{printf " %s", $2}'; echopods running: 18static control-plane pods: etcd-cka6001 kube-apiserver-cka6001 kube-controller-manager-cka6001 kube-scheduler-cka6001everything else is one of these controllers: daemonset.apps/cilium daemonset.apps/cilium-envoy daemonset.apps/kube-proxy deployment.apps/cilium-operator deployment.apps/coredns deployment.apps/hubble-relay deployment.apps/hubble-uiExpected result
5namespaces,0network policies,0admission webhooks,0storage classes. Then 18 pods: four static control-plane pods and seven controllers.Success conditionYou can say exactly what a control demonstrated later was added by.
-
The CNI, and the one setting that defines this path
Four lines of status, and the resource kinds the CNI adds.
bash Example session kubectl -n kube-system exec ds/cilium -c cilium-agent -- cilium-dbg status 2>/dev/null | grep -E 'KubeProxyReplacement|Encryption|Hubble|Host firewall'; echo "--- Cilium, with Hubble on and encryption OFF. CKS domain 4 names pod-to-pod encryption explicitly, so that last line is a task this path has to complete rather than describe"KubeProxyReplacement: FalseHost firewall: DisabledHubble: Ok Current/Max Flows: 4095/4095 (100.00%), Flows/s: 3.84 Metrics: DisabledEncryption: Disabled--- Cilium, with Hubble on and encryption OFF. CKS domain 4 names pod-to-pod encryption explicitly, so that last line is a task this path has to complete rather than describekubectl api-resources --api-group=cilium.io --no-headers 2>/dev/null | awk '{printf "%s ", $1}'; echo; echo "--- policy kinds that exist only because the CNI is Cilium. CiliumNetworkPolicy is what makes L7 rules and pod-to-pod encryption possible here"ciliumcidrgroups ciliumclusterwidenetworkpolicies ciliumendpoints ciliumidentities ciliuml2announcementpolicies ciliumloadbalancerippools ciliumnetworkpolicies ciliumnodeconfigs ciliumnodes ciliumpodippools--- policy kinds that exist only because the CNI is Cilium. CiliumNetworkPolicy is what makes L7 rules and pod-to-pod encryption possible hereExpected result
KubeProxyReplacement: False,Host firewall: Disabled,Hubble: Ok, andEncryption: Disabled. Then tencilium.ioresource kinds.Success conditionYou know what the CNI can do here, and what it is not yet doing.
Troubleshooting
A workload will not schedule on a control-plane node.
Why:
node-role.kubernetes.io/control-plane:NoSchedule.Fix:Use a worker, or tolerate the taint deliberately - as cka8001 does by not having it.
An exercise behaves differently from the guide.
Why: It was run on a cluster something else had already changed.
Fix:Check what is installed against the baseline in this guide - the counts should be zero.
A NetworkPolicy has no effect.
Why: Enforcement is the CNI's job.
Fix:Confirm Cilium is healthy first.
cilium-dbg statusis the one-line check.Assuming encapsulated pod traffic is encrypted.
Why: It is not.
Encryption: Disabledhere.Fix:Domain 4 turns it on. Until then, treat the node network as readable.