cgroup v2 OOM Killer 연쇄 종료 오류 해결: 메모리 리밋과 zram 최적화
서버 메모리가 약간 모자란 상황에서 특정 프로세스 하나가 OOM(Out-Of-Memory)으로 죽더니, 곧이어 멀쩡하던 옆 컨테이너와 필수 데몬들까지 도미노처럼 연쇄 종료되는 기이한 현상 때문에 밤새 식은땀을 흘린 적이 있습니다. 구글링을 해봐도 단순히 물리 램 용량을 늘리라거나 Docker/Kubernetes 리밋을 높이라는 뻔한 대답만 나올 뿐, 왜 cgroup v2 환경에서 커널이 개별 프로세스가 아닌 서브트리 전체에 OOM Killer를 연쇄 유발하는지 실마리를 찾기 어려웠습니다. 이 글에서는 cgroup v2 메모리 제어 메커니즘의 맹점을 분석하고, memory.high/max 재설정과 zram 동적 압축 스왑을 결합해 서버의 OOM 연쇄 붕괴를 완벽히 막는 실전 가이드를 공유합니다.
cgroup v1과 v2 메모리 임계값 제어 방식의 핵심 변경점
처음엔 단순히 애플리케이션의 메모리 누수 문제인 줄 알고 코드만 며칠 동안 쥐잡듯 뒤졌습니다. 알고 보니 cgroup v2 통합 구조(2016년, Linux Kernel Organization 커널 4.5 공식 발표)와 Kubernetes v1.25 cgroup v2 기본 채택(2022년, CNCF 공식 발표) 이후 메모리를 다루는 커널의 하드웨어 할당 제어 방식 자체가 v1 때와 근본적으로 달라진 것이 원인이었습니다.
- cgroup v1: memory.limit_in_bytes 하나로 소프트/하드 제한을 뭉뚱그려 관리하여 한계 도달 시 즉시 단순 OOM Killer가 작동했습니다.
- cgroup v2: memory.min(절대 보호), memory.low(소프트 보호), memory.high(할당 지연 스로틀링), memory.max(하드 한계)로 4단계 세분화되었습니다.
- 페이지 캐시 회수 우선순위: v2에서는 익명 메모리(Anonymous Memory)와 파일 캐시(Page Cache)를 더 균형 있게 회수하도록 계층 구조가 개선되었습니다.
프로세스 단일 살처분과 연쇄 붕괴를 가르는 cgroup v2 설정의 맹점
막상 dmesg 커널 로그를 뒤져보니 범인은 메모리를 과다 사용한 단일 파드나 서비스가 아니라 cgroup 속성 설정의 꼬임이었습니다. 찾아보니 은근히 헷갈리는 지점이 바로 memory.oom.group 파라미터와 memory.high의 상호작용이었습니다.
- memory.oom.group=1 설정 위험: 메모리 초과 시 해당 cgroup 서브트리에 속한 모든 프로세스에 SIGKILL을 보내므로 메인 프로세스뿐만 아니라 사이드카 및 인접 워크로드가 동시에 연쇄 사멸합니다.
- memory.high 미설정 시 Direct Reclaim 폭주: memory.max에 다다를 때까지 스로틀링 완충지대가 없으면 커널이 메모리를 급하게 회수하느라 CPU I/O Wait가 100%까지 치솟으며 노드 전체가 멈춥니다.
memory.high와 memory.max 모범 비율 및 워크로드별 계산 예시
이것 때문에 설정값을 몇 번이나 바꾸며 서버를 재부팅했는지 모릅니다. 결론부터 말씀드리면 memory.high는 OOM Killer가 불시에 개입하지 않도록 커널에 사전 경고를 주는 완충 장치 역할을 수행하므로 전체 제한의 70~80% 수준으로 배치하는 것이 최적입니다.
| 서버 용량 | high 리밋 | max 리밋 |
|---|---|---|
| 8GB 웹서버 | 5.6GB (70%) | 6.8GB (85%) |
| 16GB DB서버 | 11.2GB (70%) | 13.6GB (85%) |
| 32GB AI노드 | 22.4GB (70%) | 27.2GB (85%) |
zram 압축 스왑 도입으로 커널 OOM Killer 연쇄 발동을 방지하는 5단계 설정
물리 디스크 스왑을 켜면 I/O 병목 때문에 서버가 먹통이 되고, 스왑을 아예 끄면 순간적인 핑퐁 스파이크 트래픽에 OOM Killer가 바로 작동합니다. 이 문제를 해결하기 위해 zram 인메모리 압축 블록 드라이버(2014년, Linux Kernel Organization 커널 3.14 공식 발표)를 활용해 메모리 공간을 즉시 압축 확보하는 순서를 정리했습니다.
- zram 커널 모듈 로드: sudo modprobe zram num_devices=1 명령으로 인메모리 압축 모듈을 활성화합니다.
- zstd 압축 알고리즘 지정: echo zstd > /sys/block/zram0/comp_algorithm 명령으로 높은 압축률과 속도를 확보합니다.
- 압축 스왑 용량 지정: echo 4G > /sys/block/zram0/disksize 명령으로 물리 램 일부를 동적 압축 영역으로 할당합니다.
- 스왑 장치 생성 및 활성화: mkswap /dev/zram0 && swapon -p 10 /dev/zram0 명령을 수행하여 최우선순위 스왑으로 등록합니다.
- swappiness 제어 수치 상향: sysctl vm.swappiness=150 설정을 통해 커널이 OOM을 부르기 전 비활성 메모리를 zram으로 적극 압축 이전하게 만듭니다.
cgroup v2 환경과 기존 Swap 및 zswap 대응 비교
운영 중인 시스템 환경에 따라 일반 NVMe 디스크 스왑, zswap, zram 중 무엇을 채택해야 할지 판단 기준이 서지 않을 때가 많습니다. 아래 성능 및 OOM 방지 특성 비교표를 참고하여 구축 방향을 결정할 수 있습니다.
| 방식 | 처리 속도 | OOM 지연 |
|---|---|---|
| 디스크Swap | 느림 (I/O병목) | 낮음 (스래싱) |
| zswap | 보통 (디스크) | 중간 (I/O대기) |
| zram | 빠름 (RAM압축) | 높음 (메모리확장) |
자주 막히는 지점
- cgroup v2 환경에서 memory.oom.group=1 옵션 설정 시 단일 자식 프로세스의 메모리 초과임에도 같은 cgroup slice 내 모든 연관 프로세스가 동시에 SIGKILL 강제 종료되는 이슈
- memory.high 완충선 없이 memory.max만 단독으로 지정했을 때, 커널이 동기식 메모리 회수(Direct Reclaim)를 지속 유발하여 CPU Wait가 100%에 달하고 노드 응답이 불가능해지는 현상
체크리스트
- cat /sys/fs/cgroup/cgroup.controllers 명령으로 memory 컨트롤러 활성화 확인
- memory.high 설정값이 memory.max 수치의 70~80% 수준으로 배치되었는지 점검
- /proc/pressure/memory 파일의 PSI avg10 수치가 20%를 초과하는지 모니터링
- zramctl 명령어로 zram0 압축 장치 활성화 상태 및 zstd 알고리즘 적용 여부 확인
- sysctl vm.swappiness 수치가 zram 환경에 맞춰 100~150 사이로 지정되었는지 점검
- /sys/fs/cgroup/<slice>/memory.oom.group 파라미터가 서비스 특성에 맞게 0 또는 1로 제어되었는지 확인
오해하기 쉬운 정보
- cgroup v2에서 memory.max만 설정해두면 커널이 알아서 안정적으로 메모리를 관리한다: memory.max만 설정하면 완충 지대가 없어 메모리 임계값 도달 시 갑작스러운 OOM Killer가 호출되므로 memory.high를 통한 사전 할당 지연이 필수적입니다.
- zram 압축 스왑을 활성화하면 CPU 점유율이 폭증하고 서버 디스크 I/O가 느려진다: zram은 SSD/HDD가 아닌 순수 RAM 내부에서 빠르게 압축을 진행하는 인메모리 드라이버이므로 디스크 I/O 병목이 발생하지 않습니다.
- OOM Killer가 동작하면 커널은 항상 메모리를 가장 많이 쓰는 프로세스 하나만 골라서 종료한다: cgroup v2에서 memory.oom.group=1로 지정된 경우 리소스 사용량에 상관없이 해당 cgroup 트리에 속한 전체 프로세스가 한꺼번에 살처분됩니다.
- zram을 사용할 때는 vm.swappiness 수치를 0에 가깝게 낮춰야 안전하다: 디스크 기반 스왑과 달리 zram은 RAM 내 압축 방식이므로 swappiness를 100 이상으로 올려 커널이 잉여 메모리를 적극 압축하도록 유도하는 것이 훨씬 유리합니다.
멀쩡하던 서비스 데몬들이 무작위로 연쇄 다운되던 원인을 추적하다가 cgroup v2의 memory.high 미설정과 memory.oom.group 오남용이 결합된 연쇄 OOM Killer 현상임을 알아냈을 때의 허탈감과 안도감은 아직도 생생합니다. 단순 하드웨어 사양 증설에만 의존하기보다 커널 수준의 메모리 슬라이싱과 zram 압축 스왑을 적절히 구성해주면 한정된 서버 자원에서도 극적인 시스템 안정성을 확보할 수 있습니다. 오늘 소개해 드린 파라미터 조합을 여러분의 운영 서버에 단계적으로 적용해 보시고 더 이상 야간 장애 알람에 시달리지 않으시길 바랍니다.
핵심 요약
- cgroup v2는 v1과 달리 memory.high(소프트 스로틀)와 memory.max(하드 한계)로 메커니즘이 다단계 분리되었습니다.
- memory.high 완충선이 없으면 메모리 고갈 시 커널 Direct Reclaim 폭주로 CPU I/O Wait가 100%까지 치솟습니다.
- memory.oom.group 값이 1로 세팅되면 단일 프로세스 메모리 초과 시 그룹 내 전체 프로세스가 연쇄 SIGKILL됩니다.
- 권장 설정법은 memory.high를 memory.max의 70~80% 수준으로 배치하여 커널 스로틀링 이벤트를 사전 유발하는 것입니다.
- zram 인메모리 압축 스왑을 결합하면 가용 메모리 영역을 1.5~2배 실질적으로 확장하여 OOM Killer 발동을 지연시킵니다.
- zram 적용 시 sysctl vm.swappiness 수치를 100~150으로 상향 설정해 적극적인 메모리 압축 회수를 유도해야 합니다.
- /proc/pressure/memory 경로의 PSI(Pressure Stall Information) avg10 수치를 모니터링하여 병목을 사전에 파악합니다.
#LinuxKernel #CgroupsV2 #OOMKiller #zRAM #SysAdmin #Kubernetes