CKA Practice: Recover a Broken Static Control-Plane Pod
12:41 in the incident channel:
@here kubectl is failing against the training cluster:
The connection to the server 172.16.0.2:6443 was refused. Deploys are blocked and the dashboard is blank. The VMs are all up, and nobody changed anything (they say). Can someone take a look?
The control plane on cplane-01 is unhealthy. Cluster operations are failing and
workloads are affected. Restore the control plane and return the cluster to a healthy
state.
The plan:
- Work out what is broken without the API server to help you.
- Get the API server answering again.
- Confirm the rest of the control plane came back with it.
- Confirm the nodes and the workload are healthy.
Constraints:
- Repair the control plane. Do not recreate the cluster. A rebuild takes the evidence with it.
- Do not delete or edit the
web-backenddeployment in thewebnamespace. - Keep the existing cluster CA. Reissuing it fails the attempt.
Pre-flight
Work on cplane-01 as root (sudo -i).
kubectl talks to the API server, and the API server is what is missing, so every
kubectl command will fail for the whole first half of this challenge. Everything you
need is on the node:
systemctlandjournalctlanswer for the kubelet, which is a plain systemd unit.crictltalks to the container runtime over its own socket, so it keeps working./etc/kubernetes/manifests/and/var/log/are files on disk.
Each step below carries its own hints. Open them only as far as you need.
What runs the control plane
On a kubeadm cluster the control plane components are not scheduled. The kubelet on
cplane-01 runs them directly from manifest files in /etc/kubernetes/manifests/.
They are called static pods, and the kubelet owns their whole lifecycle.
That has two consequences worth holding on to:
- The kubelet does not need the API server to run them. If the kubelet is up, it is still trying to run the control plane right now.
- The mirror pods you normally see with
kubectl get pods -n kube-systemare only read-only copies published to the API. With the API down, they do not exist as far as you are concerned.
So the first question is whether the kubelet is healthy: systemctl status kubelet.
Step 1: Get the API server answering again
Find the component that is failing, work out why from its own output, and correct it.
This step passes when kubectl get --raw=/readyz returns ok.
Seeing containers without the API
crictl speaks to the container runtime over its socket, so it works with the API
server down:
crictl ps # running containers
crictl ps -a # also containers that have exited
Compare the two. A component that starts and dies keeps appearing in crictl ps -a
with a climbing attempt count, and never appears in crictl ps.
crictl pods is a different list. It shows pod sandboxes, which hold the network
and IPC namespaces. The container runs inside the sandbox and has its own ID. The two
lists never share an ID, and most crictl subcommands take one or the other, not
both. Reach for crictl ps, not crictl pods.
On this playground crictl prints two warnings on every call, because
/etc/crictl.yaml does not exist and it falls back to probing the default sockets. It
still works. Silence it once:
crictl config --set runtime-endpoint=unix:///run/containerd/containerd.sock
Reading the logs of a container that will not stay up
crictl logs <container-id>
logs takes a container ID. Passing it a pod ID from crictl pods, or a pod name,
returns NotFound, which reads like the container is gone when it is only the wrong
identifier. Take the ID from crictl ps -a, or skip the copying:
crictl logs "$(crictl ps -a --name kube-apiserver -q | head -1)"
A process that exits immediately usually explains itself in its last line.
The kubelet garbage-collects exited containers, so that ID can disappear mid-incident. The logs on disk outlive it:
ls /var/log/pods/kube-system_kube-apiserver-*/
tail -50 /var/log/containers/kube-apiserver-*.log
Know where the kubelet writes container logs. It is the more reliable path.
From the error to the manifest
The component is failing on its own configuration, before it does any real work. Read its static pod manifest and compare it line by line against what the error message named:
grep -n -- '--etcd' /etc/kubernetes/manifests/kube-apiserver.yaml
Applying the fix
Edit the manifest in place. You do not need to restart anything by hand.
The kubelet watches /etc/kubernetes/manifests/ and also rescans it on a timer set by
fileCheckFrequency, 20 seconds by default, so an edit is picked up shortly after you
save it. Changing the file is how you deploy a change to a static pod.
Give it a moment after that. An API server that answers requests is not yet an API
server that is ready: /readyz reports failures for a few seconds while its
post-start hooks finish.
Step 2: Confirm the control plane came back whole
The API server is one of four components on this node. Check that all of them are running, and meet the objects that represent them:
kubectl get pods -n kube-system -l tier=control-plane
Mirror pods, and why kubectl cannot manage them
The control plane pods are back in that listing now. They are mirror pods: the kubelet publishes a read-only copy of each static pod to the API server so that you can see it. The API server does not manage them. The practical difference:
kubectl -n kube-system delete pod kube-apiserver-cplane-01
That command appears to succeed. It deletes the mirror, the kubelet notices, and the mirror comes straight back. It never touched the running container. To change or stop a static pod you edit or move its manifest file, which is what you did in Step 1.
In the exam: kubectl edit on a mirror pod is rejected, and
kubectl delete on one is close to useless.
Step 3: Confirm the nodes and the workload
Getting the process running is not the same as the cluster being healthy:
kubectl get nodes
kubectl -n web get deployment web-backend
The workload never stopped serving during the outage. This step confirms it, and confirms the kubelets have re-reported since the API server returned.
Here kubectl was dead, so the investigation had to start at the runtime. When the
API server is up and a single node has gone quiet, the investigation runs the other
way, from the control plane down. The sibling challenge in this series,
Recover a NotReady Node After a Kubelet Configuration
Error,
starts from that side.
Related on iximiuz Labs
Other people's work on the same terrain, worth doing alongside this one:
- Klustered: Level Three by Rawkode Academy. Break-and-fix on a sabotaged cluster, the genre this challenge belongs to
- Take and Restore an etcd Snapshot on a Kubernetes Cluster by Omkar Shelke. The other half of control plane recovery: the data, not the process
- Kubernetes the (Very) Hard Way by Márk Sági-Kazár. Assembling the control plane by hand, which makes a static pod manifest much easier to read
- Kubernetes Debugging with DebugBox: Right-Sized Containers for Every Scenario by Muhammad Ibtisam. Tooling for the moment
kubectl execis not available to you