kubectl get nodes NotReady? kubelet·CNI·디스크 6원인 5분 복구
K8s_Troubleshooting_Guide 9편
새벽에 알람이 울리고 kubectl get nodes를 쳤더니 워커 한 대가 NotReady. 그 위에 떠 있던 Pod들이 한꺼번에 Terminating을 거쳐 사라지고, 서비스 응답이 흔들리기 시작합니다. 이 글의 목표는 단 하나입니다. 이 화면을 보고 5분 안에 6가지 원인 중 하나로 분기하고, Pod를 안전하게 대피시킨 뒤 노드를 살려내는 것. 개념 설명은 최소화하고, "이 메시지가 보이면 이 명령" 식으로 바로 갑니다.
1. 패닉 멈추고 봐야 할 첫 화면
먼저 노드가 정말 NotReady인지, 언제부터인지 확인합니다.
kubectl get nodes -o wide
kubectl get node <node> -o jsonpath='{.status.conditions}' | jqNotReady가 떴다고 해서 무조건 노드가 죽은 건 아닙니다. kubelet 하트비트가 잠깐 끊긴 일시적 상태일 수도 있고, 디스크가 꽉 차서 kubelet이 스스로 Ready를 False로 내린 것일 수도 있습니다. 그래서 가장 먼저 Conditions를 읽어 원인 후보를 좁히는 것이 핵심입니다.
2. Conditions 판독표 — 5초 만에 후보 좁히기
kubectl describe node <node> | grep -A8 Conditions출력의 Reason/Message 원문을 아래 표와 대조하면 바로 분기됩니다.
| Conditions / 메시지 원문 | 가리키는 원인 | 다음에 칠 명령 |
|---|---|---|
Ready=Unknown, NodeStatusUnknown, "Kubelet stopped posting node status" | kubelet 다운 또는 노드↔API 단절 | systemctl status kubelet / curl -k https://<API>:6443/healthz |
Ready=False, KubeletNotReady, "container runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized" | CNI 미초기화 | ls /etc/cni/net.d / journalctl -u kubelet | grep -i cni |
DiskPressure=True, "kubelet has disk pressure" | 디스크 압박 (이미지/로그 폭증) | df -h / df -i / crictl rmi --prune |
MemoryPressure=True, "kubelet has insufficient memory available" | 메모리 압박 | free -m / kubectl top node |
PIDPressure=True, "kubelet has insufficient PID available" | 프로세스/스레드 폭주 | ps -eLf | wc -l |
Ready=False, "failed to get container runtime" / containerd 무응답 | 컨테이너 런타임 다운 | systemctl status containerd / crictl info |
참고로 kubelet은 기본적으로
DiskPressure를 imagefs/nodefs 가용량 15% 미만(evictionHard기본값)에서,MemoryPressure를memory.available<100Mi에서 True로 올립니다. 임계에 닿으면 노드가 NotReady로 전환되고 Pod 축출이 시작됩니다.
3. 원인 6분기 진단·복구
① kubelet 다운
systemctl status kubelet
journalctl -u kubelet -factive (running)이 아니면 바로 재기동합니다.
systemctl restart kubelet && systemctl status kubelet② CNI plugin not initialized
가장 흔한 함정입니다. CNI 설정/바이너리가 비어 있으면 위 메시지가 뜹니다.
journalctl -u kubelet | grep -i cni
ls /etc/cni/net.d # 비어 있으면 CNI conf 미배포
ls /opt/cni/bin # bridge, loopback 등 바이너리 존재 확인설정이 비어 있다면 CNI DaemonSet을 재배포해 conf를 다시 깔아줍니다.
kubectl rollout restart ds <calico-node|cilium|aws-node> -n kube-system③ 디스크 / 메모리 압박
df -h # nodefs 사용률
df -i # inode 고갈도 NotReady 유발
free -m디스크가 원인이면 안 쓰는 이미지와 로그부터 비웁니다.
crictl rmi --prune
journalctl --vacuum-size=200M확보 후 kubelet이 자동으로 DiskPressure를 해제하고 Ready로 돌아옵니다.
④ 컨테이너 런타임 다운 (containerd)
dockershim 제거 이후 표준은 containerd입니다.
systemctl status containerd
crictl ps
crictl info무응답이면 재기동 → kubelet 순으로 복구합니다.
systemctl restart containerd && systemctl restart kubelet⑤ 인증서 / 노드 등록 문제
journalctl -u kubelet | grep -i certificate
kubectl get csrPending CSR이 보이면 승인합니다.
kubectl certificate approve <csr-name>⑥ 노드 ↔ API 네트워크 단절
노드에 SSH로 들어가 API 서버 도달성을 확인합니다.
curl -k https://<API_SERVER>:6443/healthz # ok 안 나오면 네트워크/SG/방화벽
ss -tnp | grep 6443보안그룹·라우팅·DNS를 점검하세요. 노드는 멀쩡한데 컨트롤플레인과 못 닿는 경우가 의외로 많습니다.
4. Pod 안전 대피와 노드 복귀
복구 작업 전에 새 Pod가 그 노드로 더 안 떨어지게 막고, 기존 Pod를 비웁니다.
kubectl cordon <node>
kubectl drain <node> --ignore-daemonsets --delete-emptydir-data
# 복구 작업 수행 후
kubectl uncordon <node>drain이 PDB(PodDisruptionBudget)나 emptyDir 때문에 막히면, PDB의 minAvailable을 잠시 조정하거나 --disable-eviction(API 미지원 환경)으로 강제 삭제합니다. 단, 강제 삭제는 데이터 유실 위험이 있으니 stateful 워크로드에는 신중히 적용하세요.
실무 한마디
제 경험상 새벽 NotReady의 70%는 디스크 압박과 CNI 미초기화 둘로 수렴합니다. 그래서 알람이 오면 저는 describe Conditions를 본 뒤 묻지도 따지지도 않고 df -h와 ls /etc/cni/net.d부터 칩니다. 이 두 줄로 대부분 분기가 끝나고, 나머지 시간은 drain하면서 천천히 봅니다. EKS·GKE 같은 관리형은 노드 auto-repair가 NotReady 노드를 알아서 교체해 주지만, 셀프매니지드 클러스터는 결국 사람이 위 6단계를 직접 돌려야 한다는 차이를 기억해 두세요.
결론: NotReady 5분 복구 체크리스트
kubectl describe nodeConditions로 원인 후보 좁히기- kubelet / containerd 상태 확인 → 필요 시 restart
- CNI conf·bin 존재 확인 → DaemonSet rollout restart
df -h·df -i·free -m로 압박 여부 확인 → 이미지/로그 정리- CSR·API healthz 확인
cordon→drain→ 복구 →uncordon
자주 묻는 질문 (FAQ)
Q. 잠깐 NotReady였다가 알아서 Ready로 돌아왔는데 왜 그런가요?
A. kubelet의 하트비트(nodeStatusUpdateFrequency, 기본 10초)가 일시 지연되면 컨트롤러가 node-monitor-grace-period(기본 40초) 후 Ready=Unknown으로 표시합니다. GC, 일시적 부하, 네트워크 지터로 하트비트가 밀렸다 회복되면 자동으로 Ready로 복귀합니다. 반복된다면 노드 부하나 etcd/API 지연을 의심하세요.
Q. 노드가 NotReady 되자마자 Pod가 다 빠지는 걸 막을 수 있나요?
A. 노드가 NotReady가 되면 node.kubernetes.io/not-ready(또는 unreachable) taint가 붙고, 기본 tolerationSeconds: 300 후 Pod가 축출됩니다. 일시 장애에 민감하지 않은 워크로드는 toleration의 tolerationSeconds를 늘려 즉시 축출을 늦추거나, 반대로 빠른 페일오버가 필요하면 값을 줄여 조정합니다.
Q. CNI plugin not initialized인데 DaemonSet은 Running입니다?
A. Pod는 Running이어도 /etc/cni/net.d에 conf가 안 깔렸을 수 있습니다. ls /etc/cni/net.d와 ls /opt/cni/bin을 직접 확인하고, 비어 있으면 rollout restart로 conf 재배포를 강제하세요.
다음 편(10편)에서는 CrashLoopBackOff와 OOMKilled — Pod가 무한 재시작할 때의 원인 추적을 다룹니다.
이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.
댓글
불러오는 중...