컨테이너의 메모리와 CPU 제한
컨테이너에 메모리와 CPU 상한을 설정하는 방법, 제한이 없으면 하나의 서비스가 다른 모든 서비스를 마비시키는 이유, 커널이 프로세스를 종료했는지 판별하는 방법을 설명해요.
업데이트 2026년 8월 23일
기본적으로 컨테이너는 머신의 모든 메모리와 모든 코어를 가져갈 수 있어요. 2GB인 VDS-1에서는 결과가 뻔하죠. 서비스 하나가 부풀어 오르고, 커널이 프로세스를 죽이기 시작하는데, 정작 죽는 건 그 서비스가 아니라 옆에 있는 데이터베이스예요.
상한을 설정하는 방법
services:
app:
image: ghcr.io/example/app:1.5.0
deploy:
resources:
limits:
memory: 768M
cpus: "1.0"
reservations:
memory: 256M
Compose는 단순한 docker compose up -d에서도 deploy.resources.limits를 적용해요. docker run에 해당하는 방법은 다음과 같아요.
docker run -d --memory 768m --cpus 1.0 --memory-swap 768m <image>
--memory-swap을 --memory와 같게 설정하면 스왑을 금지해요. 몇 시간 동안 거의 움직이지 않는 머신보다는 차라리 정직하게 죽는 게 나아요.
얼마나 할당할까
티어에서 시작해서 시스템에 여유를 남겨 두세요.
| 티어 | 메모리 | 적절한 컨테이너 총 상한 |
|---|---|---|
| VDS-1 | 2 GB | 최대 1.5 GB |
| VDS-2 | 4 GB | 최대 3 GB |
| VDS-3 | 8 GB | 최대 6.5 GB |
| VDS-4 | 16 GB | 최대 14 GB |
| VDS-5 | 24 GB | 최대 21 GB |
| VDS-6 | 32 GB | 최대 29 GB |
나머지는 시스템, 파일 캐시, Docker 자체에 필요해요. 상한의 합이 메모리 양보다 큰 건 설정 실수가 아니라 — 터지기를 기다리는 재앙이에요.
CPU의 경우: cpus: "1.0"은 코어 하나 전체예요. VDS-1은 코어가 하나뿐이라 CPU를 제한할 의미가 거의 없지만, VDS-4 이상에서는 서버 구성이 여섯 개를 전부 차지하지 않도록 하는 게 좋아요.
실제로 소비되는 것
docker stats --no-stream
docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.CPUPerc}}"
free -h
부하가 걸린 상태에서 하루 동안 서비스를 지켜보고, 최대치의 약 1.5배로 상한을 설정하세요. 너무 빡빡한 상한은 없는 것보다 나빠요. 가장 필요할 때 서비스가 재시작될 거예요.
전체 화면
htop과btop은 "Screen" 콘솔(VNC)에서만 작동해요. 텍스트 콘솔은 창 크기를 전달하지 않아서 항상 80×24이고, 인터페이스가 깨져요. 텍스트 콘솔에서 빠르게 측정하려면docker stats --no-stream과free -h를 사용하세요.
메모리 때문에 죽었다는 걸 알아내는 방법
docker inspect -f '{{.State.ExitCode}} {{.State.OOMKilled}}' <container>
sudo dmesg -T | grep -i -E "out of memory|killed process"
종료 코드 137과 OOMKilled: true가 함께 나오면 명백한 판정이에요. 여기서 선택지는 두 가지예요. 상한이 너무 낮게 설정됐다면 올리거나, 애플리케이션이 계속 커지는 이유를 알아내거나. 누수는 메모리를 더 준다고 해결되지 않고, 미뤄질 뿐이에요.
Java와 기타 특수한 경우
Java 가상 머신은 여전히 컨테이너 상한이 아니라 호스트의 메모리를 보는 경우가 있고, 존재하지도 않는 여유를 감안해서 힙을 설정해요. 명시적으로 지정하세요.
environment:
JAVA_TOOL_OPTIONS: "-Xmx512m -Xms256m"
PostgreSQL(shared_buffers)과 자체 풀을 가진 모든 서비스에도 마찬가지예요.