얼마나 쓰는가를 제한하는 리눅스 커널 기능(control group).
docker run -d --memory=512m --cpus=1.5 --pids-limit=200 myapp
- memory — 상한을 넘으면 커널이 프로세스를 죽인다 (OOM Killer)
- cpu — 주기당 실행 시간을 제한한다
- pids — 프로세스·스레드 수 제한 (fork 폭탄·좀비 누적 방어)
- io — 디스크 대역폭 제한
메모리와 CPU 는 증상이 완전히 다르다
-
메모리 초과 — 죽인다 (하드 리밋)
- 상태 OOMKilled · 종료 코드 137
- 커널이 SIGKILL 로 죽여 정리 코드가 안 돈다
- 애플리케이션 로그에 아무것도 안 남는다
-
CPU 초과 — 죽이지 않는다. 다음 주기까지 재운다 (스로틀링)
- 안 죽는데 응답이 튄다. p99 만 나빠진다
CPU 제한의 진짜 의미
--cpus=1.0 은 '평균 1코어' 가 아니라 '주기당 상한' 이다
- 기본 주기 100ms 에 100ms 만 쓸 수 있다
멀티스레드 애플리케이션이 순간적으로 4코어를 쓰려 하면
- 25ms 만에 할당량을 다 쓰고 나머지 75ms 를 강제로 쉰다
- 평균 사용률은 낮게 보이는데 지연은 튄다
확인 — cgroup 스로틀 통계
cat /sys/fs/cgroup/cpu.stat
nr_throttled 스로틀된 주기 수
throttled_usec 스로틀된 총 시간
nr_throttled 가 계속 늘면 확정이다 "CPU 사용률은 60% 인데 왜 느리지" 의 정체가 대개 이것이다
애플리케이션이 제한을 알아야 한다
cgroup 은 커널 수준 제한이라, 런타임이 인식하지 못하면 호스트 전체 자원을 기준으로 판단한다
- 스레드 풀 크기를 호스트 코어 수로 잡는다 → 과도한 병렬 → 스로틀
- 힙 크기를 호스트 메모리 기준으로 잡는다 → OOMKilled
JVM 은 Java 10+ 에서 컨테이너 제한을 인식한다 (UseContainerSupport) -XX:MaxRAMPercentage=75 로 limit 대비 비율로 잡는다 -Xmx 를 직접 주면 limit 이 바뀌어도 안 따라간다
스왑을 함께 잠근다
docker run --memory=512m --memory-swap=512m myapp
--memory-swap 을 --memory 와 같게 두면 스왑을 안 쓴다 안 그러면 메모리를 넘겨도 스왑으로 버티며 극도로 느려진다
- 죽는 것보다 나쁜 상태가 될 수 있다 (살아 있는데 응답이 없다)
cgroup v1 과 v2
- v1 — 자원마다 별도 계층 (memory · cpu · blkio …)
- 경로: /sys/fs/cgroup/memory/... 처럼 나뉘어 있다
- v2 — 하나의 통합 계층. 파일 이름도 정리됐다
- memory.max · cpu.max · pids.max
요즘 배포판은 대부분 v2 다 문서나 블로그의 경로가 안 맞으면 버전 차이를 의심한다
- 확인 — stat -fc %T /sys/fs/cgroup → cgroup2fs 면 v2
컨테이너 안에서 직접 읽는다
cat /sys/fs/cgroup/memory.max # 메모리 상한 (v2)
cat /sys/fs/cgroup/memory.current # 현재 사용량
cat /sys/fs/cgroup/cpu.max # "150000 100000" = 주기 100ms 중 150ms
cat /sys/fs/cgroup/cpu.stat # nr_throttled · throttled_usec
cat /sys/fs/cgroup/pids.max
애플리케이션이 자기 제한을 알고 싶으면 이 파일을 읽으면 된다 런타임이 자동으로 안 해 주는 언어에서는 직접 읽어 스레드 풀을 정한다
면접 함정
- ❌ "CPU 제한을 넘으면 컨테이너가 죽는다" → 스로틀될 뿐이다. 죽는 것은 메모리다.
- ❌ "메모리 제한만 걸면 안전하다" → 런타임이 그 값을 인식하지 못하면 그대로 초과한다.