- Published on
리눅스 OOM Killer로 프로세스 죽음 진단·방지
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버에서 애플리케이션이 예고 없이 종료되고, 애플리케이션 로그에는 아무것도 남지 않는 상황이 종종 있습니다. Killed 한 줄만 찍히거나, systemd가 서비스 재시작을 반복하는데 원인이 불명확한 경우도 많습니다. 이때 가장 먼저 의심해야 할 것이 리눅스 OOM(Out-Of-Memory) Killer입니다.
OOM Killer는 “메모리가 부족하니 누군가를 죽여서라도 시스템을 살리자”라는 커널의 마지막 안전장치입니다. 즉, 프로세스 입장에서는 억울하게 죽지만, 시스템 입장에서는 전체 다운을 막는 선택입니다. 이 글에서는 OOM Killer로 인한 프로세스 죽음을 정확히 진단하고, 서비스/호스트/Kubernetes 레벨에서 방지하는 실전 방법을 정리합니다.
> systemd 재시작 루프가 함께 보인다면 원인 분석에 도움이 됩니다: systemd 서비스 무한 재시작 - Exit code 203 해결
OOM Killer가 발생하는 전형적인 증상
다음 중 하나라도 해당하면 OOM을 강하게 의심할 수 있습니다.
- 프로세스가 갑자기 종료되며 터미널에
Killed만 출력 - 애플리케이션 로그에는 스택트레이스/에러 없이 종료
dmesg또는/var/log/kern.log에Out of memory:/Killed process기록- Kubernetes에서는 Pod가
OOMKilled로 재시작 (kubectl describe pod에서 확인) - 노드에서 메모리 사용량이 지속적으로 상승하다가 특정 시점에 급락(프로세스 강제 종료)
핵심은 애플리케이션이 “정상 종료”한 것이 아니라 커널이 강제 종료했다는 점입니다.
OOM Killer의 동작 원리(최소한으로 알아야 할 것)
리눅스는 메모리가 부족해지면 다음을 시도합니다.
- 페이지 캐시 회수(reclaim)
- 스왑이 있으면 스왑 아웃(성능 저하 발생)
- 그래도 부족하면 OOM Killer 발동 → 특정 프로세스를 골라
SIGKILL로 종료
OOM Killer는 “누구를 죽일지”를 점수화하여 결정합니다. 커널 로그에는 보통 다음 정보가 함께 찍힙니다.
- 어떤 프로세스를 죽였는지(PID, 이름)
- 해당 프로세스의 메모리 사용량(RSS 등)
- 당시 메모리 상태(Free, Cache, Swap, cgroup 메모리 등)
특히 컨테이너 환경(cgroup)에서는 노드 전체 OOM이 아니라 **cgroup OOM(컨테이너 한도 초과)**으로도 프로세스가 죽습니다. 이 둘은 진단/대응 방식이 다릅니다.
1단계: 커널 로그로 “OOM이 맞는지” 확정하기
가장 먼저 해야 할 일은 커널 로그에서 OOM 이벤트를 찾는 것입니다.
dmesg로 확인
sudo dmesg -T | egrep -i "out of memory|oom|killed process|oom-killer" | tail -n 50
journalctl로 확인(systemd 환경)
sudo journalctl -k --since "-2h" | egrep -i "out of memory|oom|killed process|oom-killer"
/var/log/kern.log (Ubuntu/Debian 계열에서 흔함)
sudo egrep -i "out of memory|oom|killed process" /var/log/kern.log | tail -n 50
로그에서 아래와 같은 패턴이 보이면 거의 확정입니다.
Out of memory: Kill process 12345 (java) score 987 or sacrifice childKilled process 12345 (python) total-vm:... anon-rss:... file-rss:...
여기서 중요한 포인트:
- Killed process 줄에 나온 PID/프로세스명이 “죽은 당사자”입니다.
anon-rss가 큰 경우가 많습니다(힙/익명 메모리).- 컨테이너라면
Memory cgroup out of memory같은 문구가 함께 나올 수 있습니다.
2단계: “노드 OOM” vs “cgroup OOM(컨테이너 OOM)” 구분
Kubernetes에서 빠르게 확인
kubectl describe pod <pod-name> -n <ns>
Events에 다음이 보이면 컨테이너 OOM일 가능성이 큽니다.
Reason: OOMKilledLast State: Terminated/Exit Code: 137
137은 SIGKILL(9)로 종료되었음을 의미합니다(꼭 OOM만은 아니지만 OOM에서 흔함).
Kubernetes에서 노드 자원이 빡빡해지면 스케줄링/안정성 문제도 같이 나타날 수 있습니다. 노드 수 부족/자원 부족으로 Pod가 못 뜨는 이슈는 다음 글이 맥락상 함께 도움이 됩니다: K8s Pod Pending(0/노드) - 스케줄 불가 원인·해결
호스트에서 cgroup OOM 흔적 찾기(개념)
- systemd 서비스라면 해당 유닛이 cgroup에 속함
- Docker/Kubernetes 컨테이너는 각자 메모리 제한(cgroup limit)을 가질 수 있음
cgroup OOM은 “노드 메모리는 남아 있는데도” 컨테이너가 죽을 수 있습니다. 이유는 단순합니다. 컨테이너에 할당한 상한을 넘었기 때문입니다.
3단계: 당시 메모리 상황을 수치로 복원하기
OOM은 “순간”에 발생하므로, 평소에 지표를 수집하지 않았다면 사후 분석이 어렵습니다. 그래도 기본 명령으로 현재 상태와 패턴을 확인할 수 있습니다.
메모리/스왑 현황
free -h
swapon --show
- Swap이 0인데 메모리 압박이 크면 OOM이 더 빨리 올 수 있습니다.
- Swap이 너무 크면 OOM은 늦게 오지만, 대신 심각한 성능 저하(스래싱)가 올 수 있습니다.
어떤 프로세스가 많이 쓰는지
ps -eo pid,comm,rss,%mem --sort=-rss | head -n 20
vmstat로 압박 징후 보기
vmstat 1 10
si/so(swap in/out)가 증가하면 메모리 부족 신호입니다.r이 높고wa가 높다면 I/O 대기/스왑 영향 가능성이 있습니다.
4단계: OOM Killer가 “왜 그 프로세스를 죽였는지” 이해하기
커널은 프로세스별로 OOM 점수(oom_score)를 계산합니다. 점수가 높을수록 “죽이기 좋은 후보”입니다.
현재 OOM 점수 확인
PID=<pid>
cat /proc/$PID/oom_score
cat /proc/$PID/oom_score_adj
oom_score: 커널이 계산한 현재 점수oom_score_adj: 관리자가 조정 가능한 값(-1000 ~ 1000)
예를 들어, 중요한 프로세스(예: DB)가 OOM으로 죽는 것을 줄이려면 oom_score_adj를 낮추는 방법이 있습니다. 단, 이것은 OOM을 해결하는 게 아니라 “누가 대신 죽을지”를 바꾸는 것에 가깝습니다.
방지 전략 1: 애플리케이션 레벨(가장 근본적)
OOM의 1순위 원인은 보통 다음 중 하나입니다.
- 메모리 릭(해제되지 않는 객체/버퍼)
- 캐시/버퍼를 무제한으로 쌓음
- 동시성 증가로 per-request 메모리가 선형 증가
- 큰 파일/응답을 한 번에 메모리에 올림
빠른 방지 체크리스트
- 캐시에는 상한(LRU/TTL/Max size)을 둔다
- 요청 바디/파일 처리 시 스트리밍을 사용한다
- 워커/스레드 수를 메모리 기반으로 제한한다
- 언어 런타임(Java/Node/Python 등)의 힙/메모리 옵션을 명시한다
예: Node.js 힙 상한 설정
node --max-old-space-size=2048 server.js
예: Java 힙 상한 설정
java -Xms512m -Xmx2048m -jar app.jar
애플리케이션이 메모리를 무한정 쓰지 못하게 “상한”을 잡아두면, OOM이 나더라도 예측 가능한 실패 모드로 바뀝니다(예: 500 증가, 재시작 등).
방지 전략 2: 호스트(리눅스) 레벨에서의 완화
(1) 메모리 여유분 확보와 스왑 전략
- 스왑 0은 깔끔하지만, 순간 스파이크에 취약합니다.
- 작은 스왑(예: RAM의 25~50% 또는 워크로드에 맞게)은 “완충재”가 될 수 있습니다.
스왑을 쓰되 과도한 스왑 사용을 줄이려면 vm.swappiness를 조정합니다.
# 현재 값 확인
cat /proc/sys/vm/swappiness
# 임시 적용(재부팅 시 원복)
sudo sysctl -w vm.swappiness=10
# 영구 적용
echo "vm.swappiness=10" | sudo tee /etc/sysctl.d/99-swappiness.conf
sudo sysctl --system
(2) systemd 서비스에 메모리 상한을 걸어 “폭발 반경” 줄이기
특정 서비스가 메모리를 다 먹어 시스템 전체를 불안정하게 만들면, systemd cgroup 제한으로 피해를 국소화할 수 있습니다.
/etc/systemd/system/myapp.service.d/override.conf
[Service]
MemoryMax=2G
# OOM 상황에서 해당 서비스만 먼저 종료되도록 유도(환경에 따라 선택)
OOMScoreAdjust=500
sudo systemctl daemon-reload
sudo systemctl restart myapp
MemoryMax는 강력한 안전장치입니다.- 다만 서비스가 제한을 넘으면 종료될 수 있으므로, 애플리케이션이 재시작에 견고해야 합니다.
(3) EarlyOOM / systemd-oomd 같은 사용자 공간 OOM 대응
커널 OOM까지 가기 전에, 사용자 공간 데몬이 먼저 개입해 “덜 중요한 것”을 정리하도록 할 수 있습니다.
systemd-oomd: PSI(Pressure Stall Information) 기반으로 압박을 감지해 조치earlyoom: 메모리/스왑이 임계치에 근접하면 프로세스 종료
운영 환경에서는 “커널 OOM 한 방”보다 “조기 개입”이 장애 전파를 줄이는 경우가 많습니다.
방지 전략 3: Kubernetes에서 OOMKilled 줄이기
Kubernetes에서는 requests/limits 설계가 OOM의 체감 빈도를 좌우합니다.
(1) requests/limits를 현실적으로 잡기
requests: 스케줄러가 노드에 배치할 때 기준limits: 컨테이너가 사용할 수 있는 상한(초과 시 OOMKilled 가능)
예시:
resources:
requests:
cpu: "250m"
memory: "512Mi"
limits:
cpu: "1"
memory: "1Gi"
- limit을 너무 낮게 잡으면 정상 트래픽에서도 OOMKilled가 납니다.
- request를 너무 낮게 잡으면 노드에 과밀 배치되어 노드 OOM 위험이 커집니다.
(2) Exit code 137/OOMKilled를 “관측 가능”하게 만들기
kubectl get pod에서 재시작 횟수 증가 감시- 이벤트/로그 수집(예: kubelet 이벤트)
- 노드 메모리/PSI 지표 수집(Prometheus node-exporter 등)
(3) 노드 레벨 OOM 방지: 여유 용량과 eviction 설정
노드가 꽉 차면 kubelet eviction이 동작하기도 하지만, 설정/워크로드 특성에 따라 OOM이 먼저 터질 수 있습니다.
- 노드 메모리 사용률이 장기간 80~90% 이상이면 구조적으로 위험
- Burstable/BestEffort Pod가 많으면 희생 대상이 예측 불가
클러스터에서 서비스 불안정(예: 503)이 함께 나타난다면, OOM으로 인한 백엔드 프로세스 재시작이 원인일 수 있습니다. 장애 징후를 빠르게 좁히는 관점에서는 다음 글도 참고가 됩니다: EKS에서 503 Service Unavailable 원인 10분 진단
실전 트러블슈팅 절차(요약)
- 커널 로그에서 OOM 확정:
journalctl -k/dmesg -T - 누가 죽었는지 식별:
Killed process ... (name) - 노드 OOM vs cgroup OOM 구분: Kubernetes 이벤트/ExitCode 137, cgroup 메시지
- 메모리 사용 패턴 확인:
free -h,vmstat,ps --sort=-rss - 방지책 적용
- 앱 상한(힙/캐시/동시성)
- systemd
MemoryMax,OOMScoreAdjust - Kubernetes requests/limits 재설계
- 조기 OOM 대응(systemd-oomd/earlyoom), 스왑/스와피니스 조정
마무리
OOM Killer는 “원인”이라기보다 메모리 부족이라는 더 큰 문제의 결과입니다. 따라서 진단의 핵심은 커널 로그로 OOM을 확정하고, 노드/컨테이너 경계를 구분한 뒤, 어떤 워크로드가 어떤 패턴으로 메모리를 소모했는지 좁혀가는 것입니다.
운영에서 가장 효과적인 방지는 보통 두 가지의 조합입니다.
- 애플리케이션이 메모리를 무한정 먹지 못하도록 상한을 설계하고
- 호스트/클러스터에서 한 서비스의 폭주가 전체 장애로 번지지 않도록 **격리(리소스 제한)와 관측(지표/이벤트)**을 갖추는 것
이 두 축을 갖추면 OOM은 “미스터리한 프로세스 증발”이 아니라, 예측 가능하고 통제 가능한 운영 이슈로 바뀝니다.