Cgroups 메모리 서브시스템의 자원 제한 (Memory Limit) 동작

핵심 인사이트 (3줄 요약)

  1. 본질: Cgroups(Control Groups) 메모리 서브시스템은 램이 부족할 때 무자비하게 남의 램을 뺏어 쓰는 리눅스의 '전역 교체(Global Replacement)' 야생성을 통제하기 위해, 특정 프로세스 무리(컨테이너)에게 보이지 않는 강철 철창(Memory Limit)을 씌워 메모리 상한선을 강제하는 커널 레벨의 샌드박싱 기술이다.
  2. 가치: 한 컨테이너에 메모리 릭(Leak) 버그가 터져 램을 미친 듯이 빨아들여도 그 철창 안에서 자기들끼리만 스래싱(Local Replacement)을 겪다가 혼자서 OOM(Out of Memory)으로 죽게 만들어, 같은 서버(Node)에 떠 있는 다른 수십 개의 컨테이너(Noisy Neighbor)가 절대 피해를 보지 않도록 완벽한 격리(Isolation) 생태계를 보장한다.
  3. 융합: 이 커널의 족쇄 기술은 도커(Docker)와 쿠버네티스(Kubernetes)의 Resource: Limit 설정(YAML)과 100% 1:1로 융합되어, 현대 클라우드 데이터센터가 수백만 개의 앱을 한 서버에 욱여넣고 안전하게 과금할 수 있는 거대한 자본주의 인프라의 척추가 되었다.

Ⅰ. 개요 및 필요성 (Context & Necessity)

  • 개념: cgroups(컨트롤 그룹)는 구글(Google) 엔지니어들이 리눅스 커널에 추가한 자원 통제 모듈이다. CPU, 네트워크 등 여러 서브시스템이 있지만 그중 메모리(Memory) 서브시스템은 특정 프로세스 트리(PIDs)가 사용할 수 있는 최대 물리 램(RAM)과 스왑(Swap)의 상한선(Limit) 바이트 수를 바위처럼 굳게 설정한다. 이 선을 넘으면 얄짤없이 램 징수나 사형(OOM Kill)이 집행된다.

  • 필요성: 2010년대 클라우드 시대가 열리며 서버 1대에 100개 회사의 앱(가상 머신/컨테이너)을 구겨 넣는 멀티 테넌트(Multi-tenant) 환경이 대세가 되었다. 리눅스는 태생이 '전역 교체'다. A 회사 앱이 램을 10GB 달라고 떼쓰면, OS는 B, C, D 회사의 멀쩡한 램 프레임을 싹 다 뺏어다가 A에게 상납해 버린다. 결과적으로 A의 실수 때문에 아무 죄 없는 다른 회사 서버들이 전부 뻗어버리는 최악의 시끄러운 이웃(Noisy Neighbor) 재앙이 터졌다. 클라우드 사업자들은 피눈물을 흘리며 "프로세스마다 보이지 않는 벽을 쳐서, 남의 램은 절대 뺏어올 수 없게 막는 깐깐한 배급 통제소"를 커널 심장부에 박아넣어야만 했다.

  • 💡 비유: cgroups 메모리 제한은 호텔 조식 뷔페의 칸막이 식판 배식과 같다. 리눅스의 기본 상태(전역 교체)는 거대한 공용 냄비 하나를 놔두고 알아서 퍼먹는 것이다. 식탐 많은 뚱보 손님(메모리 릭) 한 명이 냄비째 마셔버리면 99명의 다른 손님은 굶어 죽는다. cgroups를 켜면 지배인이 뚱보 손님에게 **"너는 이 네모난 작은 식판(Limit) 크기 이상으로는 절대 음식을 못 담는다!"**라고 철창을 친다. 뚱보가 식판을 꽉 채워서 음식이 부족하면 지 식판 안의 음식을 토해내고 다시 담든 말든 혼자 헐떡이게 내버려 둔다. 나머지 99명의 손님은 자기들 식판에 밥을 가득 담아 평화롭고 배부르게 식사를 마칠 수 있다.

  • 등장 배경 및 컨테이너 혁명의 조건:

    1. 가상 머신(VM)의 무거움: VM(KVM/VMware)은 하드웨어 단에서 램을 찢어줘서 안전했지만, OS를 띄우느라 너무 느리고 무거웠다.
    2. 가볍고 안전한 격리(Isolation)의 열망: 리눅스 프로세스(chroot, namespace) 수준에서 가볍게 띄우되, 자원 뺏기를 막을 강력한 장치가 필요했다.
    3. cgroups의 등장: 프로세스 그룹에 하드 리밋(Hard Limit)을 거는 cgroups v1(그리고 더 정교한 v2)이 커널에 병합되며 마침내 가벼운 도커(Docker)의 시대가 폭발적으로 열렸다.
┌────────────────────────────────────────────────────────────────────────────┐
│        cgroups 유무에 따른 메모리 릭(Leak) 터짐 시의 파급력 시각화         │
├────────────────────────────────────────────────────────────────────────────┤
│                                                                            │
│ [ 서버 물리 RAM: 16GB / 컨테이너 A, B 구동 중 ]                            │
│                                                                            │
│ ▶ 1. 과거 야생 상태 (cgroups OFF - 리눅스 전역 교체)                       │
│  - 컨테이너 A에 메모리 릭 발생! 램을 무한대로 빨아들임 (10GB -> 15GB)      │
│  - OS는 A를 돕기 위해 멀쩡한 B의 램(5GB)을 강제로 뺏어 스왑으로 날림.      │
│  💥 결과: A 때문에 아무 죄 없는 B 서버가 렉 걸려 마비됨. 서버 전체 뇌사!   │
│                                                                            │
│ ▶ 2. 현대 클라우드 상태 (cgroups ON - 철저한 격리)                         │
│  - 설정: `docker run -m 4g 컨테이너A` (A는 최대 4GB로 묶임)                │
│  - 컨테이너 A에 메모리 릭 발생! 램이 4GB에 도달함.                         │
│  - OS: "A 놈아, 서버에 램 12GB가 남아돌지만, 넌 Limit 4GB 쳤네?            │
│        절대 B의 램을 뺏지 말고 네 철창 안에서 혼자 알아서 쫓아내고 놀아라!"│
│  - A가 4GB 안에서 아등바등 대다 결국 폭발함 (OOM Killed).                  │
│  ✅ 결과: 미친놈 A 하나만 깔끔하게 죽고, B는 평화롭게 로켓 속도로 생존!    │
└────────────────────────────────────────────────────────────────────────────┘

[다이어그램 해설] cgroups는 리눅스 메모리 관리의 가장 큰 아이러니를 보여준다. 전체 램(RAM)이 12GB나 텅텅 비어있는데도, 프로세스를 "메모리가 부족하다(OOM)"며 무참히 쏴 죽이는 인위적이고 융통성 없는 에러를 발생시킨다. 자원의 활용도(Efficiency)를 기꺼이 포기하고, 그 대가로 타인에 대한 방어막(Isolation)과 시스템 전체의 생존(Stability)을 쟁취한 클라우드 아키텍처의 비정한 선택이다.

  • 📢 섹션 요약 비유: 은행(서버) 금고에 현금이 100억(16GB 램)이 쌓여있어도, 마이너스 통장 한도가 1천만 원(cgroups 4GB)인 고객이 와서 "내 통장에서 1천 1백만 원 꺼내 줘!" 하면 은행원은 가차 없이 "잔액 부족(OOM)입니다" 하고 쫓아냅니다. 은행 전체 돈이 내 돈이 아니듯, 철저한 한도 관리가 금융 사고를 막는 1원칙입니다.

Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)

Local Replacement (지역 교체)의 부활

앞 장에서 리눅스는 "무조건 남의 램을 뺏는 전역 교체(Global Replacement)"를 쓴다고 했다. 그런데 cgroups를 씌우는 순간 지역 교체(Local Replacement)가 마법처럼 부활한다.

  • 프로세스가 cgroup에 설정된 memory.limit_in_bytes (예: 2GB)에 도달했다 치자.
  • 프로세스가 새 페이지(4KB)를 달라고 폴트를 냈다.
  • 커널은 시스템의 전체 Free List에서 램을 가져오지 않는다. 오직 **이 프로세스가 속한 cgroup 안에 있는 페이지들만을 뱅글뱅글 도는 전용 LRU 리스트(cgroup 단위의 Clock 알고리즘)**를 뒤진다.
  • 그리고 같은 cgroup 철창 안에 있는 불쌍한 자기 자신의 과거 페이지 하나를 골라 스왑으로 쫓아낸다 (돌려막기).
  • 즉, cgroups는 광활한 바다(전체 램)에 떠 있던 리눅스의 전역망을, 작은 어항(컨테이너) 단위의 수백 개의 독립된 지역 교체 생태계로 갈기갈기 찢어놓는 위대한 격리 아키텍처다.

OOM Killer의 하청 (cgroup OOM)

cgroups 안에서 지역 교체로 낑낑대며 돌려막기를 하다가, 스왑 공간마저 다 쓰거나(또는 스왑 옵션을 꺼놔서) 도저히 빈 페이지를 못 내는 순간이 온다.

  • 이때 커널은 전체 시스템(Node) 레벨의 거대한 OOM Killer를 깨우지 않는다.

  • 대신 해당 cgroup에 딸린 **소규모 전용 킬러 (cgroup OOM Killer)**를 조용히 파견한다.

  • 이 킬러는 다른 컨테이너는 쳐다보지도 않고, 오직 Limit을 초과한 그 불량 cgroup 철창 안에 있는 프로세스(PID)들만 평가(Score)해서 제일 뚱뚱한 놈의 머리통을 날린다(SIGKILL).

  • 이 덕분에 AWS나 GCP에서 내 컨테이너가 뻗어 죽었어도, 내가 돌리던 옆의 컨테이너들이나 호스트 OS의 관리 데몬은 생채기 하나 없이 살아서 무사히 "님 서버 죽었어요"라고 알람(Health Check)을 보낼 수 있는 것이다.

  • 📢 섹션 요약 비유: 옛날엔 서울(서버)에서 범죄(메모리 릭)가 터지면 전국 경찰(Global OOM)이 출동해 나라 전체를 통제하며 범인을 잡았습니다. 지금은 강남구, 서초구마다 관할 경찰서(cgroup OOM)를 둬서, 강남에서 터진 사고는 강남 경찰이 알아서 그 동네 안에서만 조용히 범인을 사살하고 끝내기 때문에 다른 동네 주민들은 사건이 났는지도 모르게 평화로운 일상을 보냅니다.


Ⅲ. 융합 비교 및 다각도 분석

비교 1: Memory Limit vs Memory Request (쿠버네티스의 2중 장부)

cgroups를 조종하는 쿠버네티스(k8s)는 개발자에게 2가지 값을 쓰라고 강요한다. 이 두 값의 하드웨어적 본질은 완전히 다르다.

설정 값리눅스 커널의 물리적 맵핑 (cgroups)역할 및 튜닝의 의미
Request (요구량)memory.soft_limit_in_bytes스케줄러가 서버에 빈자리(램)가 있는지 검사할 때 쓰는 최소 보장 보증금. 이 값은 cgroup이 강제로 앱을 죽이는 락(Lock)으로 작용하지 않음.
Limit (제한량)memory.limit_in_bytes이 선을 넘으면 하늘이 두 쪽 나도 OOM 총알이 발사되는 강철 지붕(Hard Limit). 무조건 이 선 아래서 교체와 쫓겨남이 일어남.
  • 만약 Request = 1GB, Limit = 4GB 로 설정했다면?
    • 컨테이너는 평소에 1GB는 보장받고 맘 편히 쓴다. (워킹 셋 보장).
    • 1GB를 넘어 3GB를 쓰고 있는데 서버 전체 램이 모자라기 시작하면, 커널은 이 컨테이너의 1GB 초과분(2GB 잉여분)을 0순위로 스왑으로 쫓아낸다(Reclaim). 죽이지는 않는다.
    • 하지만 앱이 미쳐서 4GB 선을 찌르는 순간, 램이 아무리 남아돌아도 자비 없이 OOM으로 즉사시킨다.

Page Cache (버퍼 캐시) 도둑의 비극

개발자들이 도커를 띄우고 가장 억울해하며 많이 당하는 버그다.

  • 내 자바 앱 힙 메모리(Heap)는 2GB로 넉넉하게 짰고, cgroups Limit은 3GB로 설정했다. "절대 안 죽겠지?"
  • 자바 앱이 10GB짜리 로그 파일을 열어 계속 썼다(write).
  • 리눅스는 이 파일 기록을 **Page Cache(파일 지원 메모리)**에 쌓아둔다.
  • 🚨 핵심: cgroups의 memory.limit_in_bytes 카운터는 순수 힙(익명 메모리)뿐만 아니라, 이 앱이 낳은 거대한 Page Cache 파일 조각들의 무게까지 몽땅 합산하여 한도를 깎아 먹는다!
  • 결국 힙은 2GB밖에 안 썼는데, 로그 파일 Page Cache가 1.5GB 쌓이면서 총 3.5GB가 되어 Limit(3GB)을 돌파, 어이없게도 멀쩡한 자바 앱이 OOM Killer에게 암살당한다. 실무 백엔드에서 90% 이상 겪게 되는 "원인 모를 도커 튕김"의 진짜 범인이다.
┌──────────┬────────────┬────────────┬──────────────────────────┐
│ 램 사용 내역│ 힙(익명) 크기 │ Page Cache │ cgroups Limit 도달 │
├──────────┼────────────┼────────────┼──────────────────────────┤
│ 앱의 착각  │ 2GB (안전)  │ "OS가 관리함"│ Limit 3GB 안 넘음!  │
│ OS의 채점  │ 2GB        │ + 1.5GB 누적│ 💥 3.5GB > 3GB (OOM)  │
└──────────┴────────────┴────────────┴──────────────────────────┘

[매트릭스 해설] cgroups는 무자비한 사채업자다. 네가 빌려 간 힙 메모리 원금뿐만 아니라, 네가 몰래 파일 시스템에 싸질러놓은 더티 페이지 캐시 이자까지 모두 합산해서 청구서를 들이민다. 이것을 막으려면 자바 앱 실행 시 파일 로깅을 끄거나 O_DIRECT를 써서 페이지 캐시 우회를 시도해야만 OOM 폭사를 막을 수 있다.

  • 📢 섹션 요약 비유: 비행기 수화물 제한(Limit)이 20kg입니다. 나는 캐리어(힙 메모리)를 딱 15kg 맞춰 싸서 안심하고 공항에 갔습니다. 그런데 승무원(cgroups)이 내가 등 뒤에 멘 작은 백팩과 손에 든 면세점 쇼핑백(Page Cache)의 무게까지 모조리 저울에 올려놓더니 총 22kg이라며 비행기 탑승을 거부(OOM)해 버리는, 자비 없는 전체 무게 합산 룰입니다.

Ⅳ. 실무 적용 및 기술사적 판단 (Strategy & Decision)

실무 시나리오: cgroups v2와 PSI (Pressure Stall Information)의 구원

  1. 과거 cgroups v1의 무식함: v1 시절에는 Limit에 다다르면 무작정 앱을 죽이거나 디스크 스왑을 치면서 렉을 만들어냈다. 시스템 엔지니어는 앱이 죽고 나서야 "아 Limit을 너무 낮게 잡았구나" 후회하며 수동으로 YAML 파일의 램 용량을 올려주는 뒷북을 쳤다.
  2. 페이스북의 구원 (PSI 도입):
    • 페이스북 커널 엔지니어들은 이 장님 같은 OOM 관리에 빡쳐서 **PSI (메모리 압박 지연 정보)**라는 신기능을 cgroups v2에 박아넣어 리눅스 본가에 기증했다.
    • 앱이 Limit 철창에 부딪히기 직전, 메모리를 달라고 헐떡이는 '기다림의 시간(Delay time)'을 0.001초 단위로 측정해서 memory.pressure 파일에 "이 컨테이너 지금 램 모자라서 1초 중 0.2초를 멍때리고(Stall) 있습니다!"라고 실시간 스트리밍 경고를 뿜어낸다.
  3. 오토 스케일링의 예술:
    • 이제 쿠버네티스와 모니터링 툴(Datadog 등)은 앱이 OOM으로 터져 죽기 한참 전에 이 PSI 압박 지수를 듣는다.
    • "오! 1번 컨테이너 램 압박 지수가 20%를 넘었어! 죽기 전에 빨리 옆에 똑같은 컨테이너를 3개 더 띄워서 트래픽을 분산시켜라!(Horizontal Pod Autoscaling)"
    • cgroups v2의 발전으로 클라우드는 앱이 죽는 사후 대처에서 죽기 전에 미리 살려내는 **예측적 자동 확장(Predictive Scaling)**의 시대로 진입했다.

OOM 킬러 비활성화 옵션 (OOMKillDisable)의 함정

도커 설정에 --oom-kill-disable 이라는 무적 같은 옵션이 있다. "와, 이거 켜면 램 꽉 차도 내 앱이 안 죽겠지?" 하고 켜는 순간 당신의 서버는 영원한 냉동 인간이 된다. Limit에 도달했는데 OOM 킬러마저 안 쏘면, cgroups는 그 앱의 스레드를 영원히 대기 상태(Uninterruptible Sleep)로 묶어버린다. 차라리 깔끔하게 죽고 컨테이너가 1초 만에 재시작(Restart)되는 게 100배 나은데, 죽지도 살지도 못한 채 트래픽을 다 잡아먹으며 굳어버리는 최악의 좀비 컨테이너 안티패턴이다. 클라우드에서는 "빨리 죽이는(Fail-fast)" 것이 미덕이다.

  • 📢 섹션 요약 비유: 심장이 멈추면(Limit 도달) 바로 제세동기(OOM 후 재시작)를 쳐서 살려내야 하는데, 제세동기 전기 충격이 아프다며 스위치를 꺼버린(OOM Disable) 격입니다. 결국 환자는 영원한 코마 상태에 빠져 병원 침대만 축내게 됩니다. 빨리 죽이고 빨리 부활시키는 게 클라우드 불사조의 생존법입니다.

Ⅴ. 기대효과 및 결론 (Future & Standard)

정량/정성 기대효과

구분내용
멀티 테넌트(Multi-tenant) 완벽 격리하나의 물리 서버에 수백 개의 서로 다른 회사 컨테이너를 구겨 넣어도, 남의 램을 뺏지 못하게 막아 보안과 가용성을 100% 보장
마이크로서비스 비용 산정(Billing)"1GB 램 컨테이너당 월 만 원"이라는 클라우드 비즈니스 모델을 수학적/물리적으로 증명하고 강제 집행하는 하드코어 장부
스래싱(Thrashing)의 국지화(Localize)램 파편화의 연쇄 폭발을 시스템 전체가 아닌 단일 철창(cgroup) 안으로 고립시켜 서버 메인 커널의 패닉을 영구 면제

결론 및 미래 전망

Cgroups 메모리 서브시스템의 자원 제한 (Memory Limit) 동작은, "모두의 램은 내 램이다"라고 외치던 리눅스의 무정부주의적 전역 교체(Global Replacement) 낭만에 차가운 자본주의의 철창을 들이민 위대한 아키텍처적 선 긋기다. 이 철창이 없었다면 도커(Docker)도, 쿠버네티스(Kubernetes)도, 지금의 AWS 클라우드 생태계도 모래성처럼 무너져 내렸을 것이다. 우리는 cgroups를 통해 하드웨어 가상 머신(KVM)의 무거운 이중 페이징 렉을 피하면서도, 마치 독립된 컴퓨터를 통째로 산 것과 같은 완벽한 램 샌드박스를 단 1초 만에 공짜로 띄울 수 있게 되었다. 미래의 리눅스 커널(eBPF 융합 등)은 이 cgroups 제한을 단순히 용량(Bytes)을 자르는 것을 넘어, 캐시 대역폭(L3 Cache Allocation Technology)과 메모리 버스 속도까지 정밀하게 쪼개어 배급하는 극초정밀 자원 스케줄링 머신으로 진화하며 현대 백엔드의 신(God)으로 군림할 것이다.

  • 📢 섹션 요약 비유: 수백 명의 죄수(앱)가 거대한 하나의 감옥 식당(전역 램)에서 밥을 먹다 매일 밥그릇 싸움(스래싱)이 터져 피바다가 되던 폭동의 시대가 있었습니다. 교도소장(구글)이 식당을 부수고 모든 죄수를 독방(cgroups)에 가둔 뒤 밥구멍으로 정해진 정량의 식판(Limit)만 넣어주기 시작하자, 배고파 죽는 죄수(OOM)는 생길지언정 교도소 전체의 평화와 폭동 방지는 완벽하게 달성된 가장 차갑고 효율적인 통제 시스템입니다.

📌 관련 개념 맵 (Knowledge Graph)

  • 전역 교체 (Global Replacement) | cgroups가 태어나기 전, 리눅스 램을 약육강식의 전쟁터로 만들었던 주범이자 Limit이 없을 때의 기본 상태
  • 지역 교체 (Local Replacement) | cgroups Limit 철창이 닫히는 순간 앱이 살아남기 위해 자기 살을 깎아 먹으며 강제로 돌려야 하는 돌려막기
  • OOM Killer | 철창(Limit) 안에서 돌려막기도 실패하여 램이 100% 꽉 차면, 시스템 전체 킬러가 아닌 cgroup 전용 미니 킬러가 파견되어 앱을 척살함
  • Page Cache (페이지 캐시) | 내가 파일을 읽었을 뿐인데 내 cgroup 램 한도를 갉아먹어서 억울하게 나를 OOM으로 몰고 가는 가장 흔한 숨겨진 램 도둑
  • 쿠버네티스 (Kubernetes) | Limit과 Request라는 껍데기 설정만으로 이 무시무시한 cgroups 커널 흑마술을 전 세계 10만 대 서버에 1초 만에 씌우는 오케스트레이터 지휘자

👶 어린이를 위한 3줄 비유 설명

  1. cgroups 메모리 제한이 뭔가요? 뷔페식당에서 욕심쟁이 친구 한 명이 피자(메모리) 100조각을 다 쓸어 담아서 다른 친구들이 울음(서버 다운)을 터뜨리는 걸 막는 규칙이에요.
  2. 어떻게 막나요? 식당 사장님(운영체제)이 입장할 때 아이들마다 딱 '3조각만 들어가는 작은 접시(Limit)'를 나눠주고 절대 남의 접시에는 손을 못 대게 투명 벽을 쳐버려요.
  3. 만약 4조각을 담으려 하면요? 사장님이 번개같이 나타나 4번째 피자를 뺏어버리고 욕심부린 아이를 식당 밖으로 쫓아내(OOM Killer) 버려서, 나머지 친구들이 평화롭게 밥을 먹게 해준답니다!