Control plane and node components
What every control plane and node component does, how kubeadm runs each one, and how to read the flags a running component was actually started with.
After this lesson
- 01You can name each control plane and node component, say how kubeadm runs it, and predict the symptom when it is down.
- 02You can read a component's live configuration from its static Pod manifest, its process arguments, the API or the kubelet config file.
- 03You can create and remove a static Pod on any node.
The exam does not ask you to draw the architecture diagram. It asks you to find things: which flag the API server runs with, where a kubelet loads static Pods from, why nothing is scheduling. Every one of those answers is a file on a node or a field in the API. kind builds its nodes with kubeadm, so the paths in this lesson are the paths you will meet on exam day.
What runs where
Three patterns, then. The kubelet and the runtime are systemd services on the host: debug them with systemctl status and journalctl -u kubelet. The control plane runs as static Pods the kubelet starts from files: debug them with the manifest directory and crictl. kube-proxy and the CNI agent (Calico in this lab) are DaemonSets in kube-system: debug them with kubectl. Knowing which group a component is in tells you which tool to reach for.
Static Pods
A static Pod is defined by a file, not by an API object. The kubelet watches its staticPodPath (kubeadm sets /etc/kubernetes/manifests), runs every Pod it finds there and restarts it if it dies, with no scheduler and no API server involved. That is how a control plane bootstraps itself: you cannot create the API server through the API server. For visibility the kubelet publishes a read-only mirror Pod named <pod>-<node> and owned by the Node object. Deleting the mirror Pod changes nothing; edit or delete the file instead. The kubelet rescans the directory about every 20 seconds.
# exam: ssh <node> lab: docker exec -it cka-control-plane bash
grep staticPodPath /var/lib/kubelet/config.yaml
ls /etc/kubernetes/manifests
# etcd.yaml kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml
# Not every kubelet uses a config file; the path can also come from a flag
ps -o args= -C kubelet | tr ' ' '\n' | grep -E -- '--config|--pod-manifest-path'
# From your workstation: a mirror Pod is owned by a Node and sourced from a file
kubectl -n kube-system get pod kube-apiserver-cka-control-plane \
-o jsonpath='{.metadata.ownerReferences[0].kind}{" "}{.metadata.annotations.kubernetes\.io/config\.source}{"\n"}'
# Node fileReading a component's live flags
# 1. What the kubelet was told to run (the static Pod manifest)
docker exec cka-control-plane grep -- --service-cluster-ip-range /etc/kubernetes/manifests/kube-apiserver.yaml
# 2. What is actually running (differs when the kubelet could not start a new manifest)
docker exec cka-control-plane sh -c "ps -o args= -C kube-apiserver | tr ' ' '\n' | grep -- --etcd-servers"
# 3. The same through the API, no node shell needed
kubectl -n kube-system get pod -l component=kube-apiserver \
-o jsonpath='{.items[0].spec.containers[0].command}' | tr ',' '\n' | grep service-cluster
# 4. The kubelet: unit file + kubeadm drop-in, generated flags, then the config file
docker exec cka-control-plane systemctl cat kubelet
docker exec cka-control-plane cat /var/lib/kubelet/kubeadm-flags.env
docker exec cka-control-plane grep -E 'staticPodPath|containerRuntimeEndpoint|cgroupDriver' /var/lib/kubelet/config.yaml
# kube-proxy keeps its settings in a ConfigMap
kubectl -n kube-system get cm kube-proxy -o jsonpath='{.data.config\.conf}' | grep -E '^mode|clusterCIDR'The manifest and the process normally agree. When they do not, the kubelet failed to start the new version of the Pod, which is exactly the situation in a broken-control-plane question; crictl ps -a and crictl logs <id> on the node tell you why. Values also differ between clusters: kubeadm defaults the Service range to 10.96.0.0/12, while this kind lab uses 10.96.0.0/16. Read the cluster in front of you, never your memory.
In the docs · the tab you get on exam day
- Kubernetes Components ↗search: “kubernetes components”
- Create static Pods ↗search: “static pod”
- Set Kubelet Parameters Via A Configuration File ↗search: “kubelet config file”
- kube-apiserver ↗search: “kube-apiserver”
context · kubectl config use-context kind-cka
Read control plane settings from the node
/tmp/cka/ar-svc-cidr.txt. (2) The directory the kubelet on cka-control-plane loads static Pod manifests from, to /tmp/cka/ar-static-path.txt. (3) The CRI endpoint that same kubelet uses to reach its container runtime, to /tmp/cka/ar-cri-endpoint.txt.- 01
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/ar-cp-1/setup | bash - 02
Solve it in your terminal
Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under
/tmp/cka/. - 03
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/ar-cp-1/verify | bashCounts toward your readiness score. - 04
Clean up
curl -fsSL https://codewithmuh.com/labs/cka/t/ar-cp-1/cleanup | bash
context · kubectl config use-context kind-cka
Run a static Pod on a worker
ar-static-web in namespace ar-cp on node cka-worker. It uses image nginx:1.27, has the label app=ar-static-web and declares container port 80. It must be started by the kubelet on cka-worker from a manifest file, so that deleting it through the API does not get rid of it.- 01
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/ar-cp-2/setup | bash - 02
Solve it in your terminal
Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under
/tmp/cka/. - 03
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/ar-cp-2/verify | bashCounts toward your readiness score. - 04
Clean up
curl -fsSL https://codewithmuh.com/labs/cka/t/ar-cp-2/cleanup | bash
Finished reading?
0 of 2 tasks passed. Readiness counts tasks, so run them before you move on.