레이블이 메모리에러인 게시물을 표시합니다. 모든 게시물 표시
레이블이 메모리에러인 게시물을 표시합니다. 모든 게시물 표시

토요일

Kubernetes OOMKilled 에러 해결 (Exit Code 137) — Spring Boot/Java Pod 메모리 초과 완전 정리

🔍 검색 키워드: kubernetes oomkilled, exit code 137, pod oomkilled 해결, spring boot kubernetes memory, k8s OOMKilled java, 쿠버네티스 메모리 에러, kubectl oomkilled 원인

증상

Pod를 describe 했더니 이런 게 나온다.

State:          Running
  Last State:   Terminated
    Reason:     OOMKilled
    Exit Code:  137
    Started:    ...
    Finished:   ...

kubectl get pods에서 STATUS가 OOMKilled 또는 Error로 뜨고, Restarts 카운터가 계속 올라간다.

원인

컨테이너가 설정된 resources.limits.memory를 초과해서 커널이 강제로 프로세스를 죽인 것이다. Exit Code 137은 SIGKILL(128 + 9)을 의미한다.

Java 기반 Spring Boot 앱에서 특히 자주 발생하는 이유가 있다:

  • JVM íž™ 크기는 컨테이너 limit와 별개로 동작
  • JVM은 기본적으로 호스트 ì „ì²´ 메모리를 기준으로 힙을 잡으려 함
  • Off-heap 메모리(Metaspace, Stack, Direct Buffer 등)ê°€ íž™ 외에 추가로 소비됨

결과적으로 -Xmx512m 설정에 limit를 512Mi로 주면 반드시 OOMKilled 된다.

빠른 진단

1. 현재 메모리 사용량 확인

# Pod 메모리 현황
kubectl top pod <pod-name> -n <namespace>

# 상세 상태 확인
kubectl describe pod <pod-name> -n <namespace> | grep -A 10 "Last State"

# 최근 종료된 컨테이너 로그
kubectl logs <pod-name> --previous -n <namespace>

2. 리소스 설정 확인

kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].resources}' -n <namespace>
{
  "limits": { "memory": "512Mi" },
  "requests": { "memory": "256Mi" }
}

3. 노드 전체 메모리 현황

kubectl top nodes
kubectl describe node <node-name> | grep -A 5 "Allocated resources"

체크리스트

항목확인 방법정상 기준
Pod OOMKilled 여부kubectl describe pod → Reason: OOMKilledCompleted 또는 Running
현재 메모리 사용량kubectl top podlimit의 70% 이하
JVM 힙 설정JAVA_OPTS 환경변수limit의 50~75%
Metaspace 제한-XX:MaxMetaspaceSize256m 이하 권장
requests ≤ limitsDeployment YAML항상 requests ≤ limits
OOM Heap Dump-XX:+HeapDumpOnOutOfMemoryError파일 생성 확인

해결 방법

방법 1: JVM 힙을 컨테이너 limit에 맞게 명시 설정

가장 흔한 실수. limit 대비 JVM 힙 비율을 반드시 맞춰야 한다.

# deployment.yaml
containers:
  - name: my-spring-app
    image: my-app:latest
    resources:
      requests:
        memory: "512Mi"
        cpu: "250m"
      limits:
        memory: "1Gi"
        cpu: "1000m"
    env:
      - name: JAVA_OPTS
        value: "-Xms256m -Xmx768m -XX:MaxMetaspaceSize=256m -XX:+UseContainerSupport"

메모리 배분 계산 예시 (limit: 1Gi = 1024Mi)

영역크기비고
JVM Heap (-Xmx)768Milimit의 약 75%
Metaspace256Mi-XX:MaxMetaspaceSize
Stack, Direct Buffer 등~100MiOS/JVM 관리
총계~1124Milimit 초과 → OOMKilled 위험!
위 예시도 빡빡하다. limit를 1.5Gi 이상으로 올리거나 Metaspace를 줄여야 한다.

방법 2: -XX:+UseContainerSupport 활용 (Java 11+)

Java 11 이상에서는 JVM이 컨테이너 limit를 자동 인식한다.

env:
  - name: JAVA_OPTS
    value: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -XX:MaxMetaspaceSize=256m"

-XX:MaxRAMPercentage=75.0은 컨테이너 limit의 75%를 자동으로 힙에 할당한다. -Xmx 하드코딩 없이 limit만 조정해도 알아서 따라온다.

Java 8 사용자: update 191 이상이면 UseContainerSupport 지원됨. 미만이면 -Xmx를 직접 계산해서 설정해야 한다.

방법 3: 실제 메모리 누수인 경우 — 힙 덤프 분석

limit를 올려도 계속 OOMKilled 된다면 실제 누수를 의심해야 한다.

env:
  - name: JAVA_OPTS
    value: >-
      -Xmx768m
      -XX:+HeapDumpOnOutOfMemoryError
      -XX:HeapDumpPath=/tmp/heapdump.hprof
      -XX:+ExitOnOutOfMemoryError

덤프 파일 로컬로 복사:

kubectl cp <namespace>/<pod-name>:/tmp/heapdump.hprof ./heapdump.hprof

Eclipse MAT 또는 VisualVM으로 열어서 Leak Suspects Report 돌린다.

방법 4: Spring Boot Actuator로 메모리 실시간 모니터링

<!-- pom.xml -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application.yaml
management:
  endpoints:
    web:
      exposure:
        include: health,metrics,prometheus
# JVM 메모리 사용량 확인
curl http://localhost:8080/actuator/metrics/jvm.memory.used
curl http://localhost:8080/actuator/metrics/jvm.memory.max

Prometheus + Grafana로 시계열 수집하면 OOMKilled 발생 패턴을 잡기 쉽다.

방법 5: Node.js / Python Pod라면

Node.js:

env:
  - name: NODE_OPTIONS
    value: "--max-old-space-size=768"
resources:
  limits:
    memory: "1Gi"

Python (Gunicorn):

command: ["gunicorn", "--workers=2", "--threads=2", "app:app"]
resources:
  limits:
    memory: "512Mi"

HPA와 VPA 연계

단기 해결은 limit 증가지만, 장기적으로는 자동 스케일링이 필요하다.

# HPA - 메모리 기준 스케일 아웃
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-spring-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-spring-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 70

정리

OOMKilled는 대부분 limit 설정 부족 아니면 JVM 힙과 limit 불일치에서 온다. Spring Boot 앱이라면 순서대로 확인:

  1. kubectl describe pod로 OOMKilled 확인
  2. kubectl top pod로 실사용량 파악
  3. JAVA_OPTS에서 -Xmx vs limit 비율 점검
  4. Java 11+이면 UseContainerSupport + MaxRAMPercentage로 교체
  5. 그래도 반복되면 힙 덤프 분석

limit를 무한정 올리는 게 해결책이 아니다. 적정 비율로 잡고, 모니터링으로 추세를 보면서 튜닝하는 게 맞다.