Control plane failures

Where the control plane lives on a kubeadm node, what each component's failure looks like from kubectl, and how to debug with crictl and the kubelet journal when kubectl itself is gone.

Troubleshooting · 30%40 min3 tasks

After this lesson

  • 01You can tell from a single symptom which component is down: API server, scheduler, controller-manager or etcd.
  • 02You can find and fix a typo in a static Pod manifest under /etc/kubernetes/manifests and watch the kubelet bring the component back.
  • 03You can read component logs with crictl and journalctl when the API server is not answering.

This lesson and the next cover two competencies of the domain verbatim: Troubleshoot clusters and nodes and Troubleshoot cluster components. On the exam these are the tasks where you ssh to a control-plane node, find a manifest with one wrong character, fix it and wait for the component to come back. They look frightening because kubectl may be dead when you start. They are not: a kubeadm control plane is four files in one directory, and everything you need to read them is on the node.

Where the control plane lives

kubeadm runs kube-apiserver, kube-controller-manager, kube-scheduler and etcd as static Pods: the kubelet on the control-plane node reads their manifests from staticPodPath (set in /var/lib/kubelet/config.yaml, normally /etc/kubernetes/manifests) and runs them itself, with no scheduler or controller involved. For each one it creates a read-only mirror Pod in kube-system named <component>-<node> so you can see it in kubectl. Deleting a mirror Pod does nothing lasting; editing it is rejected. The file is the source of truth: change the file and the kubelet restarts the component; move the file out of the directory and the component stops.

Find them (exam: ssh cka-control-plane; lab: docker exec -it cka-control-plane bash)bash
grep staticPodPath /var/lib/kubelet/config.yaml
ls -l /etc/kubernetes/manifests
# etcd.yaml  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml

# From anywhere, while the API server works
kubectl -n kube-system get pods -o wide
kubectl -n kube-system get pod kube-apiserver-cka-control-plane -o jsonpath='{.metadata.annotations.kubernetes\.io/config\.source}'   # file
ComponentManifestTalks toFlags worth checkingWhen it is down
kube-apiserverkube-apiserver.yamletcd on 127.0.0.1:2379, listens on 6443--etcd-servers, --client-ca-file, --tls-cert-file, --service-cluster-ip-range, every path under /etc/kubernetes/pkikubectl: connection refused. Running Pods keep running; nothing can change
kube-schedulerkube-scheduler.yamlthe API server via /etc/kubernetes/scheduler.conf--kubeconfig, --authentication-kubeconfig, --authorization-kubeconfignew Pods stay Pending with no events
kube-controller-managerkube-controller-manager.yamlthe API server via /etc/kubernetes/controller-manager.conf--kubeconfig, --cluster-signing-cert-file/-key-file, --service-account-private-key-file, --root-ca-fileDeployments create no ReplicaSets, nothing is reconciled, dead nodes stay Ready
etcdetcd.yamllistens on 2379 (clients) and 2380 (peers); data in /var/lib/etcd--data-dir, --listen-client-urls, --cert-file, --key-file, --trusted-ca-filethe API server answers with etcd errors or times out; writes fail

Symptoms by component

  • kube-apiserver down. Every kubectl command fails with The connection to the server <host>:6443 was refused or Unable to connect to the server: … i/o timeout. Pods already running keep serving traffic; kubelets keep their last state; nothing can be created, scaled or deleted. This is the one failure you must debug entirely on the node.
  • kube-scheduler down. New Pods stay Pending with NODE <none> and no events, not even FailedScheduling, because nothing is looking at them. Existing Pods are unaffected. kubectl -n kube-system get pods shows the scheduler Pod in CrashLoopBackOff, Error, or missing.
  • kube-controller-manager down. A new Deployment shows 0/3 with no ReplicaSet and no Pods (kubectl get rs is empty); scaling does nothing; Jobs never start; a node you shut down is never marked NotReady; PVCs are not bound; a new namespace never gets its default ServiceAccount. The API accepts every change and nothing acts on it.
  • etcd down. The API server process is up but has nowhere to read or write: kubectl returns etcdserver: request timed out, context deadline exceeded, or hangs; kubectl get --raw /readyz?verbose shows [-]etcd failed. The API server's log is full of connection errors to 127.0.0.1:2379, and the etcd container itself is Exited or missing in crictl ps -a.

Debugging while kubectl works

Scheduler, controller-manager and etcd problems can be read through the APIbash
kubectl -n kube-system get pods
kubectl -n kube-system describe pod kube-scheduler-cka-control-plane | tail -15
kubectl -n kube-system logs kube-scheduler-cka-control-plane
kubectl -n kube-system logs kube-controller-manager-cka-control-plane --previous
kubectl get --raw /readyz?verbose | tail -5     # per-check health of the API server, including etcd
kubectl get --raw /livez?verbose | tail -3

A mirror Pod in CrashLoopBackOff with an executable file not found message is a typo in command; ErrImagePull is a typo in image; a container that exits at once with unknown flag or no such file or directory in its logs is a typo in an argument or a path. All three are fixed in the manifest, never in the mirror Pod.

Debugging when kubectl does not work

When the API server is down you have the container runtime and the kubelet's journal. crictl talks to containerd directly and lists what is really running on the node, including containers that exited. Its output has a STATE column and an ATTEMPT column: a component whose ATTEMPT keeps climbing is crash-looping; a component with no container at all was never started, which means the kubelet could not pull its image or could not parse its manifest.

On the control-plane nodebash
crictl ps -a                                   # every container, including Exited ones
crictl ps -a --name kube-apiserver
crictl logs <container-id>                     # the component's own output; add --tail 50
crictl pods                                    # sandboxes, including ones with no container
journalctl -u kubelet --since '10 min ago' --no-pager | grep -iE 'apiserver|manifest|static|failed' | tail -20
journalctl -u kubelet -f                       # watch while you fix
systemctl status kubelet --no-pager
cat /etc/kubernetes/manifests/kube-apiserver.yaml

On the lab, run those after docker exec -it cka-control-plane bash; on the exam, after ssh <control-plane-node> (and sudo -i if the prompt is not root). Either way, type exit before the next question: kubectl on the node has no kubeconfig and will fail with connection refused to localhost:8080, which looks like a second outage and is not.

What goes wrong in a manifest

  • Image tag typo. crictl ps -a shows no container for the component; the kubelet journal says Failed to pull image with the bad reference. Once the API server is up, the mirror Pod shows ErrImagePull. Compare the tag with the other three manifests: all four components share the Kubernetes version, except etcd.
  • Command or flag typo. The container starts and exits within a second. crictl logs prints Error: unknown flag: --etcd-serverss or exec: "kube-schedulerr": executable file not found. ATTEMPT climbs.
  • Wrong path in a flag. --kubeconfig, --client-ca-file, --etcd-certfile, --tls-private-key-file: the component logs open /etc/kubernetes/pki/…: no such file or directory. Check each path exists on the host and is mounted into the container by a hostPath volume.
  • Wrong hostPath. A volume whose path points at a file that does not exist. With type: FileOrCreate the kubelet silently creates an empty file, so the component starts and then fails on an empty kubeconfig or certificate: invalid configuration: no configuration has been provided, or a TLS error.
  • YAML that does not parse. A broken indentation or a stray character. The kubelet logs Could not process manifest file with the path and skips it, so the component simply disappears: no mirror Pod, no container, nothing in crictl ps -a. The journal is the only place this is written.
  • Manifest moved or renamed. Same effect as a parse error. The kubelet reads every file in the directory, so a backup copy saved inside it is read as a second Pod with the same name. Keep backups in /tmp or /root.
Three fast checks for a suspicious manifestbash
cd /etc/kubernetes/manifests
grep -n 'image:' *.yaml                                   # tags agree? (etcd has its own)
grep -o '/etc/kubernetes/[a-z./-]*' kube-apiserver.yaml | sort -u | xargs ls -ld    # every path exists?
diff /tmp/kube-apiserver.yaml.bak kube-apiserver.yaml     # if you (or the task) made a backup

How the kubelet reports it, and the fix-and-wait rhythm

The kubelet watches the manifest directory with inotify and also rescans it every 20 seconds (fileCheckFrequency), so a saved file is noticed within seconds. It kills the old container, creates the new one, and logs what it does with the static pod's name. After an API server fix, allow up to a minute before kubectl answers: the server needs to come up, pass its own health checks and reconnect to etcd. Watch with crictl ps on the node, or watch kubectl -n kube-system get pods from the student terminal once it responds.

The rhythm for a manifest fixbash
cp /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/kube-apiserver.yaml.bak   # outside the directory
vim /etc/kubernetes/manifests/kube-apiserver.yaml                               # fix the one character
journalctl -u kubelet -f | grep -i apiserver                                    # Ctrl-C when it says the pod was created
crictl ps --name kube-apiserver                                                 # Running, ATTEMPT stable
exit
kubectl get nodes                                                               # back in business

In the docs · the tab you get on exam day

Taskts-cp-1
Troubleshooting2%~4 min

context · kubectl config use-context kind-cka

Map the control plane's static Pods

On cka-control-plane the kubelet runs the control-plane components as static Pods. Write to /tmp/cka/static-pods.txt: on the first line, the absolute path of the directory the kubelet on that node reads static Pod manifests from, taken from the kubelet's configuration file rather than from memory; then the names of the control plane's static Pods exactly as kubectl get pods -n kube-system shows them, one per line.
  1. 01

    Set up the scenario

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

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/ts-cp-1/cleanup | bash
Taskts-cp-2
Troubleshooting7%~10 min

context · kubectl config use-context kind-cka

Nothing gets scheduled

The Deployment web in namespace ts-cp was created a few minutes ago and all of its Pods are still Pending; new Pods anywhere in the cluster do not start either. Find the cause on the control-plane node and fix it there. When you are done the Deployment must have 2 Pods Running and the component you repaired must be healthy.

Setup breaks part of your lab on purpose. Run the cleanup step when you finish, or reset the lab.

  1. 01

    Set up the scenario

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

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/ts-cp-2/cleanup | bash
Taskts-cp-3
Troubleshooting7%~10 min

context · kubectl config use-context kind-cka

A Deployment that creates nothing

The Deployment api in namespace ts-cp was created with 3 replicas but has no Pods at all, and kubectl get rs -n ts-cp shows nothing. Find the failing control-plane component on cka-control-plane and repair it. When you are done the Deployment must have 3 Pods Running.

Setup breaks part of your lab on purpose. Run the cleanup step when you finish, or reset the lab.

  1. 01

    Set up the scenario

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

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/ts-cp-3/cleanup | bash

Finished reading?

0 of 3 tasks passed. Readiness counts tasks, so run them before you move on.