Node Debugging — NotReady, Resource Pressure, kubelet
Tại sao quan trọng ở Production
Pods tidak bisa schedule kalau nodes tidak healthy. Ketika node problem:
- Pods pending atau evicted
- Existing pods bisa tiba-tiba terminate
- Entire cluster capacity reduced
- Hard to spot karena node problem vs pod problem berbeda
Node di GKE adalah Compute Engine VM. Problem bisa dari:
- Kubernetes layer (kubelet, containerd)
- OS layer (kernel, systemd, disk)
- Hardware (network, storage, CPU)
- GCP layer (VM issue, disk performance)
Anda perlu methodical approach untuk narrow down layer mana yang broken.
Internal Model: Node Health State Machine
Node Status dan Conditions
Node punya status yang kubelet manage:
status:
phase: Running # deprecated
conditions:
- type: Ready
status: "True"
reason: "KubeletReady"
message: "kubelet posting ready status"
- type: MemoryPressure
status: "False"
- type: DiskPressure
status: "False"
- type: PIDPressure
status: "False"
- type: NetworkUnavailable
status: "False"Ready=True: node ready to accept pods. Scheduling work normally.
Ready=False atau Unknown: node not ready. Kubelet stop accepting new pods. Existing pods may be evicted.
Node NotReady Root Causes
Kubelet report NotReady kalau:
- Kubelet process down (cannot connect to node for 5 min)
- Kubelet crash loop (keep restarting)
- Container runtime fail (containerd down, cannot launch containers)
- Cgroup system fail (cannot manage resources)
- Memory/Disk pressure extreme (cannot allocate resources)
- Network interface down (kubelet cannot reach API server)
Kubelet Architecture for Node Health
Kubelet pada node background task:
1. Heart Beat: every 10 seconds, send NodeStatus to API server
- Report Ready condition
- Report resource available (allocatable)
- Report conditions (MemoryPressure, DiskPressure, etc.)
2. Watch kubelet itself:
- Monitor PID count (if too high → PIDPressure=True)
- Monitor disk usage (if >85% → DiskPressure=True)
- Monitor memory available (if <100Mi free → MemoryPressure=True)
- Monitor network interface (if down → NetworkUnavailable=True)
3. If heartbeat fail (cannot reach API server):
- kubelet keep trying every 5 seconds
- After 5 min without successful heartbeat → node status Unknown
- Controller manager mark node NotReady (after additional delay)Timing important: Node don't go NotReady immediately. Delay terjadi untuk prevent flapping (quick up-down-up).
Container Runtime Interface
Kubelet not directly manage containers. Kubelet call container runtime API:
kubelet ←→ CRI (Container Runtime Interface)
├─ CreateContainer
├─ StartContainer
├─ StopContainer
├─ RemoveContainer
└─ InspectContainer
Container Runtime (containerd/docker):
- Allocate resources
- Mount volumes
- Setup networking
- Run processJika container runtime unreachable atau broken → kubelet cannot manage pods → Node go NotReady.
Debugging Node NotReady
Step 1: Verify Node is Actually NotReady
kubectl get nodes
# Look untuk STATUS column
# "NotReady" or "Ready"
# If NotReady, check when it happened
kubectl describe node <node-name>
# Look untuk "Conditions" section
# Check "LastHeartbeatTime", "LastTransitionTime"
# Example output:
# Conditions:
# Ready: False
# LastHeartbeatTime: Wed, 24 Jun 2025 10:30:45 +0000
# LastTransitionTime: Wed, 24 Jun 2025 10:30:15 +0000Step 2: SSH to Node, Check kubelet
ssh <node-ip>
# Check kubelet process
sudo systemctl status kubelet
# Look untuk:
# - Is service running?
# - Exit code (if stopped)?
# - Recent restart?
# Check kubelet logs
sudo journalctl -u kubelet -n 50 --no-pager
# Look untuk error messages dari last 50 lines
# Specific error patterns:
# - "cannot reach API server" → network problem
# - "cannot connect container runtime" → containerd down
# - "cgroup error" → resource problem
# - "disk full" → storage issueStep 3: Check Container Runtime (containerd)
GKE use containerd as container runtime:
ssh <node-ip>
# Check containerd service
sudo systemctl status containerd
# Is it running?
# Check containerd logs
sudo journalctl -u containerd -n 50 --no-pager
# Try connect to containerd
sudo crictl ps
# If work: containerd responsive
# If error "cannot connect": containerd socket problem
# Check containerd socket
ls -la /run/containerd/containerd.sock
# File should exist and be writable by kubelet userStep 4: Check Node Resources
Memory Pressure
ssh <node-ip>
# Check free memory
free -h
# Total, Used, Free, Available
# If Available < 100Mi → likely MemoryPressure=True
# Check page cache
grep Cached /proc/meminfo
# Check swap
swapon --show
# If swap active and high usage → might be memory pressure
# Find what's using memory
top -b -n 1 -o %MEM | head -20
# Top 20 processes by memory usageIf memory pressure:
Check if pods consuming too much:
bashkubectl top pods --all-namespaces | sort -k3 -rn | head -20Check if system pods (kubelet, containerd) leak memory:
bashps aux | grep kubelet # VSZ column: virtual memory # RSS column: resident memory ps aux | grep containerdSolution:
- Evict some pods (scale down deployments)
- Increase node memory (GCP: change machine type → need drain node first)
- Find memory leak in application (heap dump, check for OOMKilled)
Disk Pressure
ssh <node-ip>
# Check disk usage
df -h
# Look untuk "/" (root filesystem)
# If Used > 85% → likely DiskPressure=True
# Check what's using disk
du -sh /*
# Largest directories at root level
# Specific problem areas:
du -sh /var/lib/kubelet/pods
# Pod volumes mounted here, could be large
du -sh /var/lib/containerd
# Container images and storage
# Check inode usage (if inode exhausted → "disk full" error)
df -i
# Look untuk "IUsed%" > 85%If disk pressure:
Clean up old images:
bash# Via kubelet configuration: imageGCLowThresholdPercent=50, imageGCHighThresholdPercent=80 # kubelet auto-clean old images when usage high # Manual cleanup sudo crictl rmi --allClean up pods/containers:
bashsudo crictl ps -a --quiet | xargs -I {} sudo crictl rm {}Check for large log files:
bashfind /var/log -type f -size +100M # Rotate or delete old logs
CPU / Process Issues
ssh <node-ip>
# Check CPU usage
top -b -n 1 | head -20
# Top processes
# Check CPU throttling (if using cgroups v2)
grep cpu.stat /proc/cgroups
# Check PID usage
cat /proc/sys/kernel/pid_max
ps aux | wc -l
# If close to limit → PIDPressure=TrueStep 5: Check Network Connectivity
ssh <node-ip>
# Check network interface up
ip link show
# eth0 should be UP
# Check routes
ip route
# Should have route to API server
# Test connectivity to API server
curl -v https://<api-server-ip>:443/healthz
# If timeout → network unreachable
# Check DNS
cat /etc/resolv.conf
nslookup kubernetes.default.svc.cluster.local
# Check firewall
sudo iptables -L -n | head -30
# Look untuk DROP rules yang might block API trafficStep 6: Check Kubelet Configuration
ssh <node-ip>
# Kubelet config usually at:
cat /etc/kubernetes/kubelet/kubelet-config.yaml
# or from docker inspect (if using kubeadm/GKE):
sudo ps aux | grep kubelet
# See flag values
# Key config to check:
# - --max-pods: max pods per node (default 110)
# - --cgroup-driver: cgroup driver match with containerd
# - --kubelet-cgroups: cgroup pathSpecific Node Failure Modes
Failure Mode 1: Containerd Crash
kubectl describe node
# Conditions: Ready=False, reason="NodeStatusUnknown"
ssh <node>
sudo systemctl status containerd
# Status: inactiveRoot cause:
- OOMKilled by kernel
- Crash during image pull
- Configuration wrong
- Kernel upgrade break containerd
Debugging:
# Check why it crashed
sudo journalctl -u containerd -n 100 --no-pager
# Restart containerd
sudo systemctl restart containerd
# Verify it come up
sudo systemctl status containerd
sudo crictl psIf containerd keep crashing:
Check containerd config:
bashcat /etc/containerd/config.tomlCheck for corrupted state:
bashsudo rm -rf /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs sudo systemctl restart containerdIf all fail, recreate node:
bashgcloud container node-pools update <pool> \ --node-taints="" \ --num-nodes 0 # drain first # Then increase back
Failure Mode 2: Kubelet Disk Space Exhausted
ssh <node>
df -h /
# /dev/sda1 50G 49G 1G 98% /
# kubelet cannot create new pod (cgroup allocation fail)Quick fix:
# Clean up container runtime
sudo crictl rmi --all
# Or clean specific pod volumes
sudo rm -rf /var/lib/kubelet/pods/*
# This delete all pod data! Use carefully
# Restart kubelet
sudo systemctl restart kubeletLong term fix:
- Increase node disk size (GCP: increase boot disk size)
- Implement storage cleanup strategy (image GC, log rotation)
Failure Mode 3: Network Interface Down
ssh <node>
ip link show
# eth0: DOWN (not UP)
# Or check kubelet logs
sudo journalctl -u kubelet | grep -i network
# "error: failed to setup network: ..."Root cause:
- NIC physically unplugged (rare in cloud)
- Network driver issue
- GCP network configuration broken
Debug:
# Check NIC status
ethtool eth0
# Check carrier (on/off)
# Check GCP side
gcloud compute instances describe <instance-name>
# Check network interface status
# Reboot node (might help with driver issue)
# But this will cause downtime! Do carefully
sudo reboot
# Or recreate node via GKE API
gcloud container nodes create ...Debugging Tools
kubectl top for Node Resource Usage
kubectl top nodes
# Shows CPU and memory usage per node
# Compare with node capacity
kubectl describe nodes | grep "Allocatable" -A 10Cloud Logging for kubelet/containerd Logs
# Query kubelet logs
gcloud logging read \
'resource.type="k8s_node" AND component="kubelet"' \
--limit 50
# Query for specific errors
gcloud logging read \
'resource.type="k8s_node" AND jsonPayload.level="ERROR"' \
--limit 50
# Query containerd logs
gcloud logging read \
'resource.type="k8s_node" AND component="containerd"' \
--limit 50Node Auto-Recovery
GKE bisa auto-repair nodes if configured:
gcloud container node-pools describe <pool>
# Look untuk "Management" → "autoRepair: true"
# If enabled, GKE detect unhealthy nodes and recreate
# Takes ~5-10 minutesGCP-Specific Node Issues
GCP Compute Engine Instance Issues
Node di GKE adalah Compute Engine VM. Problem bisa from GCP side:
# Check instance health
gcloud compute instances describe <instance-name>
# Look untuk "cpuPlatform", "machineType", "status"
# Check if instance had recent restart
gcloud compute operations list --filter="targetResource:<instance-name>"
# Check instance serial port output (early boot logs)
gcloud compute instances get-serial-port-output <instance-name> --port 1GCP Disk Performance Issues
If node slow (not NotReady, just sluggish):
ssh <node>
# Check disk I/O
iostat -x 1 5
# Look untuk %util, await time
# Check if IOPS limit reached
# Standard persistent disk: ~3 IOPS per GB
# SSD persistent disk: ~30 IOPS per GB
# Solution: increase disk size (auto-increase disk IOPS)
# Or use SSD disk (faster I/O)GCP Zone Maintenance / Disruption
Sometimes node NotReady because of GCP zone maintenance:
# Check maintenance schedule
gcloud compute zones list --filter="name:<zone>" --format="value(name, scheduledMaintenance)"
# If scheduled maintenance → node might restart soon
# GKE might auto-reschedule pods
# To avoid maintenance:
# 1. Use different zone (geo-redundancy)
# 2. Schedule maintenance window when traffic low
# 3. Use node affinity to spread replicasOperational Practices
1. Node Taint & Drain for Maintenance
Before reboot node (upgrade OS, etc):
# Cordon node (stop new pods from scheduling)
kubectl cordon <node>
# Drain existing pods (move to other nodes)
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
# After maintenance, uncordon
kubectl uncordon <node>2. Monitor Node Health
# Check via Prometheus / Cloud Monitoring
# Alert on:
# - node.status.conditions[Ready] != True
# - node.status.allocatable[memory] < threshold
# - kubelet restarts in last hour > 53. Set Resource Requests Properly
If every node OOMKilled:
# Check if pods have resource requests
kubectl get pods -o yaml | grep -A3 "resources:"
# Pods without request → kubelet cannot enforce limits
# Set resource requests to prevent overcommit