Container Orchestration/Kubernetes

Kubernetes OOMKilled 대응 전략: 무작정 메모리만 늘리지 말자!

Somaz 2026. 6. 10. 00:00
728x90
반응형

Overview

Kubernetes를 운영하다 보면 누구나 한 번쯤 마주하는 상황이 있다. 바로 `OOMKilled` (Out of Memory Killed) 에러이다. 파드가 설정된 메모리 제한(Limit)을 초과하여 강제로 종료되는 이 현상은, 많은 DevOps 엔지니어들에게 골치 아픈 문제이다.

 

 

대부분의 팀이 이 문제를 어떻게 해결할까요? "일단 메모리를 2배로 늘리고 다시 배포해보자"는 식의 주먹구구식 대응이 흔하다. 하지만 이는 근본적인 해결책이 아니며, 리소스 낭비와 비용 증가로 이어진다.

 

 

이 글에서는 OOMKilled 문제에 대한 체계적인 접근 방법과 실무에서 바로 적용할 수 있는 해결 전략을 다룬다. 단순히 리소스를 늘리는 것이 아니라, 왜 발생했는지 파악하고, 적정 리소스를 산정하며, 재발을 방지하는 프로세스를 구축하는 것이 목표이다.

 

 

 

 

 

 

 


 

1. 흔한 잘못된 대응 방식

 

"메모리 2배 증설 + 기도" 패턴

많은 팀에서 OOMKilled가 발생하면 다음과 같이 대응한다.

# Before
resources:
  limits:
    memory: "512Mi"

# After (에러 발생 후)
resources:
  limits:
    memory: "1Gi"  # 그냥 2배로 늘림

 

 

이 방식의 문제점

  • 근본 원인을 파악하지 못함
  • 메모리 누수(Memory Leak)가 있다면 시간이 지나면 또 OOM 발생
  • 리소스 낭비와 클러스터 비용 증가
  • 재발 가능성이 높음

 

 

왜 이런 방식이 일반적일까?

  • 가시성 부족: 실제 메모리 사용량을 정확히 모름
  • 시간 압박: 프로덕션 장애 상황에서 빠른 조치 필요
  • 책임 회피: 개발팀 vs 인프라팀 간의 책임 소재가 불분명
  • 도구 부재: 적정 리소스를 산정할 방법을 모름

 

 

 

 

2. 올바른 접근법 :  ① 자동화 도구 활용

 

 

VPA (Vertical Pod Autoscaler) - 추천 모드

VPA를 "Recommendation Only" 모드로 실행하면 실제 사용량을 기반으로 적정 리소스를 제안받을 수 있다.

 

 

 

설치 및 설정

# VPA 설치
git clone https://github.com/kubernetes/autoscaler.git
cd autoscaler/vertical-pod-autoscaler
./hack/vpa-up.sh
 
 
 
# vpa-recommendation.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: my-app-vpa
spec:
  targetRef:
    apiVersion: "apps/v1"
    kind: Deployment
    name: my-app
  updatePolicy:
    updateMode: "Off"  # 추천만 하고 자동 적용 안 함
  resourcePolicy:
    containerPolicies:
    - containerName: "*"
      minAllowed:
        memory: "128Mi"
      maxAllowed:
        memory: "8Gi"

 

 

 

결과 확인

kubectl describe vpa my-app-vpa

# 출력 예시
Recommendation:
  Container Recommendations:
    Container Name:  my-app
    Lower Bound:
      Memory:  256Mi
    Target:
      Memory:  512Mi    # 추천값
    Uncapped Target:
      Memory:  512Mi
    Upper Bound:
      Memory:  1Gi

 

 

 

Best Practice

  • 최소 2주~1개월 정도 데이터 수집 후 적용
  • 피크 타임의 사용량도 고려
  • 추천값에 20-30% 버퍼 추가

 

 

 

Goldilocks - VPA를 쉽게 시각화

Goldilocks는 VPA 추천값을 Grafana처럼 대시보드로 보여주는 도구이다.

# Helm으로 설치
helm repo add fairwinds-stable https://charts.fairwinds.com/stable
helm install goldilocks fairwinds-stable/goldilocks \
  --namespace goldilocks \
  --create-namespace

 

 

 

네임스페이스 활성화

kubectl label namespace production goldilocks.fairwinds.com/enabled=true

 

 

 

대시보드 접속

kubectl -n goldilocks port-forward svc/goldilocks-dashboard 8080:80
# http://localhost:8080 접속
  • 대시보드에서 모든 워크로드의 현재 설정값 vs 추천값을 한눈에 비교할 수 있다.
  • ingress를 사용해도 된다.
  • 자세한 설치 방법은 https://github.com/FairwindsOps/charts 해당 링크를 참고하면 된다.

 

 

KRR (Kubernetes Resource Recommender)

Robusta에서 만든 CLI 도구로, Prometheus 메트릭을 분석하여 추천값을 제공한다

# 설치
pip install krr

# 실행
krr simple --prometheus-url http://prometheus:9090


# 출력 예시
| Namespace   | Name          | Container | Current Memory | Recommended | Severity |
|-------------|---------------|-----------|----------------|-------------|----------|
| production  | payment-api   | app       | 1Gi           | 512Mi       | HIGH      |
| production  | user-service  | app       | 512Mi         | 2Gi         | CRITICAL  |

 

 

 

 

 


 

 

 

 

 

3. 올바른 접근법 : 애플리케이션 최적화

 

 

 

메모리 누수 탐지

단순히 메모리를 늘리는 것만으로는 해결되지 않는 경우가 많다. 메모리 누수(Memory Leak)를 의심해야 한다.

 

 

증상

  • 메모리 사용량이 시간이 지날수록 계속 증가
  • 재시작 직후엔 정상, 며칠 후 OOM
  • Limit을 늘려도 결국 다시 OOM

 

 

 

Continuous Profiling 도구

 

 

Grafana Pyroscope 활용

# pyroscope-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: app
        image: my-app:latest
        env:
        # Pyroscope 프로파일링 활성화
        - name: PYROSCOPE_SERVER_ADDRESS
          value: "http://pyroscope:4040"
        - name: PYROSCOPE_APPLICATION_NAME
          value: "my-app"
  • Pyroscope는 Flamegraph를 통해 어떤 함수가 메모리를 많이 사용하는지 시각적으로 보여준다.

 

 

 

언어별 최적화 팁

 

 

Go 언어

// 잘못된 예 - 메모리 누수
resp, err := http.Get(url)
// resp.Body.Close() 호출 안 함!

// 올바른 예
resp, err := http.Get(url)
if err != nil {
    return err
}
defer resp.Body.Close()  // 반드시 Close 호출

// GOMEMLIMIT 설정
// 컨테이너 메모리의 80% 정도로 설정
// deployment.yaml에 환경변수 추가
env:
- name: GOMEMLIMIT
  value: "800MiB"  # 컨테이너 limit이 1Gi인 경우

 

 

 

Node.js

// package.json scripts 수정
{
  "scripts": {
    "start": "node --max-old-space-size=512 app.js"
  }
}
 
 
 
# deployment.yaml
containers:
- name: app
  resources:
    limits:
      memory: "1Gi"
  env:
  - name: NODE_OPTIONS
    value: "--max-old-space-size=768"  # 컨테이너 limit의 75%

 

 

 

Java

# deployment.yaml
containers:
- name: app
  resources:
    limits:
      memory: "2Gi"
  env:
  - name: JAVA_OPTS
    value: "-Xmx1536m -Xms512m"  # 최대 힙 크기를 limit의 75%로

 

 

 

 

4. 올바른 접근법 : 프로세스 개선

 

 

사전 예방: 부하 테스트

배포 전에 리소스 사용량을 미리 파악한다.

 

 

 

k6를 사용한 부하 테스트

// load-test.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export let options = {
  stages: [
    { duration: '2m', target: 100 },  // 2분간 100 VU로 증가
    { duration: '5m', target: 100 },  // 5분간 유지
    { duration: '2m', target: 200 },  // 2분간 200 VU로 증가
    { duration: '5m', target: 200 },  // 5분간 유지
  ],
};

export default function () {
  let res = http.get('http://my-app:8080/api/users');
  check(res, { 'status is 200': (r) => r.status === 200 });
  sleep(1);
}

 

 

 

GitLab CI/CD 통합

# .gitlab-ci.yml
performance-test:
  stage: test
  image: grafana/k6:latest
  script:
    - k6 run --out json=results.json load-test.js
    # 메모리 사용량 수집
    - kubectl top pod -l app=my-app -n staging
  artifacts:
    reports:
      performance: results.json
  only:
    - merge_requests

 

 

 

 

모니터링 및 알림 설정

 

 

Prometheus 알림 규칙

# prometheus-rules.yaml
groups:
- name: memory-alerts
  rules:
  - alert: PodMemoryUsageHigh
    expr: |
      (container_memory_working_set_bytes / container_spec_memory_limit_bytes) > 0.9
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "Pod {{ $labels.pod }} memory usage is above 90%"
      description: "Pod {{ $labels.pod }} in namespace {{ $labels.namespace }} is using {{ $value | humanizePercentage }} of its memory limit"

  - alert: PodMemoryUsageCritical
    expr: |
      (container_memory_working_set_bytes / container_spec_memory_limit_bytes) > 0.95
    for: 2m
    labels:
      severity: critical
    annotations:
      summary: "Pod {{ $labels.pod }} memory usage is above 95%"

 

 

 

Slack 알림 연동

  • 메모리 사용률이 90% 이상이면 개발팀에 자동 알림
  • OOMKilled 발생 시 즉시 알림 + 해당 파드의 최근 메모리 그래프 첨부

 

 

 

 


 

 

 

5. 실전 대응 플레이북

 

 

Step 1: 긴급 대응 (5분 이내)

# 1. 현재 상태 확인
kubectl get pod -n production | grep -E "OOMKilled|Error"

# 2. 이벤트 로그 확인
kubectl describe pod <pod-name> -n production | grep -A 10 "Events:"

# 3. 메모리 사용량 확인
kubectl top pod <pod-name> -n production

# 4. 임시 조치 (긴급한 경우)
kubectl scale deployment <deployment-name> -n production --replicas=5
# 또는 메모리 limit 임시 증설

 

 

Step 2: 원인 분석 (30분 이내)

# 1. Prometheus에서 메모리 트렌드 확인
# 쿼리: container_memory_working_set_bytes{pod=~"my-app.*"}

# 2. 애플리케이션 로그 확인
kubectl logs <pod-name> -n production --previous  # 이전 파드 로그

# 3. VPA 추천값 확인
kubectl describe vpa my-app-vpa

# 4. 프로파일링 데이터 확인 (Pyroscope 대시보드)
```

### Step 3: 근본 원인 해결 (1-2일)

**체크리스트:**

- [ ] 메모리 누수가 있는가? (프로파일링 결과 확인)
- [ ] 언어별 메모리 설정이 적절한가? (GOMEMLIMIT, XMX 등)
- [ ] 부하 테스트 결과는? (피크 시간대 사용량)
- [ ] VPA 추천값은? (최소 2주 데이터)
- [ ] 애플리케이션 최적화 가능한가? (불필요한 캐싱, 큰 객체 등)

---

## 6. 조직 문화 개선

### 개발팀과의 협업 체계

**명확한 책임 분리:**
```
[인프라팀]
- 클러스터 리소스 관리
- 모니터링 시스템 구축
- 적정 리소스 산정 도구 제공

[개발팀]
- 애플리케이션 메모리 최적화
- 부하 테스트 수행
- 메모리 누수 수정

 

 

 

PR 승인 프로세스 개선

## PR Checklist

- [ ] 부하 테스트 완료 (k6 결과 첨부)
- [ ] 메모리 사용량 프로파일링 완료
- [ ] 리소스 request/limit 값 적절성 검토
- [ ] VPA 추천값과 비교 (±20% 이내)

 

 

 

 

 

 

 


 

 

 

 

 

마무리

Kubernetes의 OOMKilled 문제는 단순히 "메모리를 늘리면 해결"되는 것이 아니다. 이는 애플리케이션의 리소스 사용 패턴을 이해하고, 적절한 도구로 가시성을 확보하며, 개발팀과 협업하여 근본 원인을 해결해야 하는 엔지니어링 문제이다.

 

 

핵심 요약

  1. 도구 활용: VPA, Goldilocks, KRR로 데이터 기반 의사결정
  2. 애플리케이션 최적화: 메모리 누수 탐지 및 언어별 설정 최적화
  3. 프로세스 구축: 부하 테스트, 모니터링, 알림 체계 수립
  4. 조직 문화: 개발팀과의 명확한 책임 분리 및 협업

 

 

무작정 메모리를 2배로 늘리고 기도하는 대신, 체계적인 접근으로 안정적이고 비용 효율적인 Kubernetes 클러스터를 운영하시길 바란다.

 

 

 

 

 

 

 

 

 

 

 


Reference

 

 

Somaz | DevOps Engineer | Kubernetes & Cloud Infrastructure Specialist

728x90
반응형