cgroups (Control Groups) 컨테이너 리소스 제한 기술
핵심 인사이트 (3줄 요약)
- 본질: cgroups(Control Groups)는 리눅스 커널(Kernel)이 제공하는 기능으로, 하나 이상의 프로세스(Process)들을 그룹으로 묶어 이들이 사용할 수 있는 CPU, 메모리(RAM), 디스크 I/O, 네트워크 대역폭 같은 물리적 하드웨어 자원의 사용량 한도를 엄격하게 제한(Limit)하고 모니터링하는 기술이다.
- 가치: 한 서버 안에 수백 개의 프로세스(컨테이너)가 띄워질 때, 특정 악성 앱이나 버그 걸린 앱이 서버의 모든 메모리를 빨아먹어 옆에 있는 정상적인 앱들까지 일제히 멈추게 만드는 **'시끄러운 이웃(Noisy Neighbor)' 현상을 커널 레벨에서 원천적으로 방어(QoS 보장)**한다.
- 융합: cgroups가 컨테이너의 물리적 손발(자원량)을 묶는 하드웨어 격리 기술이라면, Namespace는 눈과 귀(식별자)를 가리는 논리적 격리 기술이다. 이 두 커널 기술이 완벽히 융합하여 Docker의 컨테이너 엔진을 이룩했으며, 쿠버네티스(K8s)의
resources.requests와limits설정의 밑바탕이 된다.
Ⅰ. 개요 및 필요성 (Context & Necessity)
-
개념: 컴퓨터 자원을 공산당처럼 철저하게 배급(Quota)하는 시스템이다. 리눅스 시스템에서 돌아가는 프로세스들을 특정 '제어 그룹(Control Group)' 폴더에 밀어 넣고, "이 폴더 안에 있는 놈들은 다 합쳐서 메모리 500MB, CPU는 1개만 써라!"라고 리눅스 커널에 명령을 내리면, 커널 스케줄러가 이 규칙을 칼같이 지켜주는 원리다.
-
필요성: 클라우드 시대 이전에는 웹 서버 하나가 무한 루프 버그(While true)에 빠져 CPU를 100% 갉아먹으면, 그 옆에서 얌전히 돌고 있던 중요 데이터베이스(DB) 프로세스마저 CPU를 할당받지 못해 함께 뻗어버렸다. OS가 자원을 프로세스별로 알아서 대충 분배하는 전통적 방식(Fairness)으로는 특정 악성 프로세스의 '독식'을 막을 재간이 없었다. 가상 머신(VM)처럼 하이퍼바이저가 자원을 뚝뚝 쪼개주듯, 가벼운 리눅스 프로세스 레벨에서도 "절대 넘어오지 마!"라고 철벽을 칠 수 있는 강제적(Hard) 자원 통제 장치가 절실했다.
-
💡 비유: 한집에서 5명의 형제가 피자 한 판(서버의 CPU/RAM 자원)을 먹습니다. 예전에는 덩치 큰 첫째(메모리 누수 프로세스)가 피자를 혼자 8조각 다 먹어버려서 동생들(정상 프로세스)이 굶어 죽었습니다. 그래서 엄마(리눅스 커널)가 나섰습니다. 아예 접시(cgroups)를 5개로 나눠서 각자 피자 2조각씩만 딱 올려주고(Limit), "다 먹어도 절대 옆 동생 접시에는 손대지 마!"라고 무서운 감시자 역할을 하는 것이 바로 cgroups입니다.
-
등장 배경 및 발전 과정:
- 구글의 고민과 발명 (2006년): 수백만 대의 서버에 자체 검색 엔진과 배치 작업을 섞어 돌리던 구글 엔지니어들(Paul Menage, Rohit Seth)이 프로세스 제어를 위해 'Process Containers'라는 기술을 개발했다.
- 리눅스 커널 편입 (2008년): 이 기술의 위대함을 알아본 리눅스 토발즈가 2.6.24 커널에 이 기능을 공식 통합하며 이름을 'cgroups'로 변경했다.
- Docker 및 K8s의 심장 (현재): 이후 cgroups v1의 복잡성을 개선한 cgroups v2가 나왔으며, 오늘날 쿠버네티스의 Pod 자원 제한 설정 스펙(yaml)은 100% cgroups API를 호출하는 거대한 프론트엔드 인터페이스라 해도 과언이 아니다.
-
📢 섹션 요약 비유: 수영장에 그어진 레인(Lane)의 물살 차단막과 같습니다. 옆 레인의 덩치 큰 수영 선수(폭주하는 프로세스)가 아무리 심하게 물보라를 치고 수영을 해도, 튼튼한 차단막(cgroups) 덕분에 내 레인의 물결(시스템 자원)은 잔잔하게 유지되어 나만의 수영(안정적 서비스)을 할 수 있습니다.
Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)
cgroups의 핵심 서브시스템(Subsystems / Controllers)
cgroups는 덩어리 하나가 아니라, 자원의 종류별로 감독관(Controller)이 따로 나뉘어 있다.
| 제어기 (Controller) | 묶어버리는 자원 | 동작 방식 (커널 제어) | 비유 |
|---|---|---|---|
| cpu | CPU 사용 시간 통제 | 그룹별로 CPU 스케줄러 점유 시간의 가중치(Shares)를 조율. | 식사 시간제한 (너는 1분, 쟤는 2분) |
| cpuacct | CPU 사용량 모니터링 | 특정 그룹이 CPU를 얼마나 썼는지 정밀한 통계/회계(Accounting) 제공. | 식당 영수증 발급 |
| memory | RAM 상한선 및 OOM 통제 | 메모리 사용 한계(Limit)를 설정. 한계 초과 시 커널이 해당 그룹 프로세스를 무자비하게 죽임(OOM Killer 🔪). | 식사량 제한 (밥 2공기 넘으면 쫓겨남) |
| blkio | 블록 디바이스(디스크) I/O 통제 | 특정 디스크 장치(HDD/SSD)에 대한 읽기/쓰기 속도(MB/s, IOPS) 상한선 제한. | 포크/나이프 속도 제한 |
| devices | 디바이스 노드 접근 제어 | 해당 그룹의 프로세스가 /dev 내의 특정 물리 하드웨어 장치 노드를 만들거나 접근하지 못하게 차단. | 주방 출입 통제 (특정 칼 사용 금지) |
| freezer | 프로세스 일시 정지 | 해당 그룹에 속한 모든 프로세스를 일시 정지(Suspend)하거나 다시 재개(Resume)시킴. | 얼음 땡 게임 |
OOM Killer (Out Of Memory) 동작 원리
개발자가 가장 많이 마주하는 컨테이너 장애인 OOMKilled (Exit Code 137) 에러는 사실 cgroups의 메모리 컨트롤러가 수행하는 사형 집행(Execution)이다.
┌───────────────────────────────────────────────────────────────┐
│ cgroups 메모리 통제 및 OOM Killer (Out of Memory) 작동 구조 │
├───────────────────────────────────────────────────────────────┤
│ │
│ [ 리눅스 Host OS (Total RAM: 32GB) ] │
│ │ │
│ ▼ 커널(Kernel) cgroups 룰(Rule) 주입 │
│ ╔══════════════════════════════════════════════════════════╗ │
│ ║ [ Docker Container A (Java App) ] ║ │
│ ║ - Cgroup Memory Limit: 1 GB (최대 허용량) ║ │
│ ║ - Current Usage: 800 MB (메모리 누수 발생 중...) ║ │
│ ║ ║ │
│ ║ ... 10초 후 ... ║ │
│ ║ - Current Usage: 1.01 GB 돌파 시도! (선 넘음!) ║ │
│ ╚══════════════════════════════════════════════════════════╝ │
│ │ │
│ ▼ (삐용삐용! cgroups 메모리 컨트롤러 알람 발생) │
│ │
│ [ 리눅스 커널 OOM Killer 작동 🔪 ] │
│ "어이 Container A! 너 나랑 약속한 1GB 한도 넘었네?" │
│ "다른 착한 프로세스들 피해 볼라, 너 당장 사형(SIGKILL-9)!" │
│ │
│ ▶ 결과: Container A 프로세스 즉시 강제 종료 (Exit Code 137 발생). │
│ 이 잔인한 룰 덕분에 서버에 있는 31GB의 램은 안전하게 지켜져서 │
│ 옆에 있는 다른 컨테이너(DB, 웹 등)는 100% 무사함! (QoS 보장) │
└───────────────────────────────────────────────────────────────┘
[다이어그램 해설] cgroups에 메모리 상한선(1GB)을 걸어두면, 커널은 해당 그룹에 속한 프로세스가 메모리를 더 달라고 요청할 때 할당을 거부한다. 그래도 앱(Java 힙 등)이 억지로 메모리를 점유해 상한선을 뚫으려 하면, 커널 내의 저승사자인 OOM (Out Of Memory) Killer가 깨어나 해당 프로세스에 자비 없이 9번 시그널(Kill)을 날려 목을 쳐버린다. 앱 개발자 입장에선 앱이 픽픽 죽어서 짜증 나겠지만, 시스템 엔지니어 입장에서는 이 cgroups의 철권통치 덕분에 단 하나의 버그 걸린 앱 때문에 물리 서버 전체가 멈춰버리는 끔찍한 재앙(시끄러운 이웃 문제)을 원천 차단할 수 있게 된다.
Ⅲ. 실무 적용 및 기술사적 판단
실무 시나리오
-
시나리오 — 쿠버네티스(K8s) Resource Limits 미설정으로 인한 워커 노드 붕괴: 개발팀이 급하게 Node.js 마이크로서비스를 만들면서
deployment.yaml파일에resources: requests/limits설정을 아예 빼먹고 배포했다. 이 앱에 메모리 누수(Memory Leak)가 발생하여 K8s 워커 노드(EC2, 16GB)의 모든 RAM을 빨아먹자, 같은 노드에 떠 있던 Kubelet과 로깅 에이전트 데몬까지 OOM으로 터져 노드 전체가NotReady상태가 된 상황.- 판단: K8s에서 cgroups 제약(Limits)을 생략한 컨테이너(BestEffort QoS)는 무제한 폭식 권한을 가진다. 이는 멀티 테넌시 환경의 규칙을 박살 낸 최악의 설정 누락이다.
- 해결책: 모든 컨테이너 배포 스펙에 메모리와 CPU의 **Requests(최소 보장, cgroups shares)**와 **Limits(최대 상한, cgroups quota/limit)**를 강제로 입력하도록 LimitRange나 OPA(Open Policy Agent) 정책으로 파이프라인 단에서 막아야 한다. K8s의 이 선언적 설정(yaml)들이 내부적으로는 도커를 거쳐 리눅스 커널의
/sys/fs/cgroup/memory폴더에 제어값들을 기록하여 완벽한 하드웨어 격리 통제망을 구축한다.
-
시나리오 — CPU Throttling으로 인한 API 응답 지연 (Limits의 역설): Java(Spring Boot) API 컨테이너에
CPU Limit: 2(2코어 제한)를 빡빡하게 걸어두었다. 메모리는 남는데, 트래픽이 몰릴 때마다 CPU Limit 벽에 부딪혀 커널 스케줄러가 이 컨테이너의 목을 조르며(CPU Throttling) 연산을 멈춰 세웠다. 이로 인해 API 응답 시간이 수 초대로 늘어나고, 멀쩡한 컨테이너가 HPA(오토스케일링) 조건에도 맞지 않아 스케일아웃이 안 되는 딜레마.- 판단: 메모리 초과는 OOM(죽음)으로 끝나지만, CPU 초과는 죽이지 않고 대신 실행 시간을 강제로 빼앗아버려(Throttling, 버벅거림) 서비스 지연이라는 보이지 않는 독약으로 돌아온다.
- 해결책: Java나 Go처럼 초기 부팅 시 코어를 병렬로 강하게 당겨 쓰는 런타임의 경우, CPU Limit 설정을 아예 해제하거나 매우 넉넉하게 주어(Guaranteed 대신 Burstable로 유연하게 설정) Throttling을 우회하는 것이 최근 클라우드 엔지니어링의 실무 팁(Best Practice) 중 하나로 거론된다. (단, 메모리 Limit은 반드시 걸어야 한다.)
도입 체크리스트
- 운영적: 우리 회사의 컨테이너가 툭하면
OOMKilled(137)를 내며 죽는가? 그렇다면 컨테이너 메모리 제한(Limit)을 늘려줄 것이 아니라, JVM의-Xmx힙(Heap) 사이즈가 컨테이너 cgroups 제한(Limit)보다 10~20% 작게 설정되어 있는지 확인해야 한다. (앱이 cgroups 한계를 모르고 메모리를 막 쓰다 죽는 현상 방지).
Ⅳ. 기대효과 및 결론
정량/정성 기대효과
| 구분 | cgroups 미적용 (모놀리식 환경) | cgroups 적용 (Docker/K8s 환경) | 개선 효과 |
|---|---|---|---|
| 정량 (격리성) | 버그 난 앱 1개가 서버 RAM 100% 점유 | 앱 1개당 할당된 RAM(예: 1GB) 상한에서 즉사 | 특정 앱의 장애가 물리 서버 전체로 번지는 동반 장애율 0% |
| 정량 (밀집도) | 자원 쟁탈 방지를 위해 서버에 앱 1~2개만 배치 | CPU/RAM 파이 쪼개기로 1서버에 수백 개 배치 | K8s를 통한 서버 인프라 사용 효율(Density) 수백 % 향상 |
| 정성 (자원 계획) | "서버 느려졌네, RAM 꽂아줘" (주먹구구) | Requests/Limits 수치화를 통한 정밀 용량 산정 | 데이터에 기반한 용량 계획(Capacity Planning) 및 FinOps 실현 |
cgroups는 덩치 큰 하드웨어 장비(하이퍼바이저) 없이도, 소프트웨어(운영체제)의 두뇌만으로 완벽한 자원 분배 통치 체제를 이룩해 낸 천재적인 커널 발명품이다. 기술사는 쿠버네티스의 화려한 오토스케일링(HPA)이나 선언적 배포(yaml)의 이면에, 이처럼 무자비하게 프로세스 자원의 목줄을 쥐고 흔드는 'cgroups'와 보이지 않는 벽을 치는 'Namespace'의 원초적 동작 원리가 톱니바퀴처럼 맞물려 있음을 꿰뚫어 보아야 한다.
📌 관련 개념 맵 (Knowledge Graph)
| 개념 명칭 | 관계 및 시너지 설명 |
|---|---|
| Namespace (네임스페이스) | cgroups가 컨테이너의 하드웨어 사용량(손발)을 묶는 기술이라면, Namespace는 컨테이너 간의 식별자와 파일 뷰(눈과 귀)를 가려버리는 짝꿍 격리 기술이다. |
| OOM Killer (Out Of Memory) | cgroups 메모리 컨트롤러가 설정한 한도(Limit)를 초과하는 불량 프로세스를 커널이 자비 없이 강제 종료시켜 시스템을 구하는 데스노트다. |
| Kubernetes Resource Limits/Requests | K8s 개발자가 yaml에 적어 넣는 CPU/RAM 요구사항으로, 이 값들은 Kubelet을 통해 그대로 리눅스 커널의 cgroups 설정값으로 번역(Translation)되어 주입된다. |
| 시끄러운 이웃 (Noisy Neighbor) | 멀티 테넌트 환경에서 특정 가상머신이나 컨테이너가 자원을 폭식하여 남에게 피해를 주는 현상. cgroups는 이를 막는 가장 완벽한 백신이다. |
| Docker Engine | 일반 개발자가 건드리기 복잡한 /sys/fs/cgroup 폴더의 커널 파라미터 조작을, docker run -m 512m 한 줄의 명령어로 매끄럽게 자동화해 준 천재적인 소프트웨어다. |
👶 어린이를 위한 3줄 비유 설명
- 캠핑장에서 아이들 10명이 고기 파티를 해요. 고기(컴퓨터 자원)는 딱 100인분이 있는데, 덩치 큰 아이(버그 앱)가 고기를 90인분이나 혼자 다 먹어버리려고 해요. (시끄러운 이웃 현상)
- 이러면 다른 친구들이 배가 고파 쓰러지겠죠? 그래서 호랑이 선생님(리눅스 커널)이 나섰어요! "여기 각자 자기 이름이 적힌 접시에 고기 딱 10인분씩만 덜어 줄 테니, 다 먹었으면 더 이상 남의 고기 넘보지 마!"
- 이렇게 아이들(프로세스)마다 먹을 수 있는 고기와 밥의 한계량(Limit)을 아주 엄격하게 딱딱 정해서 배분해 주고 통제하는 호랑이 선생님의 마법을 'cgroups(씨그룹스)'라고 한답니다!