Node NotReady: Kubernetes-knooppuntfouten diagnosticeren

Een node in NotReady-status is gestopt met het versturen van heartbeats naar het control plane. De kubelet is uitgevallen, onbereikbaar, of meldt actief dat een gezondheidsconditie faalt. Pods op de node worden binnen vijf minuten verwijderd. Dit artikel behandelt hoe je node-condities leest, de oorzaak vaststelt (kubelet-crash, container-runtime-fout, resourcedruk, netwerkpartitie, certificaatverlopen), en de node veilig herstelt of vervangt.

Wat NotReady betekent

Elke Kubernetes-node draait een kubelet die heartbeats stuurt naar het control plane via twee mechanismen. Een lichtgewicht Lease-object in de kube-node-lease namespace wordt elke 10 seconden vernieuwd. Een zwaardere NodeStatus-update schrijft het volledige conditieset elke 5 minuten of wanneer condities veranderen.

De node lifecycle controller bewaakt deze heartbeats. Als er binnen de node monitor grace period (standaard: 50 seconden) geen heartbeat binnenkomt, grijpt de controller in:

  • Ready wordt Unknown: het control plane kan de kubelet helemaal niet bereiken. Typische oorzaken: kubelet gecrasht, node herstart, netwerkpartitie.
  • Ready wordt False: de kubelet draait en rapporteert, maar vertelt het control plane dat er iets mis is (resourcedruk, container runtime down, CNI-fout).

Beide statussen triggeren automatische taints. Unknown voegt node.kubernetes.io/unreachable:NoExecute toe. False voegt node.kubernetes.io/not-ready:NoExecute toe. Kubernetes injecteert automatisch tolerations met tolerationSeconds=300 op elke pod die geen eigen waarde instelt. Pods op een NotReady-node overleven dus 5 minuten voordat het control plane ze verwijdert en elders opnieuw inplant.

Het verschil tussen Unknown en False is belangrijk voor je diagnose. Alle condities Unknown? De kubelet is dood of onbereikbaar. Een specifieke conditie op True (zoals DiskPressure)? De kubelet draait gewoon, maar de node heeft een concreet probleem.

Node-condities lezen

De kubelet rapporteert vijf condities op elke node. kubectl describe node <naam> toont ze onder de Conditions-sectie.

Conditie Gezonde waarde Wat het signaleert als het triggert
Ready True Node kan pods accepteren
DiskPressure False Filesystem heeft weinig ruimte of inodes
MemoryPressure False Node raakt door het beschikbare geheugen heen
PIDPressure False Te veel processen op de node
NetworkUnavailable False CNI-plugin heeft het netwerk niet geconfigureerd

Begin elke diagnose hier:

# Condities voor een specifieke node
kubectl describe node worker-3

# Condities in een clean formaat
kubectl get node worker-3 -o jsonpath='{range .status.conditions[*]}{.type}{": "}{.status}{" -- "}{.message}{"\n"}{end}'

# Events voor de node
kubectl get events --field-selector involvedObject.name=worker-3 --sort-by='.lastTimestamp'

Patroonherkenning. Alle condities Unknown met het bericht "Kubelet stopped posting node status" betekent dat de kubelet compleet plat ligt of dat de node onbereikbaar is. Een enkele conditie op True (bijv. MemoryPressure: True) terwijl Ready op False staat, betekent dat de kubelet draait maar die specifieke resource uitgeput is.

Voorbeeldoutput voor een node met diskdruk:

Conditions:
  Type             Status  Reason                       Message
  ----             ------  ------                       -------
  DiskPressure     True    KubeletHasDiskPressure       kubelet has disk pressure
  MemoryPressure   False   KubeletHasSufficientMemory   kubelet has sufficient memory available
  PIDPressure      False   KubeletHasSufficientPID      kubelet has sufficient PID available
  Ready            False   KubeletNotReady              Node has disk pressure

Resourcedruk diagnosticeren

DiskPressure

De kubelet bewaakt filesystemgebruik tegen harde eviction-drempels. Standaardwaarden: nodefs.available < 10%, nodefs.inodesFree < 5%, imagefs.available < 15%. Bij overschrijding ruimt de kubelet eerst ongebruikte images en dode containers op. Als dat niet genoeg is, verwijdert hij pods.

SSH naar de node (of gebruik kubectl debug node):

# Debug-shell openen op de node
kubectl debug node/worker-3 -it --image=ubuntu --profile=sysadmin
# Binnenin: chroot /host

# Diskruimte controleren
df -h

# Inode-gebruik controleren (inode-uitputting is onzichtbaar voor df -h)
df -i

# Grootste verbruikers vinden
du -sh /var/lib/containerd/* 2>/dev/null
du -sh /var/log/pods/*

# Ongebruikte container-images handmatig opruimen
crictl rmi --prune

Veelvoorkomende oorzaken in de praktijk: containerlogs die zich opstapelen in /var/log/pods/, emptyDir-volumes die onbegrensd groeien, image-layer-accumulatie zonder garbage collection, en inode-uitputting door veel kleine bestanden (zelfs met voldoende blokruimte).

Verificatie: na het opruimen toont kubectl describe node worker-3 binnen een tot twee minuten DiskPressure: False en Ready: True.

MemoryPressure

Standaard harde eviction-drempel: memory.available < 100Mi (sommige distributies hanteren hogere standaarden; controleer je kubelet-configuratie). De kubelet leidt beschikbaar geheugen af van cgroupfs, niet van free -m. inactive_file wordt niet meegeteld als "in gebruik".

# Geheugen op de node controleren
free -h

# Top geheugenverbruikers vinden
ps aux --sort=-%mem | head -20

# Vanuit het control plane: geheugengebruik per pod
kubectl top pod --all-namespaces --sort-by=memory | head -20

Bij MemoryPressure verwijdert de kubelet pods in een specifieke volgorde: eerst pods die boven hun memory request zitten, dan BestEffort, dan Burstable, Guaranteed als laatste. Als het geheugen sneller daalt dan de 10-secondencontrole van de kubelet, schiet de Linux OOM killer er tussendoor en gebruikt oom_score_adj per QoS-klasse. Meer details over hoe geheugenlimits en QoS samenwerken staan in het artikel over OOMKilled.

PIDPressure

Komt minder vaak voor, maar legt een node volledig plat. Als PIDs op zijn, kan geen enkel nieuw proces meer forken. Zelfs simpele diagnostische commando's werken dan niet meer op de node.

# PID-plafond en huidig aantal controleren
cat /proc/sys/kernel/pid_max
ps aux | wc -l

Oplossing: verminder het aantal pods op de node, kill runaway-processen, of verhoog kernel.pid_max via de nodeconfiguratie.

Kubelet en container-runtime-gezondheid

De kubelet draait als systemd-service op de meeste distributies. Zodra hij stopt, begint de 50-secondenaftelling naar NotReady.

# Kubelet-status controleren
systemctl status kubelet

# Recente logs bekijken
journalctl -u kubelet -n 100 --no-pager

# Zoeken naar bekende foutpatronen
journalctl -u kubelet -n 200 | grep -E "Error|PLEG|NetworkPlugin|certificate|x509|runtime|evict"

Veelvoorkomende kubelet-logberichten en hun betekenis:

Logbericht Waarschijnlijke oorzaak
PLEG is not healthy Container runtime traag of geblokkeerd
container runtime is not running containerd of CRI-O is down
failed to run Kubelet: unable to load client CA file TLS-certificaatprobleem
node lease renewal failed Netwerkverbinding met API-server verbroken
x509: certificate has expired Kubelet client-certificaat verlopen

Als de kubelet gewoon gestopt is:

sudo systemctl restart kubelet
sudo systemctl enable kubelet  # zorg dat hij opstart bij boot

Container runtime en PLEG

De kubelet communiceert met de container runtime (containerd, CRI-O) via het Container Runtime Interface-socket. Als de runtime crasht, wordt de PLEG (Pod Lifecycle Event Generator) van de kubelet als ongezond gerapporteerd, en meldt de node NotReady.

# containerd controleren
systemctl status containerd
journalctl -u containerd -n 50

# CRI-socket testen
crictl info
crictl ps   # draaiende containers
crictl pods # pod sandbox-lijst

PLEG wordt als ongezond beschouwd als de relist-cyclus langer duurt dan 3 minuten. Oorzaken: container-runtime-deadlock (meest voorkomend), te veel pods waardoor relists traag worden, CPU-saturatie die de relist-loop uithongert, of disk-I/O-throttling op cloud-VMs.

Cgroup driver mismatch. Als de kubelet systemd gebruikt als cgroup driver maar containerd geconfigureerd is voor cgroupfs, faalt de kubelet. Beide moeten overeenkomen (systemd is aanbevolen voor systemd-gebaseerde distributies):

# Kubelet cgroup driver
grep cgroupDriver /var/lib/kubelet/config.yaml

# containerd cgroup driver
grep -A2 "SystemdCgroup" /etc/containerd/config.toml

Evented PLEG (Kubernetes v1.27+). De EventedPLEG feature gate (beta) vervangt polling door CRI-event-streaming. Het verlaagt CPU-overhead van de kubelet en elimineert PLEG-timeouts door trage relists.

Netwerkverbinding met het control plane

Een node die de API-server niet kan bereiken op poort 6443 stopt met het vernieuwen van zijn Lease. Na 50 seconden verklaart het control plane hem Unknown.

# Vanaf de worker node: API-server-connectiviteit testen
nc -zv 10.0.0.10 6443
curl -k https://10.0.0.10:6443/healthz

# Firewallregels controleren
iptables -L INPUT -n -v
iptables -L OUTPUT -n -v

Een netwerkpartitie brengt een specifiek risico mee: de geisoleerde node blijft zijn pods lokaal draaien, maar het control plane verwijdert ze na 300 seconden en plant ze elders opnieuw in. Voor StatefulSets kan dit split-brain-situaties veroorzaken waarbij dezelfde identiteit op twee nodes tegelijk draait.

NetworkUnavailable-conditie

Anders dan de drukcondities (die door de kubelet gezet worden), wordt NetworkUnavailable gezet door de CNI-plugin. Het betekent dat het netwerk van de node niet geconfigureerd is.

# CNI-pods op de getroffen node controleren
kubectl get pods -n kube-system -o wide --field-selector spec.nodeName=worker-3

# Zoeken naar specifieke CNI-plugins
kubectl get pods -n kube-system | grep -E "calico|flannel|cilium|weave"

# Op de node: controleer of CNI-configuratiebestanden bestaan
ls /etc/cni/net.d/

Veelvoorkomende oplossingen: herstart de CNI DaemonSet-pod (kubectl delete pod -n kube-system <cni-pod-naam>; de DaemonSet maakt hem opnieuw aan), verifieer dat de Pod CIDR niet overlapt met bestaande netwerkranges, en controleer op RHEL/CentOS of NetworkManager niet interfereert met CNI-routes.

Certificaatverlopen

Kubelet client-certificaten verlopen doorgaans na een jaar. Na verlopen kan de kubelet zich niet meer authenticeren bij de API-server. Alle condities springen naar Unknown.

# Certificaatverloopdatum controleren op de node
openssl x509 -enddate -noout -in /var/lib/kubelet/pki/kubelet-client-current.pem

# Kubelet-logs doorzoeken op certificaatproblemen
journalctl -u kubelet --since "1 hour ago" | grep -i "cert\|tls\|x509"

De typische foutmelding: x509: certificate has expired or is not yet valid.

Fix: automatische rotatie inschakelen in /var/lib/kubelet/config.yaml:

rotateCertificates: true
serverTLSBootstrap: true

Fix: wachtende CSRs handmatig goedkeuren:

kubectl get csr
kubectl certificate approve <csr-naam>

Fix: vernieuwen met kubeadm:

sudo kubeadm certs renew all
sudo systemctl restart kubelet

Nodeherstel en drain-procedures

Wanneer herstellen vs vervangen

Herstel wanneer de oorzaak identificeerbaar en oplosbaar is: kubelet herstarten, disk opruimen, certificaat vernieuwen, CNI-pod herstarten. Vervang wanneer de node compleet onbereikbaar is, D-state-processen heeft die niet verdwijnen, of een beeindigde cloud-instance is.

Herstelprocedure

# 1. Cordon de node zodat er geen nieuwe pods gepland worden
kubectl cordon worker-3

# 2. SSH of kubectl debug om de oorzaak op te lossen
#    (disk opruimen, kubelet herstarten, certificaat vernieuwen, etc.)

# 3. Verifieer dat de node terugkeert naar Ready
kubectl describe node worker-3

# 4. Uncordon wanneer Ready
kubectl uncordon worker-3

# 5. Houd de stabiliteit in de gaten
kubectl get nodes -w

Drain-procedure (gepland onderhoud)

kubectl drain verwijdert alle pods op een nette manier, met respect voor PodDisruptionBudgets en termination grace periods.

kubectl drain worker-3 \
  --ignore-daemonsets \
  --delete-emptydir-data \
  --grace-period=60 \
  --timeout=300s

De flags: --ignore-daemonsets slaat DaemonSet-managed pods over (die herstarten automatisch na uncordon). --delete-emptydir-data staat toe om pods met emptyDir-volumes te verwijderen. --grace-period overschrijft de termination grace period van de pod. --timeout kapt de totale draintijd af.

Als een drain blokkeert, controleer PodDisruptionBudgets:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: myapp-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: myapp

Een drain die een PDB schendt, blokkeert tot de conditie is voldaan. Gebruik --disable-eviction alleen als laatste redmiddel.

Geforceerde node-verwijdering (permanent verloren node):

kubectl delete node worker-3

Dit verwijdert het node-object maar verwijdert pods niet netjes. Pods in Terminating-status blijven bestaan tot de garbage collection timer afloopt. Maak een vervangende node aan en laat hem het cluster joinen.

Als je een pod in Pending ziet na het drainen, heeft het cluster mogelijk niet genoeg capaciteit om alle verwijderde workloads opnieuw in te plannen. Controleer resource requests en allocatable capaciteit.

Wanneer escaleren

Verzamel deze informatie voordat je hulp inschakelt:

  • Volledige output van kubectl describe node <node-naam>
  • Output van kubectl get events --field-selector involvedObject.name=<node-naam>
  • Kubelet-logs: journalctl -u kubelet -n 200 (vanaf de node)
  • Container-runtime-logs: journalctl -u containerd -n 100
  • Output van kubectl get nodes -o wide en kubectl top nodes
  • kube-system pods op de node: kubectl get pods -n kube-system -o wide --field-selector spec.nodeName=<node-naam>
  • Kubernetes-versie: kubectl version
  • Of het probleem consistent of intermitterend is
  • Of de node recent geupgrade, gepatcht of geconfigureerd is

Hoe herhaling te voorkomen

  • Schakel kubelet certificate auto-rotation in (rotateCertificates: true) om uitval door verlopen certificaten te voorkomen.
  • Stel resource requests nauwkeurig in. Overbelaste nodes raken sneller in MemoryPressure en DiskPressure.
  • Monitor node-condities met Prometheus en kube-state-metrics. De metric kube_node_status_condition slaat alarm voordat een mens het merkt.
  • Installeer Node Problem Detector als DaemonSet om kernel-level problemen te signaleren (deadlocks, read-only filesystems, frequent kubelet-herstarts) die de kubelet zelf niet rapporteert.
  • Configureer logrotatie voor containerlogs om DiskPressure door logaccumulatie te voorkomen.
  • Drain een node per keer tijdens onderhoud, en verifieer altijd dat PodDisruptionBudgets op hun plek staan voor kritieke workloads.

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.