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.

Architecture · 25%35 min2 tasks

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

ComponentWhere and how (kubeadm)Its jobWhen it is down
kube-apiserverControl plane, static PodThe front door. Authenticates, authorizes, runs admission, validates, and is the only component that reads and writes etcd.kubectl gets connection refused. Running Pods keep running; nothing can change.
etcdControl plane static Pod (stacked) or separate hosts (external)Consistent key-value store that holds every object in the cluster.The API server errors or hangs, so the API is effectively down.
kube-schedulerControl plane, static PodPicks a node for every Pod whose spec.nodeName is empty.New Pods stay Pending with no scheduling events.
kube-controller-managerControl plane, static PodRuns the reconcile loops: Deployments, ReplicaSets, Jobs, node lifecycle, EndpointSlices, ServiceAccount tokens, CSR signing.Desired state stops becoming actual state: a scaled Deployment gets no new Pods, dead nodes are never marked NotReady.
cloud-controller-managerCloud clusters only; not installed by kubeadm or kindTalks to the provider API: node addresses, LoadBalancer Services, routes.LoadBalancer Services stay <pending>.
kubeletEvery node, systemd serviceRegisters the node, runs its Pods through the CRI, runs static Pods, executes probes, reports status.Node turns NotReady; after about five minutes its Pods are evicted.
kube-proxyEvery node, DaemonSetPrograms iptables or nftables rules so Service virtual IPs reach their endpoints.Service routing on that node stops following changes.
Container runtimeEvery node, systemd service (containerd or CRI-O)Pulls images and runs containers through runc. The kubelet reaches it over a CRI socket such as unix:///run/containerd/containerd.sock.No container can start; the node reports NotReady.

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.

Find the static Pod path and recognize a mirror Podbash
# 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 file

Reading a component's live flags

Four places the answer can come frombash
# 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

Taskar-cp-1
Architecture3%~5 min

context · kubectl config use-context kind-cka

Read control plane settings from the node

Answer three questions about this cluster by reading it. Write each answer as a single bare value. (1) The IP range the API server assigns Service ClusterIPs from, to /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.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-cp-1/setup | bash
  2. 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/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-cp-1/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-cp-1/cleanup | bash
Taskar-cp-2
Architecture4%~6 min

context · kubectl config use-context kind-cka

Run a static Pod on a worker

Run a static Pod named 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.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-cp-2/setup | bash
  2. 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/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-cp-2/verify | bash
    Counts toward your readiness score.
  4. 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.