/인프라/Pod Evicted "node was low on resource" 5분 진단·해결법
인프라kubernetesPod Evicted

Pod Evicted "node was low on resource" 5분 진단·해결법

Pod가 Evicted되며 'node was low on resource' 에러가 뜬다면 범인은 컨테이너가 아니라 노드의 DiskPressure·MemoryPressure입니다. describe node Conditions로 5분 진단하고 ephemeral-storage·GC·limits로 해결하세요.

Pod Evicted "node was low on resource" 5분 진단·해결법

Pod Evicted 해결 가이드: "node was low on resource" 에러 5분 진단법

K8s_Troubleshooting_Guide 4편

새벽에 알림이 울립니다. 멀쩡히 돌던 Pod가 갑자기 Evicted 상태로 줄줄이 늘어서 있고, 서비스는 불안정합니다. 이때 가장 흔한 실수가 "컨테이너에 버그가 생겼나?" 하며 애플리케이션 로그부터 파헤치는 겁니다.

결론부터 말하면, 축출(Eviction)당했다고 컨테이너를 의심하지 마세요. 범인은 노드입니다. Evicted는 컨테이너가 죽은 게 아니라, kubelet이 "이 노드는 자원이 부족하니 너를 내보낸다"고 판단해 강제 퇴거시킨 상태입니다. 그래서 진단의 출발점도 Pod가 아니라 노드여야 합니다.

이번 편에서는 Evicted를 다른 장애와 칼같이 구분하고, kubectl describe node의 Conditions를 읽어 5분 안에 원인을 특정한 뒤, 원인별 해결과 재발 방지까지 한 번에 끝냅니다.

1. Evicted vs CrashLoopBackOff vs OOMKilled: 누가 죽였나?

세 가지는 증상이 비슷해 보이지만 책임 주체가 완전히 다릅니다. 이걸 구분하지 못하면 엉뚱한 곳을 고치게 됩니다.

상태주체 (누가 죽였나)트리거exit code해결 위치
Evicted노드 kubelet노드 전체 자원 압박 (Disk/Memory/PID Pressure)없음 (Pod phase=Failed)노드 자원 / requests·limits
CrashLoopBackOff컨테이너 자신프로세스가 반복적으로 비정상 종료1, 2 등 (앱마다 상이)애플리케이션 코드/설정
OOMKilled컨테이너 cgroup컨테이너가 자기 메모리 limit 초과137 (128+SIGKILL)해당 컨테이너 메모리 limit

핵심 포인트는 OOMKilled와 MemoryPressure Eviction의 차이입니다.

  • OOMKilled: 내 컨테이너가 자기 cgroup 한계를 넘은 것. 다른 Pod는 멀쩡합니다. exit code 137.
  • MemoryPressure Eviction: 노드 전체 가용 메모리가 임계값 아래로 떨어진 것. kubelet이 우선순위 낮은 Pod부터 여러 개를 골라 내보냅니다. exit code가 아니라 Pod phase가 Failed로 바뀌고 Reason이 Evicted입니다.

즉, 한 Pod만 137로 죽으면 limit을, 여러 Pod가 동시에 Evicted 되면 노드를 봐야 합니다.

2. 진단 루틴: get pods → describe node Conditions

축출이 의심되면 아래 시퀀스를 그대로 따라가면 됩니다.

2-1. Evicted Pod 식별

Bash
# Failed phase(=Evicted 포함) Pod 전체 조회
kubectl get pods -A --field-selector status.phase=Failed

2-2. 축출 사유 확인

특정 Pod를 describe 하면 결정적 단서가 나옵니다.

Bash
kubectl describe pod <pod-name> -n <namespace>
TEXT
Status:   Failed
Reason:   Evicted
Message:  The node was low on resource: ephemeral-storage.
          Container app was using 4521Mi, request is 0.

여기서 Messagelow on resource: <리소스> 부분이 어떤 자원이 부족했는지 알려줍니다. ephemeral-storage, memory, pids 등이 들어옵니다.

2-3. 노드 Conditions 읽기

이제 그 Pod가 있던 노드를 봅니다.

Bash
kubectl describe node <node-name>
TEXT
Conditions:
  Type             Status
  MemoryPressure   False
  DiskPressure     True     <-- 범인
  PIDPressure      False
  Ready            True

True인 Condition이 곧 원인입니다. 위 예시는 디스크 압박입니다. JSON으로 빠르게 뽑고 싶다면:

Bash
kubectl get node <node-name> -o jsonpath='{range .status.conditions[*]}{.type}={.status}{"\n"}{end}'

2-4. kubelet eviction 임계값 이해

kubelet은 두 종류의 임계값으로 동작합니다.

  • --eviction-hard: 즉시 축출. grace period 없음. 예) memory.available<100Mi, nodefs.available<10%, imagefs.available<15%, pid.available<10%
  • --eviction-soft: 임계값에 도달하면 --eviction-soft-grace-period 동안 기다렸다가, 회복되지 않으면 축출. 갑작스러운 spike에 과민반응하지 않게 해줍니다.

흐름은 이렇습니다. soft 임계값 도달 → grace period 카운트다운 → 그 사이 hard 임계값까지 떨어지면 즉시 축출, 아니면 grace 만료 후 축출. nodefs는 emptyDir·로그·컨테이너 쓰기 레이어가 쓰는 디스크, imagefs는 이미지/컨테이너 레이어 저장소를 가리킵니다.

3. 원인별 해결: Before/After

3-1. ephemeral-storage 폭증 (가장 흔함)

로그를 stdout 대신 emptyDir에 쌓거나, 임시 파일을 무한정 만드는 워크로드가 노드 nodefs를 가득 채워 DiskPressure를 유발합니다. 게다가 ephemeral-storage request가 0이면 스케줄러가 이 부담을 전혀 계산하지 못합니다.

Before — 제한 없음:

YAML
resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
  # ephemeral-storage 미설정 → 스케줄러가 디스크 압박을 모름

After — ephemeral-storage 명시 + emptyDir sizeLimit:

YAML
resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
    ephemeral-storage: "1Gi"
  limits:
    ephemeral-storage: "2Gi"   # 초과 시 이 Pod만 Evicted
volumes:
  - name: cache
    emptyDir:
      sizeLimit: "1Gi"          # 볼륨 자체에 상한

limits.ephemeral-storage를 걸면, 노드 전체를 끌어내리기 전에 범인 Pod만 먼저 축출되어 다른 Pod를 보호합니다.

3-2. 이미지/컨테이너 GC 임계값 조정

imagefs가 안 쓰는 이미지로 가득 차도 DiskPressure가 뜹니다. kubelet GC 설정(KubeletConfiguration)을 조정합니다.

YAML
# kubelet config
imageGCHighThresholdPercent: 80   # 이 % 넘으면 GC 시작
imageGCLowThresholdPercent: 70    # 이 %까지 정리
evictionHard:
  imagefs.available: "15%"

3-3. 노드 디스크 직접 정리 (containerd/CRI-O 표준)

2026년 환경에선 dockershim이 사라진 지 오래라 crictl 기반 정리가 표준입니다.

Bash
# 어디가 찼는지 점검
du -sh /var/lib/containerd/* | sort -rh | head
journalctl --disk-usage

# 미사용 이미지 정리 (containerd/CRI-O)
crictl rmi --prune

# (구형 docker 노드라면)
docker system prune -a

3-4. requests/limits 미설정 → 스케줄링 개선

requests가 없으면 스케줄러는 노드를 "텅 비었다"고 착각해 Pod를 계속 몰아넣고, 결국 노드가 과적되어 축출이 연쇄적으로 발생합니다. 표준 리소스 명세를 박아두면 스케줄러가 애초에 압박받는 노드를 피해 배치합니다.

YAML
resources:
  requests: { cpu: "200m", memory: "256Mi", ephemeral-storage: "1Gi" }
  limits:   { cpu: "500m", memory: "512Mi", ephemeral-storage: "2Gi" }

실무 경험 한마디: 저는 대규모 클러스터에서 Evicted 폭증의 80%가 "특정 노드 한 대"에서 시작되는 걸 자주 봤습니다. Karpenter나 Cluster Autoscaler 환경에선 이게 더 까다로운데, 오토스케일러가 노드를 줄이려고 Pod를 빼는 정상 동작과, 자원 부족으로 인한 비정상 축출이 섞여 보이기 때문입니다. 그래서 항상 describe pod의 Message부터 확인합니다. Reason이 Evictedlow on resource면 자원 문제, 그냥 노드 종료면 오토스케일러 동작입니다. 둘은 대응이 정반대예요.

4. Evicted Pod 일괄 정리 + 재발 방지

4-1. 쌓인 Evicted Pod 청소

Bash
# Failed phase 한정으로 일괄 삭제 (Running/Pending 건드리지 않음)
kubectl delete pods --field-selector status.phase=Failed -A

⚠️ 주의: 이건 청소일 뿐 해결이 아닙니다. 근본 원인(디스크·메모리·limits)이 그대로면 몇 분 뒤 다시 Evicted가 쌓입니다. 반드시 3장의 원인을 먼저 제거하세요.

4-2. PodDisruptionBudget으로 동시 축출 제한

YAML
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
spec:
  minAvailable: 2        # 최소 2개는 항상 유지
  selector:
    matchLabels:
      app: web

PDB는 노드 드레인·오토스케일 축소 시 한꺼번에 빠지는 걸 막아 서비스 가용성을 지킵니다(단, hard eviction은 PDB를 무시할 수 있다는 점은 기억하세요).

4-3. 모니터링 + 알림

node_exporter와 kube-state-metrics로 노드 자원을 선제적으로 감시합니다.

PROMQL
# nodefs 가용량 15% 미만 알림
node_filesystem_avail_bytes{mountpoint="/"} 
  / node_filesystem_size_bytes{mountpoint="/"} < 0.15

권장 알림 임계값:

  • 디스크 가용량 < 20% (warning), < 15% (critical, hard eviction 직전)
  • 메모리 available < 300Mi (warning)
  • OpenCost 등으로 ephemeral-storage까지 사용량·비용 추적하면 폭증 워크로드를 사전에 잡을 수 있습니다.

5. 결론: 재발 방지 체크리스트

  • 모든 워크로드에 requests/limits(cpu·memory·ephemeral-storage) 표준화
  • emptyDir에 sizeLimit 설정
  • kubelet imageGCHighThresholdPercent/evictionHard 점검
  • crictl rmi --prune 정기 실행 또는 자동화
  • 중요 서비스에 PDB 적용
  • node_exporter 디스크/메모리 알림 임계값 설정

Evicted를 만나면 순서만 기억하세요. describe pod로 어떤 자원인지 → describe node Conditions로 어느 노드인지 → 원인별 해결. 이 3단계면 5분 안에 끝납니다.

다음 편에서는 Pending에서 스케줄링이 안 되는 Pod(Insufficient cpu/memory, taint/toleration, nodeAffinity) 진단을 다룹니다.

자주 묻는 질문 (FAQ)

Q. Evicted Pod는 왜 자동으로 안 사라지나요? A. kubelet은 축출 기록을 남기기 위해 Pod 오브젝트를 Failed phase로 보존합니다. 디버깅용 흔적이라 GC 대상이 아니어서, kubectl delete pods --field-selector status.phase=Failed로 직접 정리해야 합니다.

Q. limits만 걸면 해결되나요? A. 아닙니다. limits는 개별 Pod의 폭주를 막지만, 스케줄러가 노드 배치를 판단하는 건 requests입니다. requests가 없으면 노드 과적이 계속되니 둘 다 설정해야 합니다.

Q. ephemeral-storage limit을 초과하면 OOMKilled인가요, Evicted인가요? A. Evicted입니다. ephemeral-storage 초과는 메모리가 아니라 디스크 문제라 exit code 137(OOMKilled)이 아니라 kubelet에 의한 축출(Reason: Evicted)로 처리됩니다.

Q. 노드 한 대만 계속 압박받으면 어떻게 하나요? A. 특정 노드만 반복 압박이면 그 노드에 무거운 워크로드가 쏠렸을 가능성이 큽니다. PodTopologySpread로 분산하거나, 해당 노드를 cordon/drain 후 점검하세요. Karpenter 환경이라면 노드 타입(디스크 용량) 자체를 키우는 것도 방법입니다.

Q. soft eviction grace period는 얼마로 두는 게 좋나요? A. 일반적으로 메모리는 짧게(30s~1m30s), 디스크는 다소 길게(2m 내외) 둡니다. 너무 길면 hard 임계값까지 떨어져 강제 축출되니, 모니터링 알림 주기와 맞춰 조정하는 걸 권장합니다.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.

작성 · Content Reviewer·검토 · 사람 편집자·발행 · 2026년 6월 11일

댓글

불러오는 중...