/인프라/OOMKilled exit code 137 해결: kubectl 30초 진단·5분 복구 런북
인프라OOMKilledexit code 137

OOMKilled exit code 137 해결: kubectl 30초 진단·5분 복구 런북

Pod가 OOMKilled exit code 137로 죽었나요? kubectl describe·jsonpath·dmesg로 컨테이너 limit 초과인지 노드 메모리 압박인지 30초에 가르고, requests/limits 재산정·JVM 힙 설정·Guaranteed QoS·알람까지 복붙 명령으로 5분 복구합니다.

OOMKilled exit code 137 해결: kubectl 30초 진단·5분 복구 런북

OOMKilled exit code 137 해결: kubectl 30초 진단·5분 복구 런북

kubectl describe pod 출력에서 Last State: Terminated, Reason: OOMKilled, Exit Code: 137을 보고 들어오셨다면 잘 찾아오셨습니다. 개념 설명은 뒤로 미루고, 지금 죽은 Pod부터 살립니다.

exit code 137은 128 + 9, 즉 SIGKILL을 받고 강제 종료됐다는 뜻입니다. 누가 죽였느냐가 핵심인데, 범인은 보통 셋 중 하나입니다. 컨테이너가 자기 limit을 넘겼거나(OOMKilled), 노드 전체가 메모리 부족이라 쫓겨났거나(Evicted), 사람이 직접 죽였거나(SIGKILL). 30초 안에 해야 할 단 한 가지는, 내 137이 이 셋 중 어느 분기인지 가르는 것입니다. 분기를 잘못 잡으면 limit만 올리다 노드 압박을 못 보거나, 반대로 멀쩡한 limit을 건드립니다.

30초 진단 분기표: 내 137은 어느 갈래인가

아래 표에서 본인 증상을 찾고, 바로 다음 섹션의 해당 명령으로 가세요.

증상 (describe / events에서 보이는 문구)진단다음 행동
Exit Code: 137 + Reason: OOMKilled컨테이너 메모리 limit 초과진단 명령 A·B·C → 복구 1·2
Reason: Evicted, message: The node was low on resource: memory노드 전체 메모리 압박진단 명령 D·E → 복구 3
Exit Code: 137인데 OOMKilled 표시 없음수동 kubectl delete --grace-period=0 또는 외부 SIGKILLevents·audit log 확인, 배포/스크립트 점검

핵심 구분점: OOMKilled는 컨테이너 단위, Evicted는 Pod 단위(노드 스케줄러가 쫓아냄) 입니다. kubectl get pod에서 STATUS가 OOMKilled로 안 뜨고 정상 Running처럼 보여도, 재시작 카운트가 올라간다면 lastState를 꼭 확인하세요.

복붙 진단 명령 모음

위에서 분기를 잡았으면 그대로 복사해 실행하세요. <pod>만 본인 것으로 바꾸면 됩니다.

Bash
# A) Last State 한눈에 보기 — Reason과 Exit Code가 여기 나옵니다
kubectl describe pod <pod> | grep -A5 "Last State"

# B) 스크립트/자동화용: reason + exitCode만 정확히 추출
kubectl get pod <pod> -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}{"\n"}{.status.containerStatuses[0].lastState.terminated.exitCode}{"\n"}'
# 출력 예: OOMKilled / 137

# C) 죽기 직전 실제 사용량 추세 (metrics-server 필요)
kubectl top pod <pod> --containers

# D) OOMKilling 이벤트를 클러스터 전역에서 수집
kubectl get events --field-selector reason=OOMKilling -A

# E) 노드로 들어가 커널 OOM killer 로그 직접 확인 (가장 확실한 증거)
#   nsenter/디버그 컨테이너로 노드 진입 후:
dmesg -T | grep -i "Out of memory: Killed process"
journalctl -k | grep -i oom

E의 dmesgKilled process ... (java) 같은 줄이 보이면 커널 OOM killer가 그 프로세스를 직접 골라 죽였다는 빼도 박도 못할 증거입니다. cgroup v2가 기본인 2026년 다수 배포판에서는 OOM이 cgroup 단위로 더 정확히 격리되어, 형제 컨테이너 영향 없이 해당 컨테이너만 죽는 경우가 많습니다.

원인별 복구 + 재발 방지

복구 1: limits 재산정

kubectl top으로 본 실사용량 + 여유 30%를 limit으로 잡습니다. requests는 평소 사용량, limit은 피크 기준.

YAML
# Before — limit이 실사용보다 빠듯해 피크 때 즉사
resources:
  requests: { memory: "256Mi" }
  limits:   { memory: "512Mi" }

# After — top 측정 피크 700Mi 기준 여유 확보
resources:
  requests: { memory: "768Mi" }
  limits:   { memory: "1Gi" }

복구 2: 런타임 힙 vs 컨테이너 limit 불일치 (가장 흔한 함정)

JVM·Node.js에서 런타임 힙 설정이 컨테이너 limit과 같거나 크면 시작하자마자, 혹은 GC 직전에 OOMKilled가 납니다. 힙 외 메타스페이스/스택/네이티브 버퍼도 메모리를 먹기 때문입니다.

YAML
# JVM — 절대값 -Xmx 박지 말고 limit 비율로 (limit 인식)
env:
  - name: JAVA_TOOL_OPTIONS
    value: "-XX:MaxRAMPercentage=75.0"   # limit의 75%만 힙에, 나머지는 네이티브 여유
resources:
  limits: { memory: "1Gi" }              # 힙 ~768Mi + 여유 256Mi

# Node.js — old space를 limit보다 작게
env:
  - name: NODE_OPTIONS
    value: "--max-old-space-size=768"    # limit 1Gi 대비 안전

-Xmx1glimits.memory: 1Gi처럼 같게 잡는 게 대표적 즉사 패턴입니다. 반드시 힙 < limit으로 두세요.

복구 3: limit 미설정으로 인한 eviction

limit이 없으면 Pod는 BestEffort 또는 Burstable QoS가 되고, 노드 메모리가 부족해지면 이런 Pod가 가장 먼저 쫓겨납니다(Evicted). Karpenter 같은 노드 오토스케일 환경에서는 빈 노드를 줄이려다 압박이 생겨 eviction이 더 자주 보입니다. requests/limits를 명시해 우선순위를 끌어올리세요.

재발 방지: Guaranteed QoS + 모니터링

가장 중요한 워크로드는 requests.memory == limits.memory로 맞춰 Guaranteed QoS를 확보하면 eviction 순위에서 가장 뒤로 빠집니다.

YAML
resources:
  requests: { cpu: "500m", memory: "1Gi" }
  limits:   { cpu: "500m", memory: "1Gi" }   # 메모리 동일 → Guaranteed

추천값 산출은 VPA에 맡기세요. 1.27+에서 in-place resize가 베타화되며 재시작 없이 메모리를 조정하는 흐름이 자리잡고 있습니다.

YAML
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata: { name: app-vpa }
spec:
  targetRef: { apiVersion: apps/v1, kind: Deployment, name: app }
  updatePolicy: { updateMode: "Off" }   # 우선 추천값만 받아보기

HPA는 메모리 기반 스케일이 가능하지만, 메모리는 줄어들지 않는 특성(누수·캐시) 때문에 CPU 기준만큼 안정적이지 않습니다. 메모리는 VPA로 적정값을 잡고, 트래픽 스케일은 HPA(CPU/커스텀 메트릭)로 나누는 편을 권합니다.

마지막으로 죽기 전에 알림을 받으세요. OpenTelemetry로 메모리 메트릭이 표준화되는 추세지만, 룰 자체는 동일합니다.

YAML
# Prometheus alert: limit의 90% 도달 시 사전 경고
- alert: PodMemoryNearLimit
  expr: container_memory_working_set_bytes / container_spec_memory_limit_bytes > 0.9
  for: 5m
  labels: { severity: warning }
  annotations:
    summary: "{{ $labels.pod }} 메모리 limit 90% 초과 — OOMKilled 임박"

실무 한 마디: 현장에서 137 장애의 절반 이상은 limit이 작아서가 아니라 JVM 힙을 limit과 같게 박아둔 설정 미스였습니다. limit부터 무작정 올리기 전에 MaxRAMPercentage부터 확인하면 비용도 아끼고 근본 원인도 잡습니다.

결론: 형제 에러로 오진하지 마세요

137을 다른 종료 코드와 헷갈리면 엉뚱한 곳을 파게 됩니다.

코드/상태의미구분 키워드 → 가야 할 글
137SIGKILL (OOM 또는 강제종료)OOMKilled / dmesg OOM → 이 글
143SIGTERM (graceful 종료 실패)preStop/종료 타임아웃 → graceful shutdown 편
Evicted노드 메모리·디스크 압박node was low on resource → 본문 복구 3
CrashLoopBackOff재시작 루프 (OOM 반복 시 전이)Back-off restarting → 이전 편 CrashLoopBackOff 글

OOM이 반복되면 결국 CrashLoopBackOff로 전이되므로, 반복 재시작이 보이면 lastState reason을 먼저 확인해 진짜 원인이 OOM인지 가르는 게 핵심입니다.

다음 편(K8s_Troubleshooting_Guide 20편)에서는 exit code 143과 SIGTERM, preStop 훅으로 graceful shutdown을 보장하는 런북을 다룹니다.

참고: 공식 문서

이 글에서 다루는 동작·설정·에러의 1차 출처는 다음 공식 문서입니다. 버전별 옵션과 정확한 동작은 여기서 확인하세요.

자주 묻는 질문 (FAQ)

Q. limit을 올렸는데도 또 OOMKilled가 납니다. 왜죠? A. 메모리 누수이거나 JVM/Node 힙 설정이 limit을 따라 올라가지 않은 경우입니다. kubectl top pod --containers로 사용량이 계속 우상향하면 누수, 시작 직후 즉사하면 힙 설정(MaxRAMPercentage, --max-old-space-size)을 점검하세요.

Q. OOMKilled인데 kubectl get pod엔 Running으로 보입니다. A. 컨테이너가 죽고 재시작되어 현재는 Running일 수 있습니다. kubectl describe podLast StateRESTARTS 카운트, jsonpath로 lastState.terminated.reason을 확인하면 과거 OOM 이력이 드러납니다.

Q. exit code 137인데 OOMKilled 표시가 전혀 없습니다. A. 커널 OOM이 아니라 외부 SIGKILL일 가능성이 큽니다. kubectl delete --grace-period=0, 배포 도구, 노드 종료/드레인, 또는 라이브니스 프로브 실패 후 강제 종료를 의심하고 events와 audit log를 확인하세요.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 자료 조사와 1차 초안 작성을 담당하고, 사람 편집자가 사실관계·출처·톤과 맥락을 검토한 뒤 발행했습니다. 환경(OS·버전)에 따라 결과가 다를 수 있으니 적용 전 공식 문서를 함께 확인하세요. 오류를 발견하시면 이메일로 제보해 주세요 — 확인 후 신속히 정정합니다.

초안 · AI (Content Reviewer)·검토 · Nodelog 편집자·발행 ·
관련 공식 문서Kubernetes 공식 문서

댓글

첫 번째 댓글을 남겨보세요.