Docker 성능 튜닝 — CPU, 메모리, IO 리소스 제한과 모니터링
컨테이너에 리소스 제한을 걸지 않으면 하나의 컨테이너가 호스트의 모든 CPU와 메모리를 독점할 수 있습니다. 프로덕션에서 여러 컨테이너가 안정적으로 공존하려면 어떻게 리소스를 관리해야 할까요?
CPU 리소스 제한
--cpus: CPU 시간 제한
# CPU 시간의 150% (1.5코어 분량)까지 사용 가능
docker run --cpus 1.5 myapp
# 쿼드코어 시스템에서 최대
docker run --cpus 4.0 myapp
내부적으로 cgroups의 cpu.cfs_quota_us와 cpu.cfs_period_us로 구현됩니다.
# --cpus 1.5는 다음과 동일
docker run --cpu-period=100000 --cpu-quota=150000 myapp
# 100ms 주기 중 150ms 사용 가능 (1.5코어 분량)
--cpu-shares: 상대적 가중치
# 기본값 1024
docker run --cpu-shares 512 low-priority-app
docker run --cpu-shares 2048 high-priority-app
CPU 경합이 발생할 때만 의미가 있습니다. 경합이 없으면 모든 컨테이너가 CPU를 자유롭게 사용합니다.
CPU 경합 시 배분:
low-priority: 512/(512+2048) = 20%
high-priority: 2048/(512+2048) = 80%
--cpuset-cpus: 특정 코어 할당
# CPU 코어 0번과 2번에서만 실행
docker run --cpuset-cpus "0,2" myapp
# CPU 코어 0~3번 사용
docker run --cpuset-cpus "0-3" myapp
NUMA 아키텍처에서 메모리와 CPU의 지역성을 최적화할 때 사용합니다.
메모리 리소스 제한
--memory: 메모리 상한
# 512MB 메모리 제한
docker run --memory 512m myapp
# 단위: b, k, m, g
docker run --memory 1g myapp
--memory-swap: 메모리+스왑 합계
# 메모리 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: 소프트 제한
# 소프트 제한 256MB, 하드 제한 512MB
docker run --memory 512m --memory-reservation 256m myapp
호스트 메모리가 부족할 때 reservation 값까지 메모리를 회수합니다. 하드 제한은 절대 넘을 수 없습니다.
OOM 동작 제어
# OOM Killer 비활성화 (권장하지 않음)
docker run --oom-kill-disable --memory 512m myapp
# OOM 점수 조정 (-1000 ~ 1000, 높을수록 먼저 kill)
docker run --oom-score-adj 500 myapp
JVM 메모리 설정
Java 애플리케이션은 JVM 힙 메모리를 별도로 설정해야 합니다.
# 컨테이너 메모리 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 대역폭 제한
# 디바이스 읽기 속도 제한 (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 가중치
# 블록 I/O 가중치 (10~1000, 기본 500)
docker run --blkio-weight 100 low-io-app
docker run --blkio-weight 900 high-io-app
Docker Compose에서의 리소스 제한
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
# 모든 컨테이너의 실시간 리소스 사용량
docker stats
# 커스텀 포맷
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}"
cAdvisor (Container Advisor)
Google이 개발한 컨테이너 모니터링 도구입니다.
# 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
# cAdvisor 접속
curl http://localhost:8081/metrics # Prometheus 형식 메트릭
# 웹 UI
# http://localhost:8081
Prometheus + Grafana 연동
# 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:
# 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 데몬 메트릭 활성화
// /etc/docker/daemon.json
{
"metrics-addr": "0.0.0.0:9323",
"experimental": true
}
로그 관리
로그 로테이션 설정
기본 json-file 드라이버는 크기 제한이 없어 디스크를 소진할 수 있습니다.
# 컨테이너별 설정
docker run \
--log-opt max-size=10m \
--log-opt max-file=3 \
myapp
// /etc/docker/daemon.json — 전역 설정
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
# compose.yaml
services:
api:
image: myapi:latest
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
로그 드라이버 종류
| 드라이버 | 설명 | docker logs |
|---|---|---|
| json-file | JSON 파일 (기본) | O |
| local | 최적화된 로컬 파일 | O |
| syslog | Syslog 서버로 전송 | X |
| journald | systemd journal | O |
| fluentd | Fluentd로 전송 | X |
| awslogs | CloudWatch Logs | X |
| none | 로그 비활성화 | X |
로그 크기 확인
# 컨테이너별 로그 파일 크기 확인
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-size와max-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-size와max-file설정이 프로덕션에서 필수입니다. 설정하지 않으면 디스크가 가득 찰 수 있습니다.
댓글 로딩 중...