Published on

EKS DiskPressure로 Pod Evicted 폭주 해결 10가지

Authors

서버가 멀쩡해 보이는데도 EKS에서 갑자기 Pod가 Evicted로 쏟아지면, 대부분 노드의 ephemeral storage(로컬 디스크) 가 임계치를 넘어 kubelet이 DiskPressure를 선언한 상황입니다. 문제는 한 번 압박이 시작되면 이미지/로그/emptyDir/컨테이너 writable layer가 연쇄적으로 디스크를 잠식하면서, 스케줄링은 계속되고(eviction으로 자리 생김) 다시 쓰레기가 쌓여 Evicted 폭주(storm) 로 이어진다는 점입니다.

이 글은 “지금 당장 폭주를 멈추는 응급처치”부터 “재발 방지 설계”까지, 현장에서 가장 효과가 컸던 10가지 해결책을 체크리스트 형태로 정리합니다.

> 참고로 노드 상태 이상이 동반되면 원인이 겹칠 수 있습니다. kubelet이 NotReady로 흔들리는 케이스는 EKS kubelet NotReady - PLEG is not healthy 7가지도 같이 확인해 보세요.

0) 증상 확인: DiskPressure Evicted인지 먼저 확정

먼저 “정말 DiskPressure 때문에 Evicted 되었는지”를 빠르게 확정합니다.

# Evicted Pod 목록
kubectl get pod -A --field-selector=status.phase=Failed | grep -i evicted

# 특정 Pod가 왜 죽었는지
kubectl describe pod -n <ns> <pod>

# 노드 컨디션
kubectl get nodes
kubectl describe node <node-name> | sed -n '/Conditions:/,/Addresses:/p'

describe pod 이벤트에 아래 같은 문구가 있으면 DiskPressure 계열입니다.

  • The node was low on resource: ephemeral-storage.
  • Evicted: The node was low on resource: ephemeral-storage.

또한 노드 컨디션에 DiskPressure=True가 켜져 있는지 확인합니다.

1) 즉시 폭주 멈추기: 문제 노드 Cordon/Drain + 워크로드 확산

Evicted가 폭주 중이면 “원인 분석”보다 먼저 확산을 멈추는 것이 중요합니다.

# 문제 노드 스케줄링 차단
kubectl cordon <node-name>

# 안전하게 비우기(daemonset 제외)
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data --grace-period=60
  • --delete-emptydir-data는 emptyDir가 큰 워크로드에 필요할 수 있으나, 데이터 유실 영향이 있으니 앱 특성에 맞게 판단하세요.
  • 노드가 여러 대 동시에 DiskPressure면, 한 대씩 처리하며 서비스 가용성을 유지합니다.

2) “무엇이 디스크를 먹는지” 5분 안에 분해해서 보기

DiskPressure는 원인이 다양합니다. 크게 4가지 축으로 쪼개서 보면 빨라집니다.

  1. 컨테이너 로그(stdout/stderr)
  2. 이미지/레이어/컨테이너 런타임 캐시
  3. emptyDir / hostPath / writable layer
  4. kubelet/OS 레벨 로그, core dump, 임시파일

노드에 접속할 수 있다면(SSM/SSH) 우선순위대로 확인합니다.

# 전체 사용량
sudo df -h
sudo df -h /var/lib/kubelet /var/lib/containerd /var/log

# 큰 디렉터리 Top
sudo du -xh /var/lib | sort -h | tail -n 30
sudo du -xh /var/log | sort -h | tail -n 30

# containerd 기준: 스냅샷/콘텐츠/이미지
sudo du -xh /var/lib/containerd | sort -h | tail -n 30

EKS AMI/런타임에 따라 경로가 다를 수 있지만 보통 다음이 핵심입니다.

  • /var/lib/kubelet (Pod 볼륨, emptyDir, 플러그인 데이터)
  • /var/lib/containerd 또는 /var/lib/docker (이미지/레이어)
  • /var/log/containers, /var/log/pods, /var/log/journal (로그)

3) 컨테이너 로그 폭주 차단: 로그 로테이션/드라이버 설정

DiskPressure의 가장 흔한 원인은 애플리케이션 로그 폭주입니다. stdout로 무한정 찍히면 /var/log/containers/*.log가 기하급수로 커집니다.

(1) Docker 사용 시(구형 노드) log-opts 강제

// /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "3"
  }
}
sudo systemctl restart docker

(2) containerd 사용 시: kubelet 로그 로테이션 파라미터 확인

kubelet이 컨테이너 로그 로테이션을 수행하도록 설정되어야 합니다.

  • --container-log-max-size
  • --container-log-max-files

EKS Managed Node Group는 런치 템플릿/부트스트랩 인자로 제어하는 경우가 많습니다.

ps -ef | grep kubelet | grep -E "container-log-max|container-log"

팁: 로그 수집이 필요하면 stdout를 줄이는 대신 Fluent Bit 등으로 전송하되, 노드 로컬 파일이 무한정 커지지 않도록 로테이션은 반드시 켜세요.

4) 이미지/레이어 정리: Pull 폭주 + 캐시 누적 방지

새 버전을 자주 배포하거나 이미지 태그를 매번 바꾸면 노드에 이미지가 계속 쌓입니다. 특히 큰 베이스 이미지(ML, GPU, JDK)가 많으면 치명적입니다.

(1) containerd 이미지 정리

# 설치되어 있다면
sudo crictl images
sudo crictl rmi --prune

# nerdctl 사용 가능 시
sudo nerdctl images
sudo nerdctl image prune -a

(2) 이미지 풀 정책 점검

imagePullPolicy: Always는 개발 환경에선 편하지만, 운영에서 태그 전략이 나쁘면 디스크/레지스트리 모두를 압박합니다.

5) emptyDir/writable layer 상한 설정: ephemeral-storage requests/limits

DiskPressure Eviction은 “노드 전체 디스크” 기준이지만, 쿠버네티스는 Pod별 ephemeral-storage request/limit을 통해 스케줄링과 eviction 우선순위를 더 똑똑하게 만들 수 있습니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: api
        image: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/api:sha-abc
        resources:
          requests:
            cpu: "200m"
            memory: "512Mi"
            ephemeral-storage: "1Gi"
          limits:
            cpu: "1"
            memory: "1Gi"
            ephemeral-storage: "2Gi"
  • emptyDir에 대용량 쓰기(압축, 캐시, 임시 업로드)가 있으면 반드시 설정하세요.
  • requests를 넣으면 스케줄러가 “디스크 여유가 있는 노드”로 더 잘 분산합니다.

6) emptyDir 자체에 sizeLimit 걸기 + 캐시를 PV/S3로 이동

Pod 내부 캐시/임시파일이 원인이라면 emptyDir에 sizeLimit을 걸어 “한 Pod가 노드를 먹어치우는” 상황을 막습니다.

spec:
  volumes:
  - name: cache
    emptyDir:
      sizeLimit: 2Gi
  containers:
  - name: worker
    volumeMounts:
    - name: cache
      mountPath: /cache

근본적으로는 다음 중 하나로 이동하는 게 좋습니다.

  • EBS PVC: 노드 루트 디스크와 분리
  • EFS: 여러 Pod 공유 캐시(성능/비용 고려)
  • S3: 임시 업로드/아카이브 성격이면 최적

7) 노드 루트 볼륨(EBS) 용량/타입/IOPS 재설계

EKS Managed Node Group의 기본 루트 볼륨이 20GiB인 경우가 아직도 많습니다. 컨테이너 이미지 몇 개 + 로그만으로도 쉽게 터집니다.

권장 방향

  • 루트 볼륨을 최소 50~100GiB로 상향(워크로드에 따라 더)
  • gp3로 전환해 비용 대비 성능 확보
  • 이미지가 큰 워크로드는 별도 노드그룹으로 격리

Terraform을 쓴다면 Launch Template에서 block_device_mappings로 조정합니다.

resource "aws_launch_template" "eks" {
  name_prefix = "eks-ng-"

  block_device_mappings {
    device_name = "/dev/xvda"
    ebs {
      volume_size = 100
      volume_type = "gp3"
      iops        = 3000
      throughput  = 125
      delete_on_termination = true
    }
  }
}

8) kubelet eviction threshold 튜닝(마지막 수단)

kubelet은 evictionHard/evictionSoft 임계치에 따라 Pod를 내보냅니다. 이를 무작정 늦추면 “더 늦게 터지되 더 크게 터지는” 부작용이 있어 마지막 수단으로만 고려하세요.

확인:

ps -ef | grep kubelet | grep eviction

튜닝 시 원칙:

  • 임계치를 완화하기보다, 디스크를 더 주고(볼륨 확장)
  • 로그/캐시 상한을 걸어 생성량을 줄이는 쪽이 우선

9) DaemonSet/노드 에이전트가 디스크를 먹는지 점검

운영 환경에서는 앱보다 에이전트(로그/보안/모니터링) 가 더 많이 쓰는 경우가 흔합니다.

체크 포인트:

  • Fluent Bit/Fluentd 버퍼가 노드에 쌓임
  • 보안 에이전트가 /var/log에 대용량 파일 생성
  • 노드 문제 발생 시 core dump가 누적

노드에서 “어떤 프로세스가 파일을 크게 잡고 있는지”를 보면 빠릅니다.

# 큰 파일 Top
sudo find /var -xdev -type f -size +200M -printf '%s %p\n' | sort -n | tail -n 30

# 삭제해도 공간이 안 돌아오면: 열린 파일 핸들(삭제된 파일)
sudo lsof | grep deleted | head

10) 재발 방지: 모니터링/알람 + 스케일링/분리 전략

DiskPressure는 “터지고 나서” 알면 이미 늦습니다. 아래 3가지만 해도 재발률이 크게 내려갑니다.

(1) 노드 파일시스템 사용률 알람

  • CloudWatch Agent / node-exporter로 node_filesystem_avail_bytes 감시
  • 70% 경고, 85% 치명 알람(환경에 맞게)

(2) 이벤트 기반 감지

쿠버네티스 이벤트에서 Evicted, DiskPressure를 수집해 슬랙/페이지로 알립니다.

kubectl get events -A --sort-by=.lastTimestamp | tail -n 50

(3) 워크로드 분리

  • 이미지 큰 서비스, 로그 많은 서비스, 배치/ETL은 별도 노드그룹
  • nodeSelector/taints/tolerations로 격리
spec:
  tolerations:
  - key: "workload"
    operator: "Equal"
    value: "batch"
    effect: "NoSchedule"
  nodeSelector:
    workload: batch

보너스: 현장에서 가장 많이 맞닥뜨린 “원인-대응” 매핑

  • Evicted가 배포 직후 폭주: 새 이미지가 너무 큼 → 이미지 최적화(멀티스테이지), 노드 루트 볼륨 상향, 이미지 prune
  • 특정 서비스만 반복 Evicted: emptyDir/캐시 폭주 → ephemeral-storage limit + emptyDir sizeLimit
  • 새벽 배치 시간에만 터짐: 임시 압축/다운로드 → PVC/S3로 이동, 배치 노드그룹 분리
  • 로그 수집 도입 후 터짐: 에이전트 버퍼/로컬 파일 → 로테이션/버퍼 설정 재점검

마무리 체크리스트(우선순위)

  1. 문제 노드 cordon/drain으로 확산 차단
  2. /var/log, /var/lib/kubelet, /var/lib/containerd 사용량 Top 확인
  3. 컨테이너 로그 로테이션 강제
  4. 이미지/레이어 prune + 태그/풀 정책 개선
  5. ephemeral-storage requests/limits 적용
  6. emptyDir sizeLimit 적용 및 캐시 외부화(PV/S3)
  7. 노드 루트 볼륨 확장(gp3, 100GiB 등)
  8. 에이전트/DaemonSet 디스크 사용 점검
  9. (필요 시) kubelet eviction threshold는 신중히
  10. 모니터링/알람으로 사전 감지

DiskPressure는 단순히 “디스크가 부족”한 문제가 아니라, 로그/이미지/캐시/배치/에이전트의 합이 노드 로컬 스토리지를 잠식하는 구조적 문제인 경우가 대부분입니다. 위 10가지를 순서대로 적용하면 Evicted 폭주는 빠르게 진정되고, 재발도 크게 줄일 수 있습니다.