- Published on
EKS 노드 디스크 100%로 Pod Evicted 해결법
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡히 돌던 중 갑자기 특정 노드에서만 Pod가 Evicted 되고, 새로 뜨는 Pod도 연쇄적으로 죽는 상황을 한 번 겪으면 꽤 당황스럽습니다. EKS에서는 이 문제가 대개 노드의 디스크(정확히는 nodefs 또는 imagefs) 사용량이 임계치를 넘으면서 kubelet의 eviction 로직이 동작해 발생합니다.
이 글에서는 “노드 디스크 100% → Pod Evicted”를 진단(어디가 찼는지), 즉시 복구(당장 살리는 방법), 근본 해결(재발 방지) 순서로 정리합니다.
1) Evicted가 의미하는 것: kubelet eviction 개요
kubelet은 노드의 리소스가 부족해지면 Pod를 강제로 축출(evict)합니다. 대표적인 트리거는 다음입니다.
DiskPressure발생: 디스크 사용량이 임계치 초과ephemeral-storage부족: 컨테이너의 쓰기 레이어,emptyDir, 로그 등이 누적- inode 고갈: 용량은 남았는데 파일 개수가 너무 많아 더 못 쓰는 경우
Evicted 이벤트 메시지에는 힌트가 들어 있습니다. 예를 들어 다음처럼 nodefs 또는 imagefs가 언급됩니다.
The node was low on resource: ephemeral-storage.eviction manager: nodefs available또는imagefs available
먼저 “디스크가 찼다”를 정확히 어떤 영역이 찼는지로 쪼개야 합니다.
2) 5분 안에 상태 파악: 어떤 디스크가 찼는지
2-1. Pod 이벤트로 1차 원인 확인
아래 명령으로 Evicted 이유를 먼저 봅니다.
kubectl describe pod -n <NAMESPACE> <POD_NAME>
출력의 Events 섹션에서 Evicted 사유를 확인하세요. ephemeral-storage가 보이면 거의 확정적으로 노드 디스크/로그/쓰기레이어 문제입니다.
2-2. 노드 조건에서 DiskPressure 확인
kubectl describe node <NODE_NAME>
Conditions에서 DiskPressure=True가 보이면 kubelet이 디스크 압박을 감지한 상태입니다.
2-3. 노드에 접속해서 파일시스템/인오드 확인
EKS Managed Node Group이면 보통 SSH 또는 SSM으로 들어가 확인합니다.
df -h
df -ih
여기서 포인트는 다음입니다.
/(root) 또는/var/lib/kubelet이 꽉 찼는지/var/lib/containerd또는/var/lib/docker가 큰지- inode(
df -ih)가 100%인지
EKS AMI가 containerd 기반이면 보통 containerd 경로가 핵심입니다.
3) 가장 흔한 범인 4가지
3-1. 컨테이너 로그 폭증 (stdout/stderr)
컨테이너가 stdout/stderr로 과도하게 찍으면 노드의 로그 파일이 계속 쌓입니다. 많은 환경에서 로그는 /var/log/containers, /var/log/pods, /var/lib/docker/containers(docker) 또는 containerd 관련 경로에 쌓입니다.
진단:
sudo du -h -d 2 /var/log | sort -h | tail -n 30
sudo du -h -d 3 /var/log/containers | sort -h | tail -n 30
로그 파이프라인을 사용 중이라면 fluent-bit 설정/버퍼로 인해 더 악화될 수도 있습니다. 로그 누락/지연 이슈를 함께 겪고 있다면 EKS에서 fluent-bit 로그 누락·지연 원인 9가지도 같이 점검하는 게 좋습니다.
3-2. 이미지/레이어 누적 (imagefs)
노드가 자주 롤링되거나 태그 전략이 나쁘면 이미지가 계속 pull 되고 GC가 늦어져 imagefs가 가득 찰 수 있습니다.
containerd 기준 진단:
sudo crictl images
sudo crictl imagefsinfo
특히 :latest 같은 태그를 남발하면 캐시/정리 패턴이 꼬이기 쉽습니다.
3-3. emptyDir, 쓰기 레이어 폭증 (ephemeral-storage)
애플리케이션이 임시 파일을 emptyDir에 쌓거나, 컨테이너 쓰기 레이어에 파일을 계속 만들면 노드 디스크가 찹니다.
다음처럼 emptyDir를 많이 쓰는 Pod는 특히 주의합니다.
apiVersion: v1
kind: Pod
metadata:
name: tmp-writer
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "dd if=/dev/zero of=/tmp/bigfile bs=1M count=10240; sleep 3600"]
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}
3-4. inode 고갈 (작은 파일 수백만 개)
df -h는 여유가 있는데 df -ih가 100%면 inode 고갈입니다. 캐시 디렉터리, 임시 파일, 로그 로테이션 실패에서 자주 발생합니다.
진단:
sudo find /var/log -xdev -type f | wc -l
sudo find /var/lib/kubelet -xdev -type f | wc -l
4) 즉시 복구: 서비스부터 살리는 응급처치
4-1. 노드 드레인 후 교체(가장 안전)
디스크가 100%면 kubelet/컨테이너 런타임도 불안정해집니다. 가장 안전한 방법은 노드를 비우고 교체하는 것입니다.
kubectl cordon <NODE_NAME>
kubectl drain <NODE_NAME> \
--ignore-daemonsets \
--delete-emptydir-data \
--grace-period=60 \
--timeout=10m
그 다음 Managed Node Group이면 원하는 방식으로 노드를 교체(스케일 아웃 후 스케일 인, 또는 리프레시)합니다.
4-2. 로그/임시파일 정리(원인 파악 후 제한적으로)
서비스를 당장 살려야 하고 노드 교체가 늦는다면, 큰 디렉터리를 찾아 정리합니다.
sudo du -h -d 2 /var/lib | sort -h | tail -n 30
sudo du -h -d 2 /var/log | sort -h | tail -n 30
주의: 무작정 삭제하면 런타임/로그 수집이 깨질 수 있습니다. “무엇이 커졌는지”를 먼저 확인하고, 애플리케이션 로그 폭증이면 앱 설정부터 막는 게 우선입니다.
4-3. 컨테이너 런타임 GC 유도
containerd 환경에서는 불필요한 이미지/컨테이너 정리가 도움이 됩니다.
sudo crictl ps -a
sudo crictl rm <CONTAINER_ID>
sudo crictl images
sudo crictl rmi <IMAGE_ID>
운영 중인 컨테이너/이미지를 지우지 않도록 주의해야 합니다.
5) 근본 해결 1: requests/limits로 eviction 우선순위 제어
kubelet은 eviction 시 QoS(Guaranteed/Burstable/BestEffort)와 ephemeral-storage 요청/제한을 참고합니다. ephemeral-storage를 명시하면 “누가 얼마나 쓰는지”가 보이고, 과도 사용 Pod를 더 빨리 정리할 수 있습니다.
예시:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: <YOUR_IMAGE>
resources:
requests:
cpu: "250m"
memory: "512Mi"
ephemeral-storage: "1Gi"
limits:
cpu: "1"
memory: "1Gi"
ephemeral-storage: "2Gi"
이 설정이 없으면 디스크를 가장 많이 쓰는 Pod가 아니라 “운이 나쁜 Pod”가 먼저 죽는 상황이 생길 수 있습니다.
6) 근본 해결 2: 로그 폭증 방지와 로테이션
6-1. 애플리케이션 로그 레벨/샘플링
- 에러 루프에서 초당 수천 줄 찍는지
- 요청/응답 바디를 그대로 찍는지
- 디버그 로그가 운영에 켜져 있는지
로그 폭증은 디스크 문제뿐 아니라 비용(수집/저장)도 같이 터집니다.
6-2. 노드 로그 로테이션 설정 점검
EKS AMI 기본값이 충분하지 않은 경우가 있습니다. 컨테이너 런타임 및 kubelet 로그 로테이션 정책을 점검하세요.
kubelet 설정에서 containerLogMaxSize, containerLogMaxFiles 같은 값이 관여합니다(환경에 따라 설정 방식이 다름).
7) 근본 해결 3: 이미지 전략과 노드 캐시 관리
- 태그를 불변(immutable)하게 운영하고, 불필요한 재-pull을 줄이기
- 이미지 크기 줄이기(멀티스테이지 빌드, 불필요 패키지 제거)
- 노드 그룹을 역할별로 분리해 이미지 다양성을 줄이기
이미지 수가 많고 크면 imagefs가 먼저 터지는 패턴이 흔합니다.
8) 근본 해결 4: 모니터링과 알람(선제 대응)
8-1. 노드 디스크/인오드 알람
CloudWatch Container Insights 또는 Prometheus에서 다음을 알람으로 잡습니다.
- 노드 파일시스템 사용률(%)
- inode 사용률(%)
DiskPressure조건- eviction 이벤트 증가
8-2. 어떤 Pod가 디스크를 쓰는지 가시화
kubectl top은 CPU/메모리 중심이라 디스크 사용 추적이 약합니다. 대신 다음을 병행합니다.
- 노드에서 디렉터리별
du로 핫스팟 확인 ephemeral-storagerequests/limits 적용 후, 사용량을 수집하는 에이전트 도입 고려
9) 재발 패턴별 체크리스트
9-1. 특정 배포 이후만 터진다
- 새 버전에서 로그가 급증했는지
- 임시 파일을 지우지 않는 코드가 들어갔는지
- 압축/덤프 파일을 로컬에 남기는지
9-2. 특정 노드에서만 반복된다
- 해당 노드에만 특정 워크로드가 몰리는지(affinity/taint/toleration)
- DaemonSet이 노드별로 디스크를 많이 쓰는지
- 노드가 오래 떠 있어서 이미지/로그가 누적됐는지
9-3. 노드가 불안정하고 서비스도 같이 흔들린다
디스크 100%는 종종 systemd 서비스 재시작 루프, 런타임 불안정으로 이어집니다. 노드에서 서비스가 반복 재시작되는 정황이 있으면 systemd 서비스가 자꾸 재시작될 때 진단 방법처럼 OS 레벨에서 함께 추적하세요.
10) 현장에서 통하는 결론(우선순위)
kubectl describe pod로 Evicted 사유에서nodefs/imagefs/ephemeral-storage힌트를 잡는다.kubectl describe node로DiskPressure확인 후, 노드에서df -h와df -ih로 용량 vs inode를 구분한다.- 응급 복구는
cordon/drain으로 노드를 비우고 교체하는 게 가장 안전하다. - 재발 방지는
ephemeral-storagerequests/limits, 로그 폭증 차단, 이미지 전략 개선, 디스크/인오드 알람이 핵심이다.
부록: 자주 쓰는 명령 모음
# Evicted 사유 확인
kubectl describe pod -n <NAMESPACE> <POD_NAME>
# 노드 DiskPressure 확인
kubectl describe node <NODE_NAME>
# 노드 디스크/인오드 확인
df -h
df -ih
# 큰 디렉터리 찾기
sudo du -h -d 2 /var/log | sort -h | tail -n 30
sudo du -h -d 2 /var/lib | sort -h | tail -n 30
# containerd 이미지/디스크 정보
sudo crictl images
sudo crictl imagefsinfo