CKA Practice: Recover a NotReady Node After a Kubelet Configuration Error
The alert fired eleven minutes ago and has not cleared:
NodeNotReady
node-02conditionReadyisUnknown. Workload availability in namespacewebdegraded:web-canary0/1 available.
node-02 has become NotReady and the workload running on it is affected. Investigate the node and restore it to a healthy Ready state.
The other nodes and the control plane are healthy, and they should stay that way. Whatever went wrong is local to node-02.
The plan:
- Read what the control plane knows, and work out what it cannot know.
- Get the kubelet process running again on the node.
- Get the node reporting Ready to the API server.
- Confirm the workload came back.
Constraints:
- Repair
node-02. Do not delete it, drain it permanently, or replace it. - Do not delete or edit the deployments in the
webnamespace. - Do not recreate the cluster.
- If you cordon the node while you work, uncordon it before you finish.
Three states that look the same from a distance
A NotReady node is a symptom, not a diagnosis. The kubelet passes through three states, and a node can fail at any of them:
- The process runs.
systemctlreportsactive. This says nothing about Kubernetes. - The process initialised. The kubelet parsed its configuration, reached the container runtime, and started its control loops.
- The node reports Ready. The kubelet posts node status to the API server, and keeps posting it.
Troubleshooting a NotReady node means working out which of the three the node failed to reach. The API server only knows what the node last told it, so once a node stops reporting, the next evidence lives on the node itself.
Pre-flight
Start from the cluster view on cplane-01:
kubectl get nodes -o wide
kubectl describe node node-02
kubectl get pods -n web -o wide
Read the Ready condition and its message before you go anywhere else. Each step
below carries its own hints. Open them only as far as you need.
Reading the Ready condition
kubectl describe node node-02 prints the Ready condition with a reason and a
message. A node that stopped reporting shows
reason: NodeStatusUnknown and message: Kubelet stopped posting node status.
Read that literally. The control plane is not saying the node is broken. It is
saying the node went quiet. The node lease in the kube-node-lease namespace
stopped being renewed, so after 40 seconds the node controller marked the node
Unknown. Nothing on the control plane can tell you why. The next evidence is on
the node.
Step 1: Get the kubelet process running
The control plane has told you everything it can. The rest of the evidence is on the node:
ssh node-02
sudo -i
This step passes when systemd reports the kubelet as active. First of the three
states, and the one you fix directly.
Asking systemd about the kubelet
The kubelet is a systemd unit on the node, not a pod:
systemctl status kubelet
systemctl is-active kubelet
Read the state word carefully, because three of them mean different things:
active (running): the process is up. It may still be failing to do its job.activating (auto-restart): it starts, exits, and systemd restarts it. A crash loop.failed: it exited and systemd gave up restarting it.
activating (auto-restart) is the interesting one. Something makes the kubelet
exit very early, every time.
Reading the kubelet journal
journalctl -u kubelet -n 100 --no-pager
In a restart loop the journal repeats the same short block. Find one restart boundary and read the lines above it. Those are the real error. Everything below is systemd starting the process again.
The kubelet loads its configuration file before it contacts the runtime or the API server, so a configuration failure appears at the very top of each attempt and nothing else gets a chance to log.
Where the kubelet's configuration comes from
The kubelet does not read a fixed path. systemd passes it one:
systemctl cat kubelet | grep -- --config
On a kubeadm node the drop-in points at /var/lib/kubelet/config.yaml. That file is
a KubeletConfiguration object, and it is strictly decoded, so a value of the wrong
type stops startup.
node-01 is healthy and has the same file. Comparing the two is a fast way to see
what changed:
ssh node-01 sudo cat /var/lib/kubelet/config.yaml > /tmp/node-01.yaml
diff /tmp/node-01.yaml /var/lib/kubelet/config.yaml
Step 2: Get the node reporting Ready
A running process is not a healthy node. Once the kubelet starts cleanly it
re-registers and begins posting status again, which takes a few seconds. Watch it
flip from cplane-01:
kubectl get nodes -w
You should not need to do anything else here. If the node stays NotReady after the
kubelet has been active for a minute, the process is running but not initialising,
which is the second of the three states.
Still NotReady after the kubelet came up
Separate two cases:
- The kubelet is active but the node never reports. Read the journal again after the restart. A kubelet can start, fail to reach the container runtime or the API server, and keep running while it retries.
- The node reports Ready but nothing schedules on it. That is a cordon, not a
fault.
kubectl get nodesprintsReady,SchedulingDisabled, and.spec.unschedulableistrue. It is cleared by the inverse of whatever set it.
This challenge judges the node on its Ready condition, so a cordoned but healthy
node still passes this step. It will stop you in Step 3, where the workload has to
land back on the node.
Step 3: Confirm the workload recovered
A Ready node does not by itself mean the workload recovered. Confirm that too:
kubectl get pods -n web -o wide
kubectl -n web get deployment web-backend web-canary
web-canary is pinned to node-02, so it can only become available once the node is
both Ready and schedulable.
Related on iximiuz Labs
More on the same terrain, worth doing alongside this one:
- Recover a Broken Static Control-Plane Pod, the sibling challenge in this series. Here
kubectlstill worked, so you could start on the control plane and descend; there the API server itself is down, and you start at the container runtime instead - Klustered: Level Three by Rawkode Academy. Break-and-fix on a sabotaged cluster, where nobody tells you which layer failed
- Diagnose Why a DaemonSet Skips the Control Plane Node by Omkar Shelke. The other direction: the node is fine and the workload still will not land on it
- Provisioning a Kubernetes Cluster with kubeadm by Márk Sági-Kazár. Where
/var/lib/kubelet/config.yamlcomes from in the first place