Published on

EKS Pod Evicted(디스크 압박) 원인·해결 가이드

Authors

서버가 멀쩡해 보이는데도 EKS에서 Pod가 갑자기 Evicted 되고, 이벤트에는 node had condition: [DiskPressure] 같은 문구가 찍히는 경우가 있습니다. 이 현상은 단순히 "디스크가 꽉 찼다" 수준이 아니라, kubelet이 노드의 로컬 스토리지(컨테이너 writable layer, 로그, emptyDir, 이미지 캐시 등) 를 안전 임계치 이하로 판단해 강제 축출(Eviction) 을 수행한 결과입니다.

이 글에서는 EKS에서 DiskPressure로 인한 Pod Evicted원인별로 쪼개서 재현 가능한 진단 방법과 즉시/근본 해결책을 정리합니다.

Evicted와 DiskPressure가 의미하는 것

Evicted 는 스케줄러가 내린 결정이 아니라, 노드의 kubelet 이 로컬 리소스 압박을 감지해 특정 Pod를 종료시키는 동작입니다. 여기서 DiskPressure는 주로 다음 중 하나의 부족으로 발생합니다.

  • nodefs: 노드 루트 파일시스템(예: /) 여유 공간/ inode 부족
  • imagefs: 컨테이너 이미지 저장 영역(런타임에 따라 분리되거나 nodefs와 동일) 여유 공간/ inode 부족
  • ephemeral-storage: Pod가 사용하는 로컬 임시 스토리지(컨테이너 writable layer, emptyDir, 로그 등)가 노드 임계치를 넘는 상황

kubelet은 evictionHard, evictionSoft 같은 임계치에 따라 축출을 실행합니다. EKS Managed Node Group(AMIs)에서도 kubelet 기본 임계치가 적용되며, 런타임과 디스크 구성에 따라 체감이 달라집니다.

1) 가장 먼저 확인할 것: 이벤트와 축출 사유

우선 "정말 DiskPressure 축출인지" 를 이벤트에서 확정해야 합니다.

kubectl describe pod -n <namespace> <pod-name>

아래 같은 이벤트가 핵심 단서입니다.

  • Reason: Evicted
  • The node was low on resource: ephemeral-storage
  • node had condition: [DiskPressure]

노드 상태도 함께 확인합니다.

kubectl describe node <node-name>
kubectl get node <node-name> -o jsonpath='{.status.conditions[?(@.type=="DiskPressure")].status}'

DiskPressure=True 면 kubelet이 "지금 노드가 위험" 이라고 판단하고 있다는 뜻입니다.

2) DiskPressure의 흔한 원인 6가지

원인 A. 컨테이너 로그 폭증(표준 출력/에러)

EKS에서 기본 로그는 노드 로컬에 쌓입니다(런타임과 설정에 따라 위치는 다르지만, 결국 nodefs를 사용). 애플리케이션이 JSON을 초당 수천 줄 찍거나, 에러 루프에서 스택트레이스를 계속 출력하면 로그 파일이 nodefs를 가득 채워 DiskPressure로 이어집니다.

진단 포인트

  • 특정 시간대에 에러 폭증, CrashLoopBackOff 와 동반
  • 노드에서 /var/log 또는 컨테이너 로그 디렉터리 용량 급증

대응

  • 애플리케이션 로그 레벨 조정, 샘플링
  • 로그 로테이션 설정 확인(아래 "해결책" 참고)

원인 B. 이미지 캐시가 비대해짐(특히 잦은 배포)

노드가 많은 버전의 이미지를 계속 풀(pull)하면 imagefs/nodefs에 이미지 레이어가 누적됩니다. kubelet의 이미지 GC가 동작하더라도, 임계치 설정/디스크 크기에 따라 GC가 따라가지 못하면 DiskPressure가 발생할 수 있습니다.

진단 포인트

  • 배포가 잦고 이미지 태그가 매번 바뀜(예: 커밋 SHA)
  • 큰 베이스 이미지(자바, ML, 브라우저 포함 등)

원인 C. emptyDir 사용량 폭발(캐시/임시 파일)

emptyDir 는 노드 로컬 디스크를 사용합니다(메모리 emptyDir가 아닌 이상). 파일 업로드 임시 저장, 압축 해제, 썸네일 생성, 대용량 배치 작업 등이 emptyDir 에 쓰면 ephemeral-storage가 급증합니다.

진단 포인트

  • Pod에 emptyDir 볼륨이 있고, 작업 특성상 임시 파일이 큼
  • 특정 워커/배치 Pod에서만 반복적으로 Evicted

원인 D. 컨테이너 writable layer에 대용량 파일 생성

컨테이너 내부 경로(예: /tmp, /app/cache)에 그냥 쓰면 해당 쓰기는 컨테이너 writable layer 로 들어가며, 이것도 ephemeral-storage로 집계됩니다. 특히 이미지에 포함되어야 할 파일을 런타임에 다운로드/생성하면 디스크 사용이 폭증합니다.

원인 E. inode 고갈(파일 수 폭증)

용량은 남아도 inode가 부족하면 DiskPressure가 날 수 있습니다. 작은 파일을 수백만 개 만드는 워크로드(크롤러, 캐시, 임시 파일 쪼개기 등)가 대표적입니다.

진단 포인트

  • df -h 는 여유가 있는데도 문제
  • df -i 에서 inode 사용률이 100% 근접

원인 F. 노드 디스크(EBS) 자체가 작거나, 파티션 분리가 불리

EKS 노드 루트 볼륨이 작으면 로그/이미지/emptyDir가 조금만 늘어도 임계치에 걸립니다. 또한 런타임 구성에 따라 imagefs가 nodefs와 같은 파티션을 쓰면(분리 안 됨) 이미지 증가가 곧 nodefs 압박으로 연결됩니다.

3) 노드에서 빠르게 원인 찾기(운영자 체크리스트)

DiskPressure가 발생한 노드로 들어가서 "무엇이 디스크를 먹는지" 를 확인합니다.

3-1. 용량과 inode 확인

df -h
df -i

여기서 / 또는 컨테이너 런타임 관련 마운트(예: /var/lib/containerd)가 가득 찼는지 봅니다.

3-2. 상위 디렉터리부터 누가 큰지 확인

sudo du -xh /var --max-depth=2 | sort -h | tail -n 30
sudo du -xh /var/lib --max-depth=2 | sort -h | tail -n 30

환경에 따라 큰 축은 보통 다음 중 하나입니다.

  • /var/log
  • /var/lib/containerd
  • /var/lib/docker
  • /var/lib/kubelet

3-3. 컨테이너 로그 파일 확인(대표 위치)

런타임/AMI에 따라 다르지만 흔히 다음 계열을 봅니다.

sudo ls -alh /var/log/containers | head
sudo ls -alh /var/log/pods | head

특정 Pod 로그가 비정상적으로 크면, 해당 Pod가 축출의 직접 트리거일 가능성이 큽니다.

4) 해결책: 즉시 조치(응급) vs 근본 조치(재발 방지)

4-1. 응급: 노드 디스크 확보

  1. 문제 Pod를 먼저 줄이거나 중지
  • 배치/워커를 스케일 다운
  • 로그 폭증 Pod를 롤백
  1. 이미지/로그 정리
  • 런타임이 containerd라면, 노드에서 이미지/스냅샷 정리가 도움이 됩니다.
sudo crictl images
sudo crictl rmi --prune
  • Docker 기반이면 다음도 고려할 수 있습니다.
sudo docker system prune -af

주의: 운영 중인 노드에서 무작정 prune은 위험할 수 있습니다. 가능한 한 문제 노드를 cordon/drain 후 교체하는 방식이 안전합니다.

  1. 가장 안전한 응급 처치: 노드 교체

Managed Node Group를 쓰는 경우, DiskPressure 노드는 원인 제거 후에도 "더럽혀진 상태" 가 남을 수 있습니다(누수성 파일, 잔여 스냅샷 등). 다음 흐름이 운영적으로 안전합니다.

kubectl cordon <node-name>
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data

그 후 노드를 종료해 새 노드로 대체합니다.

4-2. 근본: ephemeral-storage request/limit 설정

CPU/메모리만 설정하고 ephemeral-storage를 안 잡으면, 어떤 Pod가 디스크를 많이 쓰는지 스케줄링 단계에서 제어가 어렵습니다.

아래처럼 resources.requests/limitsephemeral-storage 를 명시하세요.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: worker
spec:
  replicas: 3
  selector:
    matchLabels:
      app: worker
  template:
    metadata:
      labels:
        app: worker
    spec:
      containers:
        - name: worker
          image: yourrepo/worker:2026-02-24
          resources:
            requests:
              cpu: "200m"
              memory: "512Mi"
              ephemeral-storage: "1Gi"
            limits:
              cpu: "1"
              memory: "1Gi"
              ephemeral-storage: "2Gi"

이렇게 하면

  • 스케줄러가 노드의 ephemeral-storage 가용량을 고려
  • 특정 Pod의 디스크 폭주가 노드 전체 장애로 번지는 것을 완화

4-3. 근본: emptyDir 크기 제한(sizeLimit) 사용

emptyDir 를 쓰는 Pod는 반드시 상한을 두는 습관이 좋습니다.

volumes:
  - name: tmp
    emptyDir:
      sizeLimit: "2Gi"
containers:
  - name: api
    volumeMounts:
      - name: tmp
        mountPath: /tmp

애플리케이션이 임시 파일을 무한히 만들더라도 Pod 단에서 제어할 수 있습니다.

4-4. 근본: 로그 로테이션과 로그 전략 재설계

  • 애플리케이션: 과도한 stdout 로그를 줄이고, 구조화 로그라도 샘플링/레이트 리밋 적용
  • 노드: 컨테이너 로그 로테이션 설정 확인

EKS AMI는 보통 컨테이너 런타임 로깅 드라이버/로테이션이 적용되어 있지만, 워크로드 특성상 로테이션 주기보다 로그 생성이 빠르면 결국 DiskPressure로 갑니다.

운영 팁

  • "에러가 나면 로그가 늘고, 로그가 늘면 DiskPressure로 Evicted, Evicted가 재시작을 부르고 로그가 더 늘어나는" 악순환이 생깁니다. 이때는 CrashLoopBackOff 원인도 같이 잡아야 합니다. 위에서 언급한 Kubernetes CrashLoopBackOff와 OOMKilled(ExitCode 137) 해결 의 접근(이벤트 기반 원인 확정, 리소스 제한, 재현)과 유사한 방식이 유효합니다.

4-5. 근본: 노드 루트 볼륨(EBS) 확장 및 인스턴스 스토리지 전략

노드 루트 볼륨이 20GiB 수준이면 요즘 이미지 크기/로그량을 버티기 어렵습니다.

  • Managed Node Group의 Launch Template에서 루트 볼륨 크기 확대(예: 80GiB, 100GiB)
  • 이미지가 큰 워크로드(ML, 브라우저 자동화, 대형 런타임)는 노드 그룹을 분리
  • 가능하면 캐시/업로드 임시는 EFS/S3 같은 외부 스토리지로 이동

4-6. 근본: 이미지 최적화와 배포 정책

  • 멀티 스테이지 빌드로 런타임 이미지 최소화
  • 불필요한 패키지 제거
  • 태그 정책을 단순화(너무 많은 태그가 노드에 공존하지 않게)

이미지 최적화는 DiskPressure뿐 아니라 배포 속도/노드 네트워크 비용에도 직결됩니다.

5) 모니터링/알림: 재발을 "사전에" 잡는 지표

운영에서는 DiskPressure가 터진 뒤 대응하면 이미 일부 Pod가 축출된 뒤입니다. 다음을 알림 기준으로 잡아두면 좋습니다.

  • 노드 조건 DiskPressure=True
  • 노드 filesystem 사용률, inode 사용률
  • Pod container_fs_usage_bytes 계열(수집 스택에 따라 이름 상이)
  • Evicted 이벤트 발생 횟수

CloudWatch Container Insights 또는 Prometheus 기반이라면, "노드별 디스크 사용률" 과 "Evicted 이벤트" 를 같은 대시보드에서 보이게 구성하는 것이 효과적입니다.

6) 운영에서 자주 하는 실수와 정리

실수 1. Pod만 재시작하면 해결될 거라 생각

DiskPressure는 노드 로컬 상태 문제인 경우가 많아, Pod 재시작만으로는 원인이 남습니다. 특히 이미지 캐시/로그/스냅샷이 원인이면 노드 교체가 더 빠를 때가 많습니다.

실수 2. emptyDir 를 "메모리" 로 착각

기본 emptyDir 는 디스크입니다. 메모리로 쓰려면 emptyDir.mediumMemory 로 지정해야 하지만, 그 경우 메모리 압박으로 OOM과 연결될 수 있어 주의가 필요합니다.

실수 3. ephemeral-storage를 리소스 계획에서 제외

CPU/메모리만 request/limit 잡으면 디스크 폭주 Pod가 한 노드에 몰릴 수 있습니다. ephemeral-storage 를 request/limit에 포함시키면 스케줄링 품질이 올라갑니다.

7) 빠른 결론: 가장 효과적인 우선순위

  1. kubectl describe pod 이벤트로 Evicted 원인 확정
  2. 노드에서 df -h, df -i, du 로 "누가 먹는지" 10분 내 특정
  3. 응급이면 cordon/drain 후 노드 교체
  4. 재발 방지는 ephemeral-storage request/limit, emptyDir.sizeLimit, 로그 로테이션/로그량 제어, 노드 볼륨 확장

DiskPressure는 한 번 터지면 "같은 유형의 워크로드" 에서 반복됩니다. 한두 개 Pod의 문제가 아니라, 클러스터 운영 정책(리소스 모델링, 로그 전략, 스토리지 설계)을 다듬는 계기로 삼는 것이 장기적으로 가장 비용이 적게 듭니다.