본문 바로가기
AI Journey/리눅스

도커 컨테이너 리소스 제한하기: 메모리, CPU, Block I/O 제어

by 보눔비스타 2026. 1. 14.

도커 컨테이너는 호스트 OS의 커널을 공유하며 프로세스 단위로 격리되어 실행된다.

리소스를 제한하지 않고 실행할 경우, 하나의 컨테이너가 호스트의 모든 자원을 독점하여 시스템 전체의 불안정성을 초래할 수 있다.

따라서 안정적인 시스템 운영을 위해 컨테이너별로 CPU, 메모리, 디스크 I/O 사용량을 제어하는 방법과 원리를 알고 있어야 한다.

 

0. 사전 준비: 커널 파라미터 설정

도커의 리소스 제한 기능은 리눅스 커널의 Cgroups(Control Groups) 기술을 기반으로 한다.

특히 메모리 스왑(Swap) 제한 기능을 활성화하기 위해서는 부트로더 수준에서의 설정이 필요하다.

# GRUB 설정 파일 수정
sudo nano /etc/default/grub

# 기존 설정에 cgroup_enable=memory swapaccount=1 추가
# 수정 후: GRUB_CMDLINE_LINUX_DEFAULT="quiet splash cgroup_enable=memory swapaccount=1"

# 설정 반영 및 재부팅
sudo update-grub
sudo reboot

  • Cgroups: 프로세스들에 대한 자원 할당을 제어하는 리눅스 커널 기능
  • Swap Limit: 기본 커널 설정에서는 메모리 제한은 활성화되어 있으나 스왑 제한은 비활성화된 경우가 많다. 위 설정은 도커가 컨테이너의 스왑 사용량까지 제어할 수 있도록 커널 파라미터를 수정하는 과정이다.

 

1. 메모리 자원 제한과 OOM Killer

메모리 제한은 컨테이너가 사용할 수 있는 물리적 RAM과 스왑 영역의 합계를 지정하는 방식이다.

 

스왑을 차단한 메모리 제한 테스트

메모리 제한이 임계치에 도달했을 때의 동작을 확인하기 위해 스왑 메모리를 사용하지 못하도록 설정하고 물리 메모리보다 큰 부하를 발생시켜본다.

# 100MB 메모리 제한, 스왑 미사용(100MB 설정), 150MB 부하 발생
docker run --name mem-test --memory="100m" --memory-swap="100m" alicek106/stress stress --vm 1 --vm-bytes 150M

# 종료 후 OOMKilled 상태 확인
docker inspect mem-test | grep -iE "OOMKilled|ExitCode"

 

  • --memory-swap: 물리 메모리와 스왑의 합계다. 이 값을 --memory와 동일하게 설정하면 실질적으로 스왑 사용이 차단된다.
  • OOMKilled (Out Of Memory Killed): 커널이 시스템 전체의 붕괴를 막기 위해 메모리를 과도하게 사용하는 프로세스를 강제로 종료한 상태를 의미한다.

💡서비스 안정성을 위해 Fail-fast(빠른 실패 후 재시작)가 필요한 경우 스왑을 엄격히 제한하고, 성능 저하를 감수하더라도 서비스 유지가 중요한 경우라면 스왑을 넉넉히 설정한다.

 

 

2. CPU 자원 제한: 상대적 가중치 vs 절대적 제한

CPU 제한 방식은 크게 상대적인 우선순위를 결정하는 shares 방식과 절대적인 사용량을 제한하는 cpus 방식으로 나뉜다.

2.1 CPU Shares (상대적 가중치)

# 가중치 1024 컨테이너 실행
docker run -d --name cpu-high --cpu-shares 1024 alicek106/stress stress --cpu 8

# 가중치 512 컨테이너 실행
docker run -d --name cpu-low --cpu-shares 512 alicek106/stress stress --cpu 8

# 실시간 점유율 확인
docker stats cpu-high cpu-low

# 설정 값 확인
docker inspect cpu-high | grep -i "CpuShares"

 

 

  • 시스템에 CPU 자원이 남을 때는 모든 컨테이너가 제한 없이 자원을 사용한다. 하지만 여러 컨테이너가 동시에 CPU를 사용하려고 경쟁(Contention)할 때만 설정된 비율(1024:512 = 2:1)에 따라 자원을 배분한다.

💡  유휴 자원을 효율적으로 활용할 수 있는 유연한 방식이다.

 

docker stats로 두 컨테이너의 실시간 점유율을 확인한 결과 cpu-high가 cpu-low보다 CPU를 약 2배 점유하고 있는 것을 확인할 수 있다. 

 

2.2 CPUs (절대사용량 제한)

# CPU 사용량을 0.5개(50%)로 제한
docker run -d --name cpu-limit --cpus 0.5 alicek106/stress stress --cpu 1

# 실시간 점유율 확인
docker stats cpu-limit

# 내부 나노 CPU 설정 값 확인
docker inspect cpu-limit | grep -i "NanoCpus"

 

  • 호스트의 CPU 자원 여유와 상관없이 해당 컨테이너가 지정된 쿼터 이상의 CPU를 점유하지 못하도록 강제로 제한한다. 내부적으로는 CFS(Completely Fair Scheduler) 주기 내에 사용할 수 있는 시간을 계산하여 제어한다.

💡  예측 가능한 성능을 보장해야 하는 멀티테넌트 환경에서 유용하다.

 

docker stats로 cpu-limit 컨테이너의 실시간 점유율을 확인한 결과 50% 부근에서 계속 왔다갔다 하는 것을 확인할 수 있다.

 

 

3. Block I/O 제한: 대역폭 제어

디스크 입출력 속도 제한은 초당 읽기/쓰기 바이트 수(BPS)나 입출력 횟수(IOPS)를 제한하는 설정이다.

 

쓰기 속도 제한 및 검증

 

 

# 쓰기 속도를 1MB/s로 제한한 컨테이너 실행
docker run -it --name io-test --device-write-bps /dev/sda:1mb ubuntu:16.04 bash

# 컨테이너 내부에서 Direct I/O 테스트 (10MB 쓰기)
dd if=/dev/zero of=testfile bs=1M count=10 oflag=direct

 

  • Buffered I/O vs Direct I/O: 리눅스는 성능 향상을 위해 메모리 캐시를 거치는 Buffered I/O를 기본으로 사용한다. 도커의 I/O 제한은 실제 디스크로 데이터가 나가는 지점에서 작동하므로, 검증 시에는 oflag=direct 옵션을 사용하여 메모리 캐시를 우회해야 정확한 속도 측정이 가능하다.

💡   로그 기록이 잦은 컨테이너나 데이터베이스 컨테이너가 디스크 대역폭을 독점하여 다른 서비스의 입출력을 방해하는 현상을 방지할 수 있다.

 

 

4. 정리

도커의 리소스 제한 기능은 컨테이너 간의 간섭(Interference)을 차단하고 전체 시스템의 예측 가능성(Predictability)을 확보할 수 있게 해준다.

리소스 유형 주요 옵션 제어 방식 주요 특징
Memory --memory, --memory-swap Hard Limit 초과 시 강제 종료(OOM Kill) 발생
CPU --cpu-shares Relative Weight 자원 경합 시에만 비율에 따라 할당
CPU --cpus Hard Quota 고정된 최대 점유율 강제 적용
Disk I/O --device-write-bps Bandwidth Limit Direct I/O에 대해 초당 처리량 제한

 

효율적인 컨테이너 인프라 구축을 위해 각 서비스의 특성에 맞춰 적절한 리소스 제한 정책을 수립하고 docker statsdocker inspect를 통해 실제 설정이 의도대로 작동하는지 모니터링할 수 있다.