핵심 인사이트 (3줄 요약)
- 본질: cgroups (Control Groups)는 하나의 리눅스 커널 안에서 프로세스 집단별로 중앙 처리 장치 (Central Processing Unit, CPU), 메모리, 입출력 (Input/Output, I/O) 같은 하드웨어 자원 사용량을 계층적으로 제한하고 계량하는 프레임워크다.
- 가치: 네임스페이스가 "무엇을 보게 할지"를 정한다면, cgroups는 "얼마나 쓰게 할지"를 정해 noisy neighbor 문제를 줄이고 서비스 품질을 예측 가능하게 만든다.
- 판단 포인트: quota만 낮춘다고 끝나는 것이 아니라
cpu.weight,cpu.max,memory.high,memory.max,io.max,cpuset의 조합이 워크로드 특성과 맞아야 안정적인 배분이 된다.
Ⅰ. 개요 및 필요성
컨테이너와 프로세스 격리에서 가장 자주 생기는 오해는 "보이지 않게 했으니 안전하다"는 생각이다. 하지만 네임스페이스로 프로세스 목록과 네트워크 인터페이스를 분리해도, 한 컨테이너가 CPU를 무한정 태우거나 페이지 캐시를 가득 채우면 같은 호스트의 다른 서비스가 그대로 느려진다. 즉 격리에는 가시성 분리와 함께 자원 사용량 통제가 반드시 필요하다.
cgroups는 이 문제를 해결하기 위해 등장했다. 프로세스를 계층적인 그룹으로 묶고, 각 그룹이 쓸 수 있는 CPU 시간, 메모리 용량, 디스크 대역폭, 프로세스 수를 제어함으로써 하나의 리눅스 서버를 다중 테넌트 플랫폼처럼 운영할 수 있게 만들었다. 클라우드와 쿠버네티스가 리눅스를 기본 토대로 삼을 수 있었던 이유 중 하나도 바로 이 자원 배분 능력이다.
핵심은 "완전한 차단"보다 "예측 가능한 배분"이다. cgroups는 하드웨어를 물리적으로 쪼개는 것이 아니라, 스케줄러와 메모리 관리자, 블록 계층이 각 그룹을 다른 정책으로 대하도록 만드는 소프트웨어 제어판이다. 그래서 잘 쓰면 효율과 안정성을 함께 얻고, 잘못 쓰면 오히려 throttling과 메모리 부족 종료 (Out Of Memory, OOM)가 늘어난다.
- 📢 섹션 요약 비유: cgroups는 급식실 배식표와 같다. 같은 부엌을 쓰더라도 반마다 배식량과 순서를 정해야 누군가가 혼자 다 먹어 버리는 일을 막을 수 있다.
Ⅱ. 아키텍처 및 핵심 원리
현대 리눅스는 주로 cgroup v2의 단일 계층 구조를 사용한다. 루트 cgroup 아래에 서비스별, Pod별, 컨테이너별 하위 그룹을 만들고, 각 컨트롤러가 그 경로를 따라 정책을 적용한다. 이때 중요한 것은 컨트롤러마다 역할이 다르다는 점이다. CPU는 스케줄링 비율과 상한선을, 메모리는 보호·회수·강제 종료 조건을, I/O는 대역폭과 지연 보호를 담당한다.
| 컨트롤러 | 대표 설정 예시 | 의미 | 트레이드오프 |
|---|---|---|---|
| CPU | cpu.weight, cpu.max | 상대 가중치와 절대 상한 | 상한이 낮으면 burst 처리 손해 |
| Memory | memory.low, memory.high, memory.max | 보호선, 회수 시작점, 최종 한도 | 너무 낮으면 reclaim/OOM 증가 |
| I/O | io.weight, io.max | 장치별 공정성, 대역폭 제한 | 지연 보호 대신 배치 작업이 느려질 수 있음 |
| Cpuset | cpuset.cpus, cpuset.mems | 특정 CPU와 NUMA (Non-Uniform Memory Access) 노드 고정 | locality 향상 대신 유연성 감소 |
| Pids | pids.max | fork 폭주 방지 | 너무 낮으면 정상적인 확장도 막힘 |
아래 그림은 cgroup v2가 자원을 계층적으로 나누는 방식을 단순화한 것이다.
/
└─ kubepods.slice
├─ guaranteed
│ └─ pod-a
│ ├─ cpu.max = 200000 100000
│ ├─ memory.high = 4G
│ └─ io.max = dev 8:0 rbps=200M
└─ burstable
└─ pod-b
├─ cpu.weight = 100
├─ memory.max = 2G
└─ pids.max = 512
여기서 특히 자주 혼동하는 개념이 cpu.weight와 cpu.max다. cpu.weight는 경합이 있을 때 상대적으로 얼마만큼 더 CPU를 받을지 정하는 값이고, cpu.max는 한가한 시스템에서도 절대로 넘을 수 없는 상한선이다. 메모리도 비슷하다. memory.high는 커널이 재회수를 적극적으로 시작하는 경고선이고, memory.max는 더 이상 버티지 못할 때 강한 제한으로 이어진다.
즉 cgroups는 단순한 "제한 장치"가 아니라 압력 조절기다. 워크로드에 따라 보호선, 경고선, 절대 상한선을 구분해 써야 서비스 품질이 안정된다.
- 📢 섹션 요약 비유: cgroups 제어값은 수도관 밸브와 같다. 어느 밸브는 평소 물 흐름 비율을 정하고, 어느 밸브는 넘치기 직전에 경고하며, 마지막 밸브는 진짜로 잠가 버린다.
Ⅲ. 비교 및 연결
cgroups는 네임스페이스나 가상 머신 예약과 자주 비교된다. 하지만 셋은 서로 다른 질문에 답한다. 네임스페이스는 "무엇을 보나", cgroups는 "얼마나 쓰나", 가상 머신 예약은 "커널 경계까지 분리할 것인가"를 다룬다.
| 비교 축 | 네임스페이스 | cgroups | 가상 머신 예약 |
|---|---|---|---|
| 해결 질문 | 무엇이 보이는가 | 얼마나 쓸 수 있는가 | 어디까지 독립 커널인가 |
| CPU/메모리 수량 제어 | 직접 못 함 | 핵심 기능 | 가능 |
| 커널 공유 여부 | 공유 | 공유 | 비공유 |
| 오버헤드 | 매우 낮음 | 매우 낮음 | 더 큼 |
| 대표 용도 | 가시성 격리 | 품질 보장, 폭주 방지 | 강한 테넌트 경계 |
cgroups 내부에서도 상대 배분과 절대 제한을 구분해 봐야 한다. 상대 배분은 시스템이 바쁠 때 공정성을 만드는 데 유리하고, 절대 제한은 폭주 방지에 유리하다. 따라서 배치성 작업과 지연 민감한 서비스가 섞여 있는 환경에서는 둘을 함께 써야 한다.
또한 cgroups는 커널 다른 서브시스템과 깊게 연결된다. CPU 설정은 CFS (Completely Fair Scheduler)의 스케줄링 결과에 영향을 주고, 메모리 설정은 페이지 캐시와 재회수 전략에 영향을 주며, I/O 설정은 블록 스케줄러가 장치별 대기 시간을 배분하는 방식과 맞물린다. 그래서 설정 파일은 단순하지만, 효과는 커널 전체에 걸쳐 나타난다.
- 📢 섹션 요약 비유: 네임스페이스가 교실 이름표를 붙이는 일이라면, cgroups는 실제로 나눠 줄 교과서와 점심 수량을 정하는 일이다. 이름표만 붙여서는 혼잡이 해결되지 않는다.
Ⅳ. 실무 적용 및 기술사 판단
실무에서는 cgroups를 단독으로 쓰기보다 오케스트레이터나 서비스 매니저가 대신 설정하는 경우가 많다. 예를 들어 쿠버네티스는 요청량과 제한량을 바탕으로 Pod를 서로 다른 품질 계층에 배치하고, 그 결과를 cgroup 값으로 풀어낸다. 예시로 cpu.max = 50000 100000은 100 밀리초 주기 중 50 밀리초만 실행하게 하므로 0.5 CPU 상한과 비슷한 의미를 가진다.
메모리와 I/O는 더 신중해야 한다. 평균 사용량만 보고 memory.max를 너무 낮게 잡으면 순간 피크 때 OOM이 반복되고, io.max 없이 로그 폭주를 방치하면 CPU는 멀쩡한데 전체 시스템 tail latency가 무너진다. 데이터베이스, 메시지 브로커, 로그 수집기처럼 디스크 패턴이 다른 서비스가 한 노드에 섞여 있다면, CPU보다 I/O 제어가 더 결정적일 때도 많다.
실무 체크리스트
- 평균 사용량이 아니라 피크 사용량과 회복 시간까지 보고
memory.high와memory.max를 분리했는가? cpu.weight로 상대 공정성을 주고, 꼭 필요한 곳에만cpu.max로 상한을 걸었는가?- 디스크가 공유 장치라면
io.max또는io.weight를 함께 설계했는가? - 압력 병목을 Pressure Stall Information (PSI)으로 관측하고 있는가?
대표 안티패턴
-
모든 서비스에 같은 CPU quota를 기계적으로 적용하는 경우
-
메모리 상한만 설정하고 reclaim 경고선인
memory.high를 무시하는 경우 -
cpuset으로 코어를 과도하게 고정해 오히려 호스트 전체 유연성을 잃는 경우 -
cgroups를 설정했으니 성능 문제가 자동 해결될 것이라 기대하는 경우
-
📢 섹션 요약 비유: cgroups 설계는 도시 교통 신호 제어와 같다. 빨간불 하나만 세게 걸면 정체가 사라지는 것이 아니라, 차종과 시간대에 맞춰 신호 비율과 우회로를 함께 조정해야 한다.
Ⅴ. 기대효과 및 결론
cgroups를 잘 설계하면 한 대의 서버 안에서도 서비스별 품질을 예측 가능하게 만들 수 있다. 특정 작업이 폭주해도 전체 노드가 무너지는 일을 줄이고, 유휴 자원은 탄력적으로 재활용하면서도 핵심 서비스에는 보호선을 줄 수 있다. 이것이 리눅스가 컨테이너 플랫폼의 기본 운영체제가 된 이유 중 하나다.
그러나 cgroups는 만능이 아니다. 공유 커널 구조는 그대로 유지되므로, 보안 경계는 네임스페이스·seccomp·가상 머신 격리와 함께 봐야 한다. 또한 값 하나를 낮추는 방식으로는 해결되지 않는 경우가 많고, PSI나 eBPF (extended Berkeley Packet Filter) 기반 관측과 함께 지속적으로 조정해야 한다.
따라서 cgroups는 "리소스를 막는 장치"가 아니라 "한정된 하드웨어를 서비스 목적에 맞게 배분하는 정책 엔진"으로 기억하는 것이 맞다. 수량 제어, 압력 감지, 회복 전략이 함께 설계될 때 비로소 진짜 효과가 나온다.
- 📢 섹션 요약 비유: cgroups는 한 냄비의 국을 여러 그릇에 나눠 담는 국자와 같다. 국자를 잘 써야 모두가 먹고, 너무 한쪽에만 퍼 주면 금방 싸움이 난다.
📌 관련 개념 맵
| 개념 | 연결 포인트 |
|---|---|
| Namespace | cgroups와 짝을 이루어 가시성 격리와 수량 제어를 분담 |
| CFS | CPU 가중치와 quota가 실제 실행 시간으로 바뀌는 스케줄링 엔진 |
| Page Cache | 메모리 cgroup 설정이 직접 영향을 주는 핵심 커널 자원 |
| PSI | CPU·메모리·I/O 압력을 수치로 보여 주는 관측 지표 |
| Kubernetes QoS (Quality of Service) | cgroups 정책을 대규모 클러스터 배치 규칙으로 연결하는 상위 계층 |
📈 관련 키워드 및 발전 흐름도
ulimit 중심 제한
│
▼
cgroup v1 개별 컨트롤러
│
▼
cgroup v2 단일 계층
│
▼
container orchestrator QoS
│
▼
PSI + 자동 튜닝 루프
이 흐름은 "단순 사용자 제한 → 그룹별 자원 제어 → 통합 계층화 → 오케스트레이션 연동 → 관측 기반 자동화"로 cgroups가 발전한 과정을 보여준다.
👶 어린이를 위한 3줄 비유 설명
- cgroups는 컴퓨터 힘을 반마다 나눠 주는 배식 선생님이에요.
- 어떤 반이 너무 많이 가져가면 다른 반이 배고프니까, 선생님이 미리 양을 정해 줘요.
- 그래서 여러 프로그램이 같이 돌아가도 한 친구가 컴퓨터 힘을 다 먹어 버리지 못한답니다.