infra
Platform

모듈 맵

[Docker] 컨테이너 로그 유실 방지와 효율적인 실시간 로깅 전략

0 / 27 완료

펼치기
0 / 27 완료0%

Docker · 14 / 27

[Docker] 컨테이너 로그 유실 방지와 효율적인 실시간 로깅 전략

컨테이너 로그를 확인하고 로그 로테이션으로 디스크 100% 장애를 예방합니다

🚨INCIDENT ALERT
HIGH

서비스 장애가 났는데 docker logs myapp에는 아무것도 나오지 않았습니다. 애플리케이션이 stdout이 아니라 /var/log/app.log 파일에 직접 로그를 쓰고 있었기 때문입니다.

다른 서버에서는 반대로 로그가 너무 많이 쌓여 /var/lib/docker 파티션이 100%가 됐습니다. Docker 로그는 어디에서 수집되고 얼마나 보관되는지 정하지 않으면, 장애를 못 찾거나 로그 때문에 다시 장애가 납니다.

도커 로그 관리와 디버깅

서비스 장애가 발생했을 때 가장 먼저 확인하는 것이 로그입니다. Docker 로그는 컨테이너 프로세스의 stdout/stderr를 자동으로 수집하는 방식으로 동작합니다. 로그를 올바르게 확인하는 방법과, 로테이션 설정 없이 운영할 때 발생하는 디스크 100% 장애를 예방하는 방법을 학습합니다.


이번 챕터에서 배울 것

컨테이너 로그가 어디에 어떻게 저장되는지 이해하고, 디스크 과적 없이 로그를 안전하게 관리하는 방법을 익힙니다.

  • 1Docker 로그 드라이버 종류 — json-file / syslog / journald / fluentd / awslogs
  • 2docker logs 명령어 — -f (follow) / --tail / --since / --until 옵션
  • 3json-file 드라이버의 max-size / max-file 로테이션 설정
  • 4컨테이너 stdout/stderr와 12-Factor App 로그 원칙
  • 5로그가 디스크를 가득 채우는 장애 시나리오와 사전 예방
실습 환경 준비

로그 파일 직접 조회(/var/lib/docker/containers/)는 root 권한이 필요합니다. sudo 권한이 없는 환경이라면 docker logs 명령어 위주로 실습합니다.

Docker 실행 중인지 확인
docker ps
현재 기본 로그 드라이버 확인
docker info | grep 'Logging Driver'
로그 파일 위치 확인 (json-file 드라이버)
ls /var/lib/docker/containers/ 2>/dev/null | head -3
실습용 컨테이너 준비
docker pull nginx:alpine
💡개념

Docker 로그 수집 원리: stdout과 stderr

서비스가 갑자기 내려갔습니다. docker logs myapp을 실행했더니 아무것도 나오지 않습니다. 컨테이너는 분명히 실행 중인데 로그가 없습니다. 원인은 해당 앱이 /var/log/app.log 파일에 로그를 기록하고 있기 때문입니다. Docker는 stdout과 stderr로 나오는 출력만 캡처합니다. 파일에 직접 기록하는 로그는 docker logs로 볼 수 없습니다. 컨테이너 환경에서 로그를 올바르게 설계하지 않으면 장애 발생 시 원인을 추적할 수 없게 됩니다. 이 ConceptBlock에서는 Docker가 로그를 수집하는 원리와 로그 드라이버 종류를 다룹니다.

Docker 로그 수집 원리: stdout과 stderr

컨테이너 로그의 동작 원리

Docker는 컨테이너의 PID 1 프로세스(메인 프로세스)가 표준 출력(stdout)과 표준 오류(stderr)로 내보내는 내용을 자동으로 캡처합니다.

컨테이너 내부
├── PID 1: nginx 프로세스
│   ├── stdout → "192.168.1.1 - GET /index.html 200"
│   └── stderr → "error: connection reset by peer"
│
Docker 데몬
└── 두 스트림 캡처 → 로그 파일로 저장
    └── /var/lib/docker/containers/[ID]/[ID]-json.log

docker logs 명령을 실행하면 이 파일을 읽어서 출력합니다.

stdout vs 파일 로그

[stdout 로그 — docker logs로 확인 가능]
app.py에서:
  print("서버 시작됨")          # stdout
  sys.stderr.write("에러발생")   # stderr
→ docker logs my-container 로 확인 가능 ✓

[파일 로그 — docker logs로 확인 불가]
app.py에서:
  with open("/var/log/app.log", "a") as f:
    f.write("서버 시작됨")
→ docker logs my-container : (아무 출력 없음) ✗
→ docker exec my-container cat /var/log/app.log 로 확인 ✓

로그 드라이버 종류

Docker는 로그를 어떻게 저장하고 전달할지 결정하는 로그 드라이버를 제공합니다.

드라이버설명특징
json-fileJSON 형식으로 로컬 파일 저장기본값, docker logs 지원
local최적화된 바이너리 형식으로 저장json-file보다 효율적
syslog시스템 syslog로 전달중앙 집중식 로깅
journaldsystemd journal로 전달docker logs 지원
fluentdFluentd 로그 수집기로 전달대규모 로그 파이프라인
none로그 비활성화성능 최우선 시
로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/docker/part4/exam_14 && cd /tmp/docker/part4/exam_14

# 기본 로그 드라이버 확인
docker info --format '{{.LoggingDriver}}'
# json-file (기본값)

# 특정 드라이버로 컨테이너 실행
docker run --log-driver local nginx

로그 파일 위치

로컬 터미널
# 로그 파일이 실제로 저장된 위치 확인
docker inspect my-container --format '{{.LogPath}}'
# /var/lib/docker/containers/abc123.../abc123...-json.log

# 직접 파일 내용 확인 (root 권한 필요)
sudo cat /var/lib/docker/containers/abc123.../abc123...-json.log
# {"log":"서버 시작됨\n","stream":"stdout","time":"2024-01-01T00:00:00Z"}

💡개념

로그 로테이션과 디스크 장애 예방

서비스가 안정적으로 잘 돌아가고 있는데 어느 날 새벽 모든 컨테이너가 한꺼번에 내려갑니다. 원인은 디스크 풀(full)입니다. docker logs도 느려지고 컨테이너를 재시작해도 디스크가 이미 꽉 찬 상태입니다. 조사해보면 특정 서비스의 로그 파일이 수십 GB에 달합니다. Docker의 기본 로그 드라이버(json-file)는 파일 크기 제한이 없어서 로테이션을 설정하지 않으면 이런 상황이 발생합니다. 트래픽이 많지 않은 서비스도 몇 달이면 디스크를 채울 수 있습니다. 로그 로테이션은 선택이 아니라 운영 필수 설정입니다. 이 ConceptBlock에서는 Docker 로그 파일이 쌓이는 구조와 로테이션 설정 방법을 다룹니다.

로그 로테이션과 디스크 장애 예방

로그 로테이션 없이 운영 시 발생하는 문제

Docker의 기본 설정에서는 로그 파일 크기 제한이 없습니다. 트래픽이 많은 서비스를 오랫동안 운영하면 로그 파일이 수십 GB로 커져 디스크를 가득 채울 수 있습니다.

로컬 터미널
# 로테이션 없는 컨테이너의 로그 파일 크기 확인
sudo du -sh /var/lib/docker/containers/*/
# 52G  /var/lib/docker/containers/abc123.../  ← 장애!

# 전체 도커 데이터 디스크 사용량
docker system df
# TYPE      TOTAL    ACTIVE   SIZE      RECLAIMABLE
# Containers  5      3        52.3GB    12.1GB

디스크가 100%가 되면 새 로그를 기록하지 못하는 것은 물론, 컨테이너가 새 프로세스를 생성하지 못해 서비스 전체가 다운될 수 있습니다.

로그 로테이션 설정 방법

방법 1: 컨테이너 실행 시 개별 설정

Docker
docker run \
  --log-driver json-file \
  --log-opt max-size=10m \
  --log-opt max-file=3 \
  nginx

max-size=10m: 로그 파일이 10MB에 도달하면 새 파일 생성 max-file=3: 최대 3개 파일 유지 → 최대 30MB

방법 2: Docker 데몬 전역 설정 (권장)

모든 컨테이너에 기본 로테이션이 적용됩니다.

JSON
// /etc/docker/daemon.json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
서버 터미널
# 설정 적용
sudo systemctl restart docker

방법 3: Docker Compose에서 설정

YAML
services:
  web:
    image: nginx
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  api:
    image: my-api
    logging:
      driver: "json-file"
      options:
        max-size: "50m"   # API는 로그가 많으므로 크게
        max-file: "5"

로테이션 동작 시각화

시간 흐름 →
파일1: [0MB → 10MB] → 순환
파일2:               [0MB → 10MB] → 순환
파일3:                             [0MB → 10MB] → 순환
파일1:                                           [0MB → ...] (파일1 재사용, 이전 내용 삭제)

최대 유지되는 로그: 파일1 + 파일2 + 파일3 = 30MB

docker logs 핵심 옵션 실습

docker logs 명령의 다양한 옵션을 실제로 사용해봅니다.

1단계: 로그를 생성하는 테스트 컨테이너 실행

실습 전 디렉토리와 예제 파일을 먼저 준비합니다.

로컬 터미널
# 실습 디렉토리 준비
mkdir -p /tmp/docker/part2/exam_4 && cd /tmp/docker/part2/exam_4

# 로그 테스트용 docker-compose.yml 생성
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
  app:
    image: nginx:alpine
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
EOF

이제 실습을 진행합니다.

Docker
# 1초마다 타임스탬프와 함께 로그를 출력하는 컨테이너
docker run -d --name log-demo alpine sh -c \
  "while true; do echo \"\$(date '+%Y-%m-%d %H:%M:%S') 서비스 정상 동작 중\"; sleep 1; done"

2단계: 기본 로그 확인

로컬 터미널
# 전체 로그 출력
docker logs log-demo

# 실시간 로그 스트리밍 (Ctrl+C로 종료)
docker logs -f log-demo

3단계: 최근 N줄만 보기

로컬 터미널
# 최근 5줄만 출력
docker logs --tail 5 log-demo

# 최근 5줄부터 실시간 스트리밍
docker logs -f --tail 5 log-demo

4단계: 시간 기반 필터링

로컬 터미널
# 지난 30초 동안의 로그
docker logs --since 30s log-demo

# 특정 시각 이후 로그
docker logs --since "2024-01-01T12:00:00" log-demo

# 특정 시각 이전 로그 (until)
docker logs --until "2024-01-01T12:05:00" log-demo

# 시간 범위 지정
docker logs --since 2m --until 1m log-demo

5단계: 타임스탬프 포함 출력

로컬 터미널
# Docker가 캡처한 시각의 타임스탬프 포함
docker logs -t log-demo
# 2024-01-01T12:00:01.123456789Z 2024-01-01 12:00:01 서비스 정상 동작 중

# 실시간 + 타임스탬프 + 최근 10줄
docker logs -f -t --tail 10 log-demo

6단계: stdout/stderr 분리 확인

Docker
# stdout과 stderr 모두 출력하는 컨테이너
docker run -d --name mixed-log alpine sh -c \
  "while true; do
    echo 'stdout: 정상 처리'
    echo 'stderr: 경고 발생' >&2
    sleep 2
  done"

# 기본은 stdout + stderr 합쳐서 출력
docker logs mixed-log

위험 명령어실습 컨테이너와 컨테이너 로그가 즉시 삭제됩니다.

로그 실습 컨테이너 강제 삭제

안전한 실행 조건: docker logs 옵션 확인을 끝낸 뒤 실습 컨테이너를 정리하는 경우에만 실행합니다.

실행 전 반드시 확인

  • docker logs로 필요한 로그 확인을 마쳤다
  • 삭제 대상이 log-demo와 mixed-log임을 확인했다
  • 보존해야 할 로그가 없음을 확인했다
docker rm -f log-demo mixed-log

위 항목을 모두 확인한 후 복사할 수 있습니다

🔍실행 후 확인할 것
  • docker logs log-demo 출력에서 1초마다 생성된 로그가 보이는가?
  • docker logs -f --tail 5 log-demo가 최근 5줄부터 실시간으로 이어지는가?
  • docker logs mixed-log 출력에서 stdout과 stderr가 함께 보이는가?
  • 정리 후 docker ps -a에서 log-demo와 mixed-log가 남아있지 않은가?

로그 로테이션 설정과 효과 확인

로그 로테이션을 설정하고 실제로 파일이 순환되는 것을 확인합니다.

1단계: 로테이션 없는 컨테이너의 로그 파일 확인

Docker
# 로테이션 없이 실행
docker run -d --name no-rotate nginx:alpine

# 로그 파일 경로 확인
LOG_PATH=$(docker inspect no-rotate --format '{{.LogPath}}')
echo "로그 파일: $LOG_PATH"

# 현재 크기 확인
sudo ls -lh "$LOG_PATH"
# 처음엔 매우 작지만 트래픽이 쌓이면 무한정 커짐

2단계: 로테이션 설정한 컨테이너 실행

Docker
docker run -d --name with-rotate \
  --log-driver json-file \
  --log-opt max-size=1m \
  --log-opt max-file=3 \
  alpine sh -c "while true; do
    # 빠르게 로그를 생성해 로테이션 유발
    for i in \$(seq 1 100); do
      echo \"로그 데이터: \$(date) - \$(cat /dev/urandom | head -c 100 | base64)\"
    done
  done"

3단계: 로그 파일 로테이션 관찰

로컬 터미널
# 로그 파일 디렉토리 감시
LOG_DIR=$(dirname $(docker inspect with-rotate --format '{{.LogPath}}'))
sudo watch -n 1 "ls -lh $LOG_DIR/"

# 처음: with-rotate-json.log (단일 파일)
# 1MB 초과 후: with-rotate-json.log + with-rotate-json.log.1
# 다시 초과: ...log + ...log.1 + ...log.2
# 3개 유지: 가장 오래된 파일 자동 삭제

4단계: docker logs는 계속 동작함 확인

로컬 터미널
# 로테이션 후에도 docker logs는 최신 로그를 정상 출력
docker logs --tail 5 with-rotate

5단계: 정리

위험 명령어컨테이너와 해당 컨테이너 로그 파일이 삭제됩니다.

로그 로테이션 실습 컨테이너 강제 삭제

안전한 실행 조건: 로그 로테이션 관찰을 끝낸 뒤 실습 컨테이너를 정리할 때만 실행합니다.

실행 전 반드시 확인

  • 로그 파일 로테이션 결과를 확인했다
  • docker logs --tail 5 with-rotate 확인을 마쳤다
  • 삭제 대상이 실습 컨테이너뿐임을 확인했다
docker rm -f no-rotate with-rotate

위 항목을 모두 확인한 후 복사할 수 있습니다


컨테이너 내부 파일 로그 확인 방법

애플리케이션이 파일에 직접 로그를 기록할 때 접근하는 방법을 익힙니다.

1단계: 파일에 로그를 기록하는 컨테이너 시뮬레이션

Docker
# /var/log/app.log 파일에 직접 기록하는 컨테이너
docker run -d --name file-logger alpine sh -c \
  "while true; do
    echo \"\$(date '+%Y-%m-%d %H:%M:%S') [INFO] 주문 처리 완료 ID=\$(shuf -i 1000-9999 -n 1)\" >> /var/log/app.log
    sleep 1
  done"

2단계: docker logs로 확인 시도 (실패)

로컬 터미널
docker logs file-logger
# (아무 출력 없음) — 파일에 기록했으므로 docker logs에 안 잡힘

3단계: exec로 컨테이너 내부에서 확인

Docker
# 최근 10줄 확인
docker exec file-logger tail -10 /var/log/app.log

# 실시간 파일 로그 스트리밍
docker exec file-logger tail -f /var/log/app.log

4단계: 로그 파일 호스트로 복사

로컬 터미널
# 분석을 위해 호스트로 복사
docker cp file-logger:/var/log/app.log ./app-logs-backup.log
cat ./app-logs-backup.log | head -20

5단계: 근본 해결 — stdout으로 심볼릭 링크

일부 컨테이너 이미지는 로그 파일을 stdout으로 리디렉션해 이 문제를 해결합니다.

Docker
# nginx 공식 이미지가 사용하는 방식 확인
docker run --rm nginx:alpine cat /etc/nginx/nginx.conf | grep access_log
# access_log  /dev/stdout main;  ← stdout으로 리디렉션!
# error_log   /dev/stderr notice; ← stderr로 리디렉션!

6단계: 정리

로컬 터미널
rm -f ./app-logs-backup.log
위험 명령어컨테이너 내부 /var/log/app.log 원본이 삭제됩니다.

파일 로그 실습 컨테이너 강제 삭제

안전한 실행 조건: 필요한 로그를 호스트로 복사했거나 로그 보존이 필요 없는 실습 상황에서만 실행합니다.

실행 전 반드시 확인

  • docker cp로 필요한 로그 백업을 완료했다
  • 삭제 대상이 file-logger인지 확인했다
  • 로컬 백업 파일 삭제 여부를 별도로 판단했다
docker rm -f file-logger

위 항목을 모두 확인한 후 복사할 수 있습니다


증상

컨테이너가 실행 중인데 docker logs를 실행해도 아무 내용이 나오지 않습니다.

로컬 터미널
docker logs my-java-app
# (출력 없음)

docker ps
# my-java-app   Up 2 hours   ← 분명히 실행 중인데...

원인 진단

원인 1: 애플리케이션이 파일에 로그를 기록

Docker
# 컨테이너 내부에 로그 파일이 있는지 확인
docker exec my-java-app find /var/log -name "*.log" 2>/dev/null
# /var/log/application/app.log ← 여기에 기록 중

# 파일 로그 확인
docker exec my-java-app tail -50 /var/log/application/app.log

원인 2: 로그 드라이버가 none으로 설정됨

로컬 터미널
docker inspect my-java-app --format '{{.HostConfig.LogConfig.Type}}'
# none ← 로그 드라이버 비활성화 상태

원인 3: 로그 버퍼링 (Python/Java에서 흔함)

Docker
# Python은 기본적으로 stdout을 버퍼링
# 버퍼가 가득 차기 전까지 docker logs에 나타나지 않음

# 해결: 환경변수로 버퍼링 비활성화
docker run -e PYTHONUNBUFFERED=1 my-python-app
# 또는
docker run my-python-app python -u app.py  # -u: unbuffered

해결 방법

로그를 stdout으로 출력하도록 애플리케이션 수정

Python
# Python: 파일 대신 stdout으로 출력
import sys
print("로그 메시지", file=sys.stdout, flush=True)
Java
// Java/Spring Boot: application.properties
logging.file.name=  # 파일 로깅 비활성화
# 콘솔 출력은 자동으로 stdout으로 전달됨
로컬 터미널
# Nginx: /dev/stdout으로 심볼릭 링크
ln -sf /dev/stdout /var/log/nginx/access.log
ln -sf /dev/stderr /var/log/nginx/error.log

증상

로컬 터미널
df -h
# Filesystem      Size  Used Avail Use% Mounted on
# /dev/sda1        50G   50G     0  100% /  ← 재앙!

docker ps
# ERROR: Cannot connect to Docker daemon — 도커도 동작 중단!

즉각 대응 (디스크 공간 확보)

1. 로그 파일 크기 확인

로컬 터미널
sudo du -sh /var/lib/docker/containers/*/
# 45G  /var/lib/docker/containers/abc123.../  ← 원인 발견!

# 해당 컨테이너 이름 확인
sudo docker inspect abc123... --format '{{.Name}}'

2. 긴급 로그 파일 비우기 (컨테이너 중단 없이)

로컬 터미널
# ⚠️ rm이 아니라 truncate로! rm하면 프로세스가 파일 핸들 유지해 공간 안 줄어듦
sudo truncate -s 0 /var/lib/docker/containers/abc123.../abc123...-json.log

# 공간 확인
df -h
# 공간 확보됨!

3. 사용하지 않는 Docker 리소스 정리

위험 명령어--volumes 포함 시 미사용 볼륨 데이터까지 영구 삭제됩니다.

로그 장애 대응 중 Docker 리소스 정리

안전한 실행 조건: 디스크 장애 대응 중이며 삭제 대상 리소스와 볼륨을 확인한 경우에만 실행합니다.

실행 전 반드시 확인

  • sudo du 또는 docker system df로 디스크 원인을 확인했다
  • docker volume ls로 보존할 볼륨이 없는지 확인했다
  • 먼저 truncate와 로그 로테이션 설정을 검토했다
docker system prune -f --volumes

위 항목을 모두 확인한 후 복사할 수 있습니다

재발 방지 설정

JSON
// /etc/docker/daemon.json — 즉시 적용
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
서버 터미널
sudo systemctl restart docker
# 이후 모든 새 컨테이너에 로테이션 자동 적용

기존 실행 중인 컨테이너에 적용하려면: 컨테이너를 재생성해야 합니다. 로그 드라이버 설정은 컨테이너 생성 시에만 적용됩니다.


💼
실무 맥락
현업 패턴

소규모 서비스는 docker logs로 충분하지만, 여러 서버에 분산된 마이크로서비스 환경에서는 중앙 집중식 로그 관리 시스템이 필수입니다.

실무 로그 파이프라인 아키텍처

[컨테이너들]
├── web-service    (stdout/stderr)
├── api-service    (stdout/stderr)
└── order-service  (stdout/stderr)
        ↓
[로그 수집기]
├── Fluentd/Promtail (컨테이너 로그 수집)
        ↓
[로그 저장소]
├── Elasticsearch + Kibana (ELK Stack)
└── Loki + Grafana (경량화 대안)

Docker Compose에서 Loki 로그 드라이버 사용

YAML
version: "3.9"

services:
  app:
    image: my-app
    logging:
      driver: loki      # Loki로 직접 전송
      options:
        loki-url: "http://loki:3100/loki/api/v1/push"
        loki-batch-size: "400"
        loki-retries: "5"
        labels: "service=app,env=production"

  loki:
    image: grafana/loki:latest
    ports:
      - "3100:3100"

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"

현업에서의 로그 레벨 전략

DEBUG  → 개발 환경에서만 (stdout)
INFO   → 주요 이벤트, 운영에서 항상 수집
WARN   → 주의 필요, 알림 연동 고려
ERROR  → 즉시 알림 발송 (Slack/PagerDuty)
FATAL  → 서비스 중단 수준, 온콜 호출

이 플랫폼의 e-commerce 프로젝트에서는 각 언어별 로깅 라이브러리(Winston, Structlog, Logrus, SLF4j)가 모두 JSON 형식으로 stdout에 출력하도록 설정되어 있어, Promtail이 자동으로 수집해 Loki로 전달하는 구조입니다. 실무 DevOps 엔지니어는 모든 서비스의 로그를 단일 대시보드(Grafana)에서 통합 조회하고, 에러 패턴을 알림으로 설정합니다.


💡개념

구조적 로그(Structured Logging)와 멀티라인 처리

구조적 로그(Structured Logging)와 멀티라인 처리

왜 구조적 로그인가?

# 비구조적 로그 (텍스트 파싱이 어려움)
2026-04-05 10:30:15 ERROR User login failed: invalid password for user john

# 구조적 로그 (JSON — 필드별 검색/집계 가능)
{"time":"2026-04-05T10:30:15Z","level":"ERROR","event":"login_failed",
 "user":"john","reason":"invalid_password","ip":"192.168.1.100","latency_ms":5}

구조적 로그는 Elasticsearch, Loki 같은 로그 저장소에서 user=john AND event=login_failed 같은 쿼리로 빠르게 검색할 수 있습니다.

앱에서 구조적 로그 출력하기

Python
# Python — structlog 라이브러리
import structlog
log = structlog.get_logger()
log.error("login_failed", user="john", reason="invalid_password", ip="1.2.3.4")
# {"event": "login_failed", "user": "john", "reason": "invalid_password", ...}
JS
// Node.js — pino 라이브러리 (고성능)
const pino = require('pino');
const log = pino();
log.error({ user: 'john', reason: 'invalid_password' }, 'login failed');
// {"level":50,"time":1712300000000,"user":"john","reason":"invalid_password","msg":"login failed"}
Java
// Java — logback JSON 레이아웃
// logback.xml에서 JsonLayout 설정
// 모든 로그가 자동으로 JSON으로 출력됨
logger.error("login failed", kv("user", "john"), kv("reason", "invalid_password"));

멀티라인 로그 문제 (스택 트레이스)

Docker의 json-file 드라이버는 줄 단위로 로그를 저장합니다. Java 스택 트레이스처럼 여러 줄인 경우 각 줄이 별개 이벤트로 분리됩니다.

로컬 터미널
# 실제 저장된 로그 파일 확인
sudo cat /var/lib/docker/containers/<id>/<id>-json.log | head -5
# {"log":"Exception in thread \"main\"\n","stream":"stderr","time":"..."}
# {"log":"\tat com.example.App.main(App.java:42)\n","stream":"stderr","time":"..."}
# {"log":"\tat com.example.App.run(App.java:28)\n","stream":"stderr","time":"..."}
# ← 스택 트레이스가 3개의 별개 이벤트로 분리됨!

멀티라인 처리 방법

YAML
# 방법 2: Promtail (Loki 수집기) 멀티라인 파서 설정
scrape_configs:
  - job_name: docker
    pipeline_stages:
      - multiline:
          firstline: '^\d{4}-\d{2}-\d{2}'  # 날짜로 시작하는 줄이 첫 줄
          max_wait_time: 3s

로그 포맷 권장 사항

상황권장 방식
단순 개발/디버깅텍스트 로그 (가독성 우선)
운영 환경 (로그 집중화 예정)JSON 구조적 로그
멀티라인 예외가 많은 JavaJSON + 스택 트레이스 직렬화
고성능 필요binary format (Loki native, OpenTelemetry)

💡개념

로그 중앙화 — 여러 컨테이너 로그를 한 곳에서 보기

로그 중앙화 — 여러 컨테이너 로그를 한 곳에서 보기

왜 중앙화가 필요한가

컨테이너가 5개일 때는 docker logs로 하나씩 확인할 수 있습니다. 하지만 수십 개의 컨테이너가 있다면 docker logs로는 불가능합니다.

중앙화 전: 컨테이너마다 docker logs 명령 실행
  → 로그가 각 서버에 분산
  → 서버 장애 시 로그 유실

중앙화 후: 모든 로그를 한 곳에 수집
  → 전체 서비스 로그를 한 화면에서 검색/분석
  → 장기 보관 및 알람 연동

주요 스택 선택

스택수집기저장소시각화특징
ELKFilebeat/LogstashElasticsearchKibana강력한 검색, 무거움
PLG (Grafana)PromtailLokiGrafana경량, Prometheus와 통합
클라우드 AWSCloudWatch AgentCloudWatchConsole관리형, 비용 발생
클라우드 GCPOps AgentCloud LoggingConsole관리형, 비용 발생

Docker 로그 드라이버로 직접 전송

YAML
# docker-compose.yml
services:
  app:
    image: myapp
    logging:
      driver: "json-file"          # 기본 (로컬 저장 + docker logs 지원)
      options:
        max-size: "10m"
        max-file: "3"

  app-prod:
    image: myapp
    logging:
      driver: "awslogs"            # AWS CloudWatch로 직접 전송
      options:
        awslogs-region: "ap-northeast-2"
        awslogs-group: "/myapp/prod"
        awslogs-stream: "api-1"

Loki + Promtail 빠른 설정 (Docker Compose)

YAML
# monitoring/compose.yml
services:
  loki:
    image: grafana/loki:latest
    ports:
      - "3100:3100"
    volumes:
      - loki-data:/loki

  promtail:
    image: grafana/promtail:latest
    volumes:
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock
      - ./promtail-config.yml:/etc/promtail/config.yml

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3001:3000"
    environment:
      - GF_AUTH_ANONYMOUS_ENABLED=true
    volumes:
      - grafana-data:/var/lib/grafana

volumes:
  loki-data:
  grafana-data:
YAML
# promtail-config.yml
scrape_configs:
  - job_name: docker
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
    relabel_configs:
      - source_labels: [__meta_docker_container_name]
        target_label: container

Grafana에서 Loki 데이터소스를 추가하면 {container="myapp"} 쿼리로 모든 컨테이너 로그를 한 화면에서 검색할 수 있습니다.


정리

Docker 로그 관리의 핵심을 요약합니다.

명령/설정용도
docker logs -f [컨테이너]실시간 로그 스트리밍
docker logs --tail 100최근 100줄 출력
docker logs --since 1h지난 1시간 로그
max-size: "10m"로그 파일 최대 크기
max-file: "3"최대 파일 개수 유지

모든 컨테이너에 로그 로테이션을 설정하는 것은 운영 환경 필수 사항입니다. /etc/docker/daemon.json에 전역 설정을 추가해 새로 생성되는 모든 컨테이너에 자동 적용하세요.

다음 모듈에서는 Docker 환경에서 실행 중인 컨테이너를 관찰하고, 메트릭 기반으로 이상 징후를 찾는 모니터링 흐름을 다룹니다.

지식 확인

퀴즈 — 5문제

Q1

Docker가 컨테이너 로그를 수집하는 기본 원리는 무엇인가요?

Q2

`docker logs -f --tail 100 my-container` 명령의 의미는 무엇인가요?

Q3

로그 로테이션 설정(`max-size: 10m, max-file: 3`)의 의미는 무엇인가요?

Q4

`--since 1h` 옵션으로 로그를 필터링하면 어떤 로그가 출력되나요?

Q5

`docker logs` 명령으로 아무 출력도 안 나올 때의 가장 흔한 원인은 무엇인가요?

0 / 5 답변

🧪 실습으로 확인하기

Docker Compose 멀티 서비스 구성

초급

docker-compose.yml로 nginx + 앱 컨테이너를 함께 정의하고, 서비스 간 통신과 볼륨 마운트를 구성한다.

35📋 4단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

docker중급 · 40
[Docker] 인터넷이 안 되는 환경에서 Docker 이미지를 이전하는 Save & Load 기법
Docker 트랙 계속
networking입문 · 45
[Network] OSI 7계층과 TCP/IP 4계층 모델 실무적 관점 분석
Networking 트랙 시작점