Theme:

컨테이너에 리소스 제한을 걸지 않으면 하나의 컨테이너가 호스트의 모든 CPU와 메모리를 독점할 수 있습니다. 프로덕션에서 여러 컨테이너가 안정적으로 공존하려면 어떻게 리소스를 관리해야 할까요?

CPU 리소스 제한

--cpus: CPU 시간 제한

BASH
# CPU 시간의 150% (1.5코어 분량)까지 사용 가능
docker run --cpus 1.5 myapp

# 쿼드코어 시스템에서 최대
docker run --cpus 4.0 myapp

내부적으로 cgroups의 cpu.cfs_quota_uscpu.cfs_period_us로 구현됩니다.

BASH
# --cpus 1.5는 다음과 동일
docker run --cpu-period=100000 --cpu-quota=150000 myapp
# 100ms 주기 중 150ms 사용 가능 (1.5코어 분량)

--cpu-shares: 상대적 가중치

BASH
# 기본값 1024
docker run --cpu-shares 512 low-priority-app
docker run --cpu-shares 2048 high-priority-app

CPU 경합이 발생할 때만 의미가 있습니다. 경합이 없으면 모든 컨테이너가 CPU를 자유롭게 사용합니다.

PLAINTEXT
CPU 경합 시 배분:
low-priority:  512/(512+2048) = 20%
high-priority: 2048/(512+2048) = 80%

--cpuset-cpus: 특정 코어 할당

BASH
# CPU 코어 0번과 2번에서만 실행
docker run --cpuset-cpus "0,2" myapp

# CPU 코어 0~3번 사용
docker run --cpuset-cpus "0-3" myapp

NUMA 아키텍처에서 메모리와 CPU의 지역성을 최적화할 때 사용합니다.

메모리 리소스 제한

--memory: 메모리 상한

BASH
# 512MB 메모리 제한
docker run --memory 512m myapp

# 단위: b, k, m, g
docker run --memory 1g myapp

--memory-swap: 메모리+스왑 합계

BASH
# 메모리 512MB, 스왑 사용 안 함
docker run --memory 512m --memory-swap 512m myapp

# 메모리 512MB, 스왑 512MB (합계 1GB)
docker run --memory 512m --memory-swap 1g myapp

# 메모리 512MB, 스왑 무제한
docker run --memory 512m --memory-swap -1 myapp

--memory-reservation: 소프트 제한

BASH
# 소프트 제한 256MB, 하드 제한 512MB
docker run --memory 512m --memory-reservation 256m myapp

호스트 메모리가 부족할 때 reservation 값까지 메모리를 회수합니다. 하드 제한은 절대 넘을 수 없습니다.

OOM 동작 제어

BASH
# OOM Killer 비활성화 (권장하지 않음)
docker run --oom-kill-disable --memory 512m myapp

# OOM 점수 조정 (-1000 ~ 1000, 높을수록 먼저 kill)
docker run --oom-score-adj 500 myapp

JVM 메모리 설정

Java 애플리케이션은 JVM 힙 메모리를 별도로 설정해야 합니다.

BASH
# 컨테이너 메모리 1G, JVM 힙 최대 768MB
docker run --memory 1g \
    -e JAVA_OPTS="-Xmx768m -Xms512m -XX:MaxMetaspaceSize=128m" \
    myjava-app

# Java 10+에서는 컨테이너 메모리를 자동 인식
docker run --memory 1g \
    -e JAVA_OPTS="-XX:MaxRAMPercentage=75.0" \
    myjava-app

I/O 리소스 제한

블록 I/O 대역폭 제한

BASH
# 디바이스 읽기 속도 제한 (10MB/s)
docker run --device-read-bps /dev/sda:10mb myapp

# 디바이스 쓰기 속도 제한
docker run --device-write-bps /dev/sda:10mb myapp

# IOPS 제한
docker run --device-read-iops /dev/sda:1000 myapp
docker run --device-write-iops /dev/sda:1000 myapp

I/O 가중치

BASH
# 블록 I/O 가중치 (10~1000, 기본 500)
docker run --blkio-weight 100 low-io-app
docker run --blkio-weight 900 high-io-app

Docker Compose에서의 리소스 제한

YAML
services:
  api:
    image: myapi:latest
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 256M
    # PID 수 제한
    pids_limit: 200
    # 스토리지 옵션
    storage_opt:
      size: "10G"

모니터링

docker stats

BASH
# 모든 컨테이너의 실시간 리소스 사용량
docker stats

# 커스텀 포맷
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}"

cAdvisor (Container Advisor)

Google이 개발한 컨테이너 모니터링 도구입니다.

YAML
# compose.yaml
services:
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.49.1
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "8081:8080"
    restart: unless-stopped
BASH
# cAdvisor 접속
curl http://localhost:8081/metrics  # Prometheus 형식 메트릭

# 웹 UI
# http://localhost:8081

Prometheus + Grafana 연동

YAML
# compose.yaml — 모니터링 스택
services:
  prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana
    volumes:
      - grafana-data:/var/lib/grafana
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:v0.49.1
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro

  node-exporter:
    image: prom/node-exporter
    pid: host
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
    command:
      - "--path.procfs=/host/proc"
      - "--path.sysfs=/host/sys"

volumes:
  prometheus-data:
  grafana-data:
YAML
# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: cadvisor
    static_configs:
      - targets: ["cadvisor:8080"]

  - job_name: node-exporter
    static_configs:
      - targets: ["node-exporter:9100"]

  - job_name: docker
    static_configs:
      - targets: ["host.docker.internal:9323"]

Docker 데몬 메트릭 활성화

JSON
// /etc/docker/daemon.json
{
  "metrics-addr": "0.0.0.0:9323",
  "experimental": true
}

로그 관리

로그 로테이션 설정

기본 json-file 드라이버는 크기 제한이 없어 디스크를 소진할 수 있습니다.

BASH
# 컨테이너별 설정
docker run \
    --log-opt max-size=10m \
    --log-opt max-file=3 \
    myapp
JSON
// /etc/docker/daemon.json — 전역 설정
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
YAML
# compose.yaml
services:
  api:
    image: myapi:latest
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

로그 드라이버 종류

드라이버설명docker logs
json-fileJSON 파일 (기본)O
local최적화된 로컬 파일O
syslogSyslog 서버로 전송X
journaldsystemd journalO
fluentdFluentd로 전송X
awslogsCloudWatch LogsX
none로그 비활성화X

로그 크기 확인

BASH
# 컨테이너별 로그 파일 크기 확인
ls -lh /var/lib/docker/containers/*/

성능 튜닝 체크리스트

CPU

  • --cpus로 CPU 시간 제한 설정
  • 우선순위가 다른 서비스는 --cpu-shares로 가중치 설정
  • NUMA 환경에서는 --cpuset-cpus로 코어 고정

메모리

  • --memory로 하드 제한 설정
  • --memory-swap으로 스왑 제한 (같은 값으로 설정하면 스왑 비활성화)
  • JVM은 -XX:MaxRAMPercentage로 컨테이너 메모리의 비율 지정
  • OOM 발생 시 로그와 docker inspect 확인

I/O

  • 디스크 집약적 워크로드에 --device-read-bps, --device-write-bps 설정
  • tmpfs 활용으로 디스크 I/O 감소

로그

  • max-sizemax-file로 로그 로테이션 필수 설정
  • 프로덕션에서는 중앙 로그 시스템(ELK, Loki) 연동 고려

모니터링

  • cAdvisor + Prometheus + Grafana 구성
  • 알림 규칙 설정 (CPU > 80%, 메모리 > 90% 등)

정리

  • CPU: --cpus로 시간 제한, --cpu-shares로 상대 가중치, --cpuset-cpus로 코어 고정을 설정합니다.
  • 메모리: --memory로 하드 제한, --memory-swap으로 스왑 제어. OOMKilled가 발생하면 제한값을 재검토합니다.
  • I/O: --device-read-bps, --device-write-bps로 디스크 대역폭을 제한합니다.
  • 모니터링: cAdvisor + Prometheus + Grafana 조합이 표준입니다.
  • 로그 로테이션: max-sizemax-file 설정이 프로덕션에서 필수입니다. 설정하지 않으면 디스크가 가득 찰 수 있습니다.
댓글 로딩 중...