- Published on
리눅스 OOM Killer로 프로세스 죽을 때 진단법
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버에서 프로세스가 예고 없이 죽고 애플리케이션 로그엔 별다른 스택트레이스가 없다면, 가장 먼저 의심해야 할 것이 리눅스 OOM(Out-Of-Memory) Killer입니다. OOM은 단순히 “메모리가 부족했다”가 아니라, 커널이 생존을 위해 특정 프로세스를 강제로 종료했다는 의미이며, 원인은 크게 세 갈래로 나뉩니다.
- 호스트 전체 메모리 고갈(전역 OOM)
- cgroup(컨테이너/슬라이스) 메모리 한도 초과(cgroup OOM)
- ulimit / 정책 제한으로 인한 간접 OOM(메모리 락, 주소 공간 제한 등)
이 글은 “왜 죽었는지”를 빠르게 확정하는 데 필요한 dmesg 로그 해석 → cgroup 한도 확인 → ulimit/서비스 설정 확인 순서로 진단 체크리스트를 제공합니다. 쿠버네티스 환경이라면 함께 보면 좋은 글로 Kubernetes OOMKilled 진단과 메모리 누수 추적 실전도 연결해 두겠습니다.
1) OOM 유형부터 구분하기: 전역 OOM vs cgroup OOM
OOM은 크게 두 가지로 나뉘며, 로그 패턴이 다릅니다.
- 전역 OOM(호스트 OOM): 시스템 전체 메모리가 고갈되어 커널이 프로세스를 선택해 죽임
- cgroup OOM: 특정 cgroup(컨테이너/서비스 단위)의
memory.max(v2) 또는memory.limit_in_bytes(v1) 한도를 넘어서 해당 그룹 내 프로세스를 죽임
실무에서 가장 흔한 함정은 “호스트 메모리는 남아 있는데 컨테이너가 죽는” 케이스입니다. 이때는 호스트 free -m만 보고 원인을 놓치기 쉽습니다.
2) dmesg로 OOM Killer 확정하기 (가장 먼저)
2.1 커널 로그에서 OOM 흔적 찾기
OOM이 발생하면 거의 항상 커널 링버퍼에 흔적이 남습니다.
# 최근 커널 로그에서 OOM 관련 라인 필터링
sudo dmesg -T | egrep -i "out of memory|oom-killer|killed process|oom_reaper|Memory cgroup" | tail -n 200
# systemd journal 기반이면
sudo journalctl -k --since "-2h" | egrep -i "out of memory|oom-killer|killed process|Memory cgroup"
대표적인 키워드:
Out of memory: Killed process ...oom-killer: ...Memory cgroup out of memory: Kill process ...Killed process 1234 (myapp) total-vm:... anon-rss:...
2.2 OOM 로그 해석 포인트(필수)
OOM 로그에서 특히 중요한 필드들:
- Killed process (PID) (COMM): 죽은 프로세스
anon-rss: 익명 메모리(대개 힙/스택) 사용량file-rss: 파일 매핑 RSS(페이지 캐시/맵드 파일)shmem-rss: 공유메모리total-vm: 가상 메모리 크기(실제 사용량과 다름)oom_score_adj: OOM 우선순위 조정값(높을수록 더 잘 죽음)
예시(전형적인 형태):
Out of memory: Killed process 24817 (java) total-vm:8123456kB, anon-rss:2048000kB, file-rss:12000kB, shmem-rss:0kB, UID:1000 pgtables:6000kB oom_score_adj:0
이 라인이 보이면 “앱이 크래시했다”가 아니라 커널이 죽였다가 확정입니다.
2.3 전역 OOM인지 cgroup OOM인지 구분하는 문구
전역 OOM 쪽에 가까운 패턴:
Out of memory: Killed process ...oom-killer: gfp_mask=... order=...Mem-Info:덤프가 길게 출력
cgroup OOM 쪽에 가까운 패턴:
Memory cgroup out of memory: Kill process ...Tasks in /kubepods/...또는 특정 slice 경로가 함께 출력
이 구분이 되면 다음 단계가 명확해집니다.
3) cgroup 메모리 제한 진단 (컨테이너/서비스가 죽는 1순위)
컨테이너, systemd 서비스, Kubernetes Pod는 대부분 cgroup에 의해 메모리 한도가 걸립니다. 호스트 메모리가 남아도 cgroup 한도를 넘으면 OOM이 납니다.
3.1 내 시스템이 cgroup v1인지 v2인지 확인
stat -fc %T /sys/fs/cgroup
# cgroup2fs면 v2
mount | grep cgroup
cgroup2fs→ v2cgroup마운트가 여러 개 → v1
3.2 (cgroup v2) memory.max / memory.current 확인
프로세스 PID를 알고 있다면 해당 프로세스의 cgroup 경로를 찾습니다.
PID=24817
cat /proc/$PID/cgroup
v2에서는 보통 0::/some/path 형태로 나오며, 그 경로로 들어가 메모리를 봅니다.
CG=/sys/fs/cgroup/some/path
cat $CG/memory.current
cat $CG/memory.max
cat $CG/memory.events
memory.max가max가 아니라 숫자라면 한도가 있는 것입니다.memory.events의oom/oom_kill카운트가 증가해 있으면 cgroup OOM이 실제로 발생한 것입니다.
oom 3
oom_kill 3
이 값은 “앱이 자체적으로 죽었다” 논쟁을 끝내는 강력한 증거입니다.
3.3 (cgroup v1) memory.limit_in_bytes / memory.oom_control
# v1에서는 memory 서브시스템 경로가 따로 있음
# 예: /sys/fs/cgroup/memory/kubepods/... 또는 /sys/fs/cgroup/memory/system.slice/...
cat /sys/fs/cgroup/memory/<path>/memory.usage_in_bytes
cat /sys/fs/cgroup/memory/<path>/memory.limit_in_bytes
cat /sys/fs/cgroup/memory/<path>/memory.failcnt
memory.failcnt가 증가하면 한도 초과 시도가 있었다는 뜻입니다.
3.4 systemd 서비스라면 MemoryMax/MemoryHigh 확인
systemd는 서비스 단위로 cgroup을 만들고 메모리 제한을 걸 수 있습니다.
systemctl show myservice --property=MemoryMax,MemoryHigh,MemoryCurrent,OOMPolicy
systemctl status myservice
MemoryMax가 설정돼 있다면 그 값이 사실상의 hard limit입니다.OOMPolicy=kill같은 정책이 있으면 OOM 시 서비스가 더 공격적으로 종료될 수 있습니다.
4) ulimit 진단: “메모리 부족”처럼 보이는 제한들
OOM Killer는 커널 메모리 부족에서 발생하지만, 실무에서 “OOM 같다”라고 오해되는 케이스가 있습니다. 그 중 상당수는 ulimit(리소스 제한) 문제입니다.
4.1 프로세스의 ulimit 확인(실행 환경 기준)
쉘에서 보는 ulimit -a는 “현재 세션” 기준입니다. 실제 서비스/컨테이너 프로세스의 제한은 다음이 더 정확합니다.
PID=24817
cat /proc/$PID/limits
여기서 특히 확인할 항목:
Max address space(ulimit-v) : 너무 낮으면 대형 메모리 할당이 실패Max locked memory(ulimit-l) :mlock/DB/캐시가 실패하며 예기치 않은 동작Max open files(ulimit-n) : FD 부족으로 메모리처럼 보이는 장애 유발
4.2 systemd의 Limit* 설정 확인
서비스가 systemd로 뜬다면 ulimit은 unit 파일에서 결정되는 경우가 많습니다.
systemctl show myservice | egrep 'LimitNOFILE|LimitNPROC|LimitAS|LimitMEMLOCK'
예: LimitAS가 너무 낮으면 프로세스가 메모리를 더 쓰지 못해 실패하고, 애플리케이션이 자체 종료할 수 있습니다(이건 커널 OOM과는 다르게 dmesg에 OOM 로그가 없을 수 있음).
5) “누가 메모리를 먹었나”를 좁히는 실전 커맨드
OOM이 확정됐으면 다음은 범인 찾기입니다. 전역 OOM과 cgroup OOM 모두에서 유용한 명령을 정리합니다.
5.1 순간 스냅샷: RSS 기준 상위 프로세스
ps aux --sort=-rss | head -n 20
5.2 smem이 있으면 PSS로 더 정확히
RSS는 공유 메모리 중복 계산이 있어 과대평가될 수 있습니다.
sudo apt-get install -y smem
smem -tk | head -n 20
5.3 OOM 직전 메모리 압박(PSI) 확인 (커널 지원 시)
PSI(Pressure Stall Information)는 “메모리 때문에 얼마나 자주 멈췄나”를 보여줍니다.
cat /proc/pressure/memory
some/full이 높으면 OOM 이전에 이미 심각한 메모리 압박이 있었다는 뜻입니다.
5.4 컨테이너/쿠버네티스라면 이벤트와 리미트 재확인
쿠버네티스에서는 노드 OOM과 Pod OOMKilled가 혼재합니다. Pod가 죽었다면 이벤트를 먼저 확인하세요.
kubectl describe pod <pod>
# Events에 OOMKilled, Evicted, ContainerStatus 등이 표시
kubectl top pod -n <ns>
kubectl top node
쿠버네티스 OOMKilled 자체의 추적은 Kubernetes OOMKilled 진단과 메모리 누수 추적 실전에 더 깊게 다뤄두었습니다.
6) 재발 방지: 설정/아키텍처 관점 체크리스트
6.1 cgroup 한도와 애플리케이션 힙 설정을 “일치”시키기
특히 JVM/Node/Python(대형 캐시)에서 흔합니다.
- 컨테이너 메모리 limit 512Mi인데 JVM
-Xmx가 512Mi면 거의 확실히 OOM(메타스페이스, 스레드 스택, 네이티브 메모리, 페이지 캐시 고려 필요) - Node.js는 기본 힙 상한이 환경/버전에 따라 달라 컨테이너에서 예상보다 크게 쓰기도 합니다.
6.2 OOMScoreAdjust로 “죽을 프로세스” 우선순위 조정
중요한 프로세스가 먼저 죽는다면 oom_score_adj를 조정할 수 있습니다.
# 실행 중 프로세스에 임시 적용(예: 더 안 죽게)
PID=24817
echo -500 | sudo tee /proc/$PID/oom_score_adj
# systemd 서비스는 OOMScoreAdjust= 로 영구 설정 가능
주의: 이것은 “원인 해결”이 아니라 피해 최소화입니다. 메모리 총량이 부족하면 결국 다른 무언가가 죽습니다.
6.3 swap/overcommit 정책 점검(전역 OOM 방지)
swap이 완전히 꺼져 있거나(swapoff), vm.overcommit_memory 정책이 워크로드와 안 맞으면 OOM이 빨라질 수 있습니다.
swapon --show
sysctl vm.swappiness
sysctl vm.overcommit_memory
sysctl vm.overcommit_ratio
- DB/캐시 서버는 swap을 매우 보수적으로 쓰는 경우가 많지만, 완전 비활성화가 항상 정답은 아닙니다(환경에 따라 OOM이 더 자주 날 수 있음).
6.4 “OOM이 원인인가 결과인가”도 확인
메모리 누수가 원인이지만, 때로는 트래픽 폭증/재시도 폭탄/큐 적체가 원인입니다. 쿠버네티스 환경에서 장애가 반복된다면 OOM 자체 외에 CrashLoop 패턴도 함께 봐야 합니다. 관련해서는 K8s Pod CrashLoopBackOff 원인 7가지와 해결도 같이 참고하면 원인 분리가 빨라집니다.
7) 한 번에 따라하는 “5분 트리아지” 절차
마지막으로 현장에서 바로 쓰는 순서를 정리합니다.
- dmesg/journalctl로 OOM 확정
Killed process/Memory cgroup out of memory문구 찾기
- 전역 OOM vs cgroup OOM 구분
- cgroup 문구/경로가 보이면 cgroup 한도부터
- cgroup 메모리 한도와 이벤트 확인
- v2:
memory.max,memory.current,memory.events - v1:
memory.limit_in_bytes,memory.failcnt
- v2:
- 프로세스 limits(ulimit) 확인
/proc/<pid>/limits, systemdLimit*
- 범인 프로세스/패턴 확인
ps --sort=-rss, (가능하면)smem, PSI
- 재발 방지
- limit/heap 정렬, OOMScoreAdjust, swap/overcommit 정책, 트래픽/재시도 제어
OOM은 “메모리가 부족했다”가 아니라 커널/런타임/컨테이너 정책이 교차하는 사건입니다. dmesg로 사실관계를 확정하고(cgroup OOM인지부터), cgroup 한도와 ulimit을 함께 점검하면 대부분의 케이스는 10~20분 내로 원인 범위를 좁힐 수 있습니다.