Published on

EKS kubelet NotReady - PLEG is not healthy 7가지

Authors

서버가 멀쩡해 보이는데 EKS 노드가 갑자기 NotReady로 떨어지고, kubelet 로그에 PLEG is not healthy가 반복되면 운영자는 대개 두 가지를 동시에 겪습니다. (1) 워크로드가 다른 노드로 쏠리며 장애가 연쇄적으로 커지고, (2) 원인이 “쿠버네티스 내부”인지 “노드/런타임/스토리지”인지 감이 안 잡혀 복구 시간이 길어집니다.

PLEG(Pod Lifecycle Event Generator)는 kubelet이 컨테이너 런타임(containerd/CRI-O/Docker 등)에서 Pod/컨테이너 상태 변화를 주기적으로 관찰해 kubelet 내부 상태를 갱신하는 핵심 루프입니다. 이 루프가 일정 시간 이상 멈추거나 지연되면 kubelet은 노드 상태를 안전하게 유지할 수 없다고 판단해 NotReady로 전환시키고, 이벤트/로그에 PLEG is not healthy를 남깁니다.

이 글은 EKS(특히 managed node group, Bottlerocket/AL2)에서 실무적으로 가장 자주 맞닥뜨리는 7가지 원인을 증상 → 확인 명령 → 해결 순서로 정리합니다.

> 참고: 노드가 NotReady로 떨어지면 트래픽 계층에서도 5xx가 동반되는 경우가 많습니다. 애플리케이션/로드밸런서 관점의 빠른 체크는 AWS ALB 502·504 난사 - 원인별 해결 체크리스트도 함께 보시면 원인 분리가 빨라집니다.

0) 먼저: “PLEG가 왜 아픈지” 5분 안에 범위 좁히기

노드/이벤트에서 1차 힌트 얻기

# 노드 상태/컨디션
kubectl describe node <node-name>

# 특정 노드에 뜨는 이벤트만 빠르게
kubectl get events -A --field-selector involvedObject.kind=Node,involvedObject.name=<node-name> \
  --sort-by=.lastTimestamp

KubeletNotReady와 함께 아래 유형이 자주 보입니다.

  • PLEG is not healthy: pleg was last seen active ... ago; threshold is ...
  • Container runtime is down
  • ImageGCFailed, DiskPressure
  • failed to get pod sandbox status, rpc error: code = DeadlineExceeded

kubelet / containerd 로그 확인 (EKS 노드 OS별)

Amazon Linux 2(대부분의 EKS AMI):

sudo journalctl -u kubelet -n 300 --no-pager
sudo journalctl -u containerd -n 300 --no-pager

Bottlerocket:

# admin container 들어간 뒤
sudo sheltie
journalctl -u kubelet -n 300 --no-pager
journalctl -u containerd -n 300 --no-pager

이제부터는 원인별로 “어디가 막혔는지”를 파고듭니다.


1) containerd/CRI 응답 지연(데드락/스톨)로 PLEG가 타임아웃

대표 증상

  • kubelet: PLEG is not healthy 반복
  • kubelet: failed to list pod sandbox / Get ... runtime service ... DeadlineExceeded
  • containerd 로그에 grpc 지연, snapshotter 관련 에러가 섞임

확인

# 컨테이너 런타임이 살아있는지
sudo systemctl status containerd

# CRI 레벨에서 응답이 느린지
sudo crictl info
sudo crictl ps
sudo crictl pods

# containerd가 과도하게 막혀있는지(스레드/FD/CPU)
sudo top -H -p $(pidof containerd)
sudo ls -l /proc/$(pidof containerd)/fd | wc -l

crictl ps 자체가 수십 초 이상 멈춘다면 PLEG가 관찰 루프를 돌기 어렵습니다.

해결

  1. 가장 빠른 복구: containerd 재시작(단, 노드에서 컨테이너가 재시작될 수 있음)
sudo systemctl restart containerd
sudo systemctl restart kubelet
  1. 재발 방지:
  • EKS AMI/Bottlerocket 버전 업(런타임 버그/커널 이슈가 원인인 경우가 많음)
  • 노드의 CPU/메모리 여유 확보(과도한 밀집 스케줄링 완화)
  • 큰 이미지/레이어가 많은 워크로드가 동시에 뜨는 패턴이면 프리풀(pre-pull) 또는 롤링 전략 조정

2) 디스크 압박(DiskPressure) + 이미지/컨테이너 폭증으로 PLEG가 느려짐

PLEG는 런타임에서 상태를 “리스트업”하는데, 디스크가 꽉 차거나 inode가 고갈되면 containerd의 메타데이터/스냅샷 접근이 느려지며 연쇄적으로 PLEG가 타임아웃 납니다.

대표 증상

  • 노드 컨디션에 DiskPressure=True
  • kubelet: ImageGCFailed, Failed to garbage collect required amount of images
  • containerd: snapshotter 에러, no space left on device

확인

kubectl describe node <node-name> | sed -n '/Conditions:/,/Addresses:/p'

# 용량/inode
sudo df -h
sudo df -i

# containerd 스토리지(대개 /var/lib/containerd)
sudo du -sh /var/lib/containerd
sudo du -sh /var/lib/kubelet

해결

  • 즉시 조치: 불필요 이미지/컨테이너 정리(가능하면 drain 후)
# 노드에서 안전하게 작업하려면 먼저 드레인
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data

# 런타임 정리(환경에 따라 다름)
sudo crictl rmi --prune
sudo crictl rm --all

# 이후 노드 복귀
kubectl uncordon <node-name>
  • 근본 해결:
    • 노드 루트 볼륨(EBS) 증설 또는 별도 볼륨에 containerd 데이터 디렉터리 분리
    • 이미지 태그 정책 개선(무한 태그/캐시 누적 방지)
    • 대규모 배포 시 동시성 낮추기(maxUnavailable 조정)

> 이미지 풀 실패/인증 이슈가 동반되면 디스크 문제와 혼동되기도 합니다. ECR 인증/IRSA/imagePullSecrets 관점은 Kubernetes ImagePullBackOff 401 - ECR·IRSA·imagePullSecrets에서 함께 점검하세요.


3) inode 고갈(파일 수 한도)로 런타임 메타데이터가 병목

용량은 남아 있는데도 no space left on device가 뜨는 전형적인 케이스가 inode 고갈입니다. 로그/캐시/작은 파일이 폭증하는 워크로드(프록시, 빌드, 임시파일 생성)가 원인인 경우가 많습니다.

확인

sudo df -i

# inode 많이 쓰는 경로 찾기(시간 걸릴 수 있음)
sudo find /var/lib -xdev -type f | wc -l
sudo find /var/log -xdev -type f | wc -l

해결

  • 로그 로테이션/보존 기간 축소
  • 임시파일 생성 경로를 emptyDir(메모리/디스크)로 분리
  • 노드 OS/AMI에 맞춰 journald 보존 설정 조정

4) cgroup/리소스 압박(CPU starvation)으로 kubelet/PLEG 스케줄링이 밀림

PLEG 자체는 “루프”이기 때문에 CPU가 심각하게 부족하거나 런타임/커널 스레드가 과도하게 바쁘면 일정 주기로 돌지 못합니다. 특히 노드에 과밀하게 파드를 올리거나, CPU limit/requests 설계가 부정확하면 kubelet이 런타임과 통신할 CPU 시간을 확보하지 못합니다.

대표 증상

  • 노드 Load average 급등
  • kubectl top node에서 CPU 90~100% 지속
  • kubelet 로그에 PLEG took too long 류 메시지

확인

kubectl top node <node-name>

# 노드에서
uptime
mpstat -P ALL 1 5
sudo top

# kubelet/containerd가 CPU를 못 받는지
sudo ps -eo pid,comm,%cpu,%mem,stat,ni,pri --sort=-%cpu | head

해결

  • 과밀 스케줄링 완화: 노드 증설/클러스터 오토스케일러 튜닝
  • requests/limits 재설계(특히 CPU requests를 현실적으로)
  • 시스템 데몬의 리소스 보호(가능하면 system-reserved/kube-reserved 설정)

EKS managed node group에서는 부트스트랩 인자에 kubelet 설정을 넣어 조절할 수 있습니다(환경에 따라 user-data/launch template 사용).


5) 스토리지/파일시스템 I/O 지연(EBS 성능, fs freeze)로 런타임 호출이 느려짐

containerd는 스냅샷/레이어/메타데이터 접근에 디스크 I/O를 강하게 사용합니다. EBS burst credit 소진(gp2)이나 gp3 IOPS/throughput 부족, 또는 파일시스템 레벨에서 지연이 커지면 crictl이 멈춘 듯 보이고 PLEG가 건강하지 않게 됩니다.

확인

# I/O 지연 관측
sudo iostat -xz 1 10
sudo vmstat 1 10

# 커널 메시지에 I/O error가 있는지
dmesg -T | tail -n 200

AWS 콘솔/CloudWatch에서도 해당 인스턴스의 EBS 지표를 같이 봅니다.

  • VolumeQueueLength, BurstBalance(gp2), Read/WriteLatency

해결

  • gp2라면 gp3로 전환 + IOPS/throughput 명시
  • 워크로드 I/O 패턴(특히 로그/캐시)을 별도 볼륨 또는 네트워크 스토리지로 분리
  • 노드 로컬 디스크에 과도한 쓰기가 발생하는지 애플리케이션 레벨에서 점검

6) CNI/iptables 꼬임으로 “런타임은 살아있는데” Pod sandbox 관련 호출이 지연

PLEG의 직접 원인이 네트워크는 아닐 때가 많지만, EKS에서는 CNI(amazon-vpc-cni), kube-proxy(iptables/ipvs), 보안 에이전트가 엮여 Pod sandbox 생성/조회가 지연되면 kubelet이 런타임과의 상호작용에서 병목을 겪으며 PLEG 경고가 동반될 수 있습니다.

대표 증상

  • FailedCreatePodSandBox, failed to setup network for sandbox
  • 특정 노드에서만 Pod 생성이 비정상적으로 느림

확인

# aws-node(CNI) 상태
kubectl -n kube-system get ds aws-node -o wide
kubectl -n kube-system logs -l k8s-app=aws-node --tail=200

# kube-proxy 상태
kubectl -n kube-system get ds kube-proxy -o wide
kubectl -n kube-system logs -l k8s-app=kube-proxy --tail=200

노드에서 iptables 룰이 비정상적으로 비대해졌는지도 힌트가 됩니다.

sudo iptables -S | wc -l
sudo iptables -t nat -S | wc -l

해결

  • CNI/kube-proxy 버전(EKS add-on) 업데이트
  • 문제 노드만 격리 후 재생성(Managed Node Group라면 가장 깔끔한 해결책)
  • 보안 에이전트(ebpf/iptables 후킹)가 있다면 해당 노드에서의 영향 점검

7) kubelet/노드 시간 드리프트(NTP) 또는 과도한 GC/로그 폭주로 헬스체크가 오판

드물지만 실제 운영에서 몇 번씩 만나는 케이스입니다.

  • 노드 시간이 크게 틀어져 있으면 “마지막으로 PLEG가 관찰된 시간” 계산이 꼬여서 과도하게 unhealthy로 판단할 수 있습니다.
  • 또는 kubelet이 이벤트/로그를 과도하게 찍으며(예: 반복 크래시 루프) CPU/I/O를 잡아먹어 PLEG가 연쇄적으로 밀립니다.

확인

# 시간 동기화 상태
timedatectl status

# 로그 폭주 여부(짧은 시간에 수천 줄)
sudo journalctl -u kubelet --since "10 min ago" | wc -l
sudo journalctl -u containerd --since "10 min ago" | wc -l

해결

  • chrony/systemd-timesyncd 상태 복구, NTP 접근 가능 여부(보안그룹/NACL/프록시) 확인
  • 원인 파드(크래시 루프, 무한 로그)를 먼저 진정시키고 노드 부하를 낮춘 뒤 kubelet/containerd 재시작

재발 방지 체크리스트(운영 관점)

1) “노드 교체가 가장 빠른” 상황을 정의

EKS Managed Node Group를 쓴다면, 다음 조건에서는 원인 분석보다 **노드 교체(terminate → 자동 재생성)**가 MTTR을 크게 줄입니다.

  • containerd crictl이 응답하지 않음
  • 디스크/inode가 이미 임계치에 도달
  • 특정 노드에서만 반복 재현

교체 전에는 가능하면 drain으로 워크로드를 안전하게 이동합니다.

kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data --grace-period=60

2) 관측 지표를 “PLEG의 간접 원인”에 맞춰 잡기

  • 노드: CPU, 메모리, node_filesystem_avail_bytes, inode, load, disk I/O latency
  • containerd: 프로세스 CPU/메모리, FD 수, 재시작 횟수
  • kubelet: 로그에서 PLEG 키워드 빈도(로그 기반 메트릭)

3) 배포 전략으로 런타임 압박을 줄이기

  • 대규모 롤아웃에서 동시성 제한
  • 이미지 크기 줄이기, 레이어 최적화
  • 급격한 스케일아웃 시 프리풀/캐시 전략

4) 연쇄 장애(트래픽/헬스체크)도 같이 보자

노드 NotReady가 트래픽 계층 502/504로 보이면, “노드 문제”를 “ALB 문제”로 오인해 시간을 잃습니다. 반대로 ALB 헬스체크/타임아웃 튜닝 미스가 노드 교체로 해결되지도 않습니다. 트래픽 관점 점검은 AWS ALB 502·504 난사 - 원인별 해결 체크리스트와 함께 병행하는 것이 좋습니다.


실전 트러블슈팅 플로우(요약)

아래 순서대로 보면 대부분 15~30분 내에 원인 범주를 확정할 수 있습니다.

  1. kubectl describe nodeDiskPressure/MemoryPressure/PIDPressure 확인
  2. journalctl -u kubelet에서 PLEG와 함께 등장하는 에러(DeadlineExceeded, ImageGCFailed 등) 확인
  3. crictl ps/pods가 즉시 응답하는지로 런타임 스톨 여부 판단
  4. df -h, df -i로 디스크/inode 즉시 확인
  5. iostat -xz로 I/O 병목 확인
  6. CNI/kube-proxy 로그로 sandbox/network 지연 확인
  7. 빠른 복구가 필요하면 drain 후 노드 재생성(또는 containerd 재시작)

마무리

PLEG is not healthy는 “쿠버네티스가 고장났다”라기보다, kubelet이 런타임/노드 리소스 상태를 제때 관찰하지 못할 만큼 노드가 느려졌거나 막혔다는 신호인 경우가 대부분입니다. 따라서 해결도 kubelet 설정만 만지기보다, containerd 응답성, 디스크(inode 포함), CPU/I/O 압박, CNI 경로를 순서대로 좁혀가면 빠르게 수습됩니다.

운영 중 같은 패턴이 반복된다면, 장애 시점의 kubelet/containerd 로그와 EBS/노드 지표를 묶어 “원인별 시그니처”를 만들어 두는 것이 다음 MTTR을 크게 줄입니다.