모니터링 실전 가이드

모니터링의 핵심 개념과 실무 활용

JavaScript중급
8시간
4개 항목
학습 진행률0 / 4 (0%)

학습 항목

1. Docker
Docker|로깅|모니터링|완벽|가이드
퀴즈튜토리얼
2. JavaScript
고급
Sentry|베스트|프랙티스|에러|모니터링
퀴즈튜토리얼
3. JavaScript
고급
Sentry|에러|모니터링|베스트|프랙티스
퀴즈튜토리얼
4. TypeScript
Kubernetes|Prometheus|모니터링|완벽|가이드
퀴즈튜토리얼
1 / 4

이미지 로딩 중...

Docker 로깅 모니터링 완벽 가이드 - 슬라이드 1/13

Docker 로깅 모니터링 완벽 가이드

Docker 컨테이너의 로깅과 모니터링을 체계적으로 관리하는 방법을 다룹니다. 실무에서 바로 적용할 수 있는 로그 드라이버 설정, 중앙 집중식 로깅 시스템 구축, 컨테이너 메트릭 수집 방법을 학습합니다.


목차

  1. Docker 로그 드라이버
  2. JSON File 로깅
  3. Syslog 드라이버
  4. Fluentd 통합
  5. 컨테이너 메트릭 수집
  6. Prometheus 연동
  7. Grafana 대시보드
  8. 로그 로테이션
  9. 멀티 컨테이너 로깅
  10. 프로덕션 모니터링 전략

1. Docker 로그 드라이버

시작하며

여러분이 프로덕션 환경에서 여러 개의 Docker 컨테이너를 운영하고 있다고 상상해보세요. 갑자기 서비스에 문제가 발생했는데, 어떤 컨테이너에서 에러가 발생했는지 찾기 위해 각 컨테이너마다 일일이 docker logs를 실행해야 한다면 얼마나 답답할까요?

이런 문제는 컨테이너 기반 인프라가 확장될수록 더욱 심각해집니다. 컨테이너가 재시작되거나 삭제되면 로그도 함께 사라져버리고, 여러 서버에 분산된 컨테이너들의 로그를 추적하기는 거의 불가능에 가깝습니다.

바로 이럴 때 필요한 것이 Docker 로그 드라이버입니다. 로그 드라이버는 컨테이너의 표준 출력(stdout)과 표준 에러(stderr)를 다양한 방식으로 수집하고 저장할 수 있게 해줍니다.

개요

간단히 말해서, Docker 로그 드라이버는 컨테이너가 생성하는 로그를 어디에, 어떻게 저장할지 결정하는 플러그인 시스템입니다. 실무에서는 기본 설정만으로는 부족합니다.

예를 들어, 마이크로서비스 아키텍처에서 50개 이상의 컨테이너가 동시에 실행되는 환경이라면, 모든 로그를 중앙 집중식으로 수집하고 검색할 수 있어야 장애 대응이 가능합니다. 기존에는 각 서버에 SSH로 접속해서 로그 파일을 직접 확인했다면, 이제는 로그 드라이버를 통해 Elasticsearch나 CloudWatch 같은 중앙 로깅 시스템으로 자동으로 전송할 수 있습니다.

Docker는 json-file, syslog, journald, fluentd, gelf, awslogs, splunk 등 다양한 로그 드라이버를 제공합니다. 각 드라이버는 로그 저장 위치, 포맷, 전송 프로토콜이 다르며, 여러분의 인프라 환경에 맞게 선택할 수 있습니다.

올바른 로그 드라이버 선택은 시스템의 관찰 가능성(observability)을 결정짓는 핵심 요소입니다.

코드 예제

# docker-compose.yml에서 로그 드라이버 설정
version: '3.8'
services:
  web:
    image: nginx:latest
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
        labels: "production,web-server"
        env: "ENVIRONMENT,SERVICE_NAME"
    environment:
      - ENVIRONMENT=production
      - SERVICE_NAME=nginx-web

설명

이것이 하는 일: Docker Compose 파일에서 컨테이너별 로깅 전략을 선언적으로 정의하여, 애플리케이션이 생성하는 모든 로그를 체계적으로 관리합니다. 첫 번째로, logging.driver 필드에서 사용할 로그 드라이버를 지정합니다.

여기서는 json-file을 선택했는데, 이는 Docker의 기본 드라이버로 로그를 JSON 형태로 호스트의 파일 시스템에 저장합니다. 이렇게 하는 이유는 구조화된 데이터로 저장되어 나중에 파싱하고 검색하기 쉽기 때문입니다.

그 다음으로, options 섹션이 실행되면서 로그 파일의 크기와 개수를 제한합니다. max-size: "10m"는 각 로그 파일이 10MB에 도달하면 자동으로 로테이션되도록 하고, max-file: "3"은 최대 3개의 로그 파일만 보관하도록 설정합니다.

이는 디스크 공간이 무한정 사용되는 것을 방지하는 핵심 메커니즘입니다. 마지막으로, labelsenv 옵션이 로그에 메타데이터를 추가하여 최종적으로 검색 가능한 컨텍스트를 만들어냅니다.

예를 들어, 수백 개의 컨테이너 중에서 "production 환경의 web-server"만 필터링해서 볼 수 있게 됩니다. 여러분이 이 코드를 사용하면 로그가 자동으로 관리되고, 디스크 공간 문제를 예방하며, 로그 검색 시 필요한 컨텍스트를 즉시 확인할 수 있습니다.

또한 프로덕션 환경에서 장애 발생 시 특정 서비스나 환경의 로그만 빠르게 필터링하여 원인을 파악할 수 있습니다.

실전 팁

💡 기본 json-file 드라이버를 사용할 때는 반드시 max-sizemax-file을 설정하세요. 설정하지 않으면 로그가 무한정 쌓여서 디스크가 가득 차는 심각한 문제가 발생합니다.

💡 프로덕션 환경에서는 컨테이너가 재시작되어도 로그가 유지되도록 외부 로깅 시스템(Fluentd, ELK, CloudWatch)을 사용하는 것이 좋습니다. json-file은 컨테이너가 삭제되면 로그도 함께 사라집니다.

💡 로그 드라이버는 런타임 성능에 영향을 줄 수 있습니다. 네트워크로 로그를 전송하는 드라이버(syslog, fluentd)는 비동기 모드를 사용하거나 버퍼링을 설정하여 애플리케이션 성능 저하를 방지하세요.

💡 개발 환경에서는 docker logs 명령으로 실시간 로그를 확인하고, 프로덕션에서는 중앙 집중식 로깅으로 전환하세요. 환경별로 다른 로그 드라이버를 사용하는 것이 실전 베스트 프랙티스입니다.

💡 labels와 env 옵션을 활용해서 로그에 서비스명, 환경, 버전 정보를 태깅하면 멀티테넌트 환경에서 로그 추적이 월등히 쉬워집니다.


2. JSON File 로깅

시작하며

여러분이 Docker 컨테이너를 처음 실행하고 docker logs container_name을 입력했을 때, 로그가 즉시 출력되는 것을 보셨을 겁니다. 하지만 이 로그들이 실제로 어디에 저장되고, 얼마나 오래 보관되는지 생각해보신 적 있나요?

이런 질문은 개발 초기에는 중요하지 않아 보이지만, 서비스가 커지면서 심각한 문제로 이어집니다. 어느 날 서버의 디스크 사용량이 100%에 도달하여 서비스가 다운되는 상황을 겪게 되고, 원인을 추적해보니 컨테이너 로그 파일이 수십 GB씩 쌓여있는 것을 발견하게 됩니다.

바로 이럴 때 필요한 것이 JSON File 로깅 전략입니다. Docker의 기본 로그 메커니즘을 제대로 이해하고 설정하면 안정적이고 예측 가능한 로깅 환경을 만들 수 있습니다.

개요

간단히 말해서, JSON File 로깅은 Docker가 기본으로 사용하는 로그 저장 방식으로, 각 컨테이너의 stdout/stderr를 JSON 형식으로 호스트의 파일 시스템에 저장합니다. 실무에서는 로그 파일의 위치를 정확히 알고 있어야 합니다.

Docker는 기본적으로 /var/lib/docker/containers/<container-id>/<container-id>-json.log 경로에 로그를 저장하며, 이 파일은 컨테이너가 살아있는 동안 계속 증가합니다. 예를 들어, 트래픽이 많은 웹 서버 컨테이너는 하루에 수 GB의 로그를 생성할 수 있으므로, 적절한 로테이션 정책이 필수입니다.

기존에는 애플리케이션 내부에서 log4j나 Winston 같은 로깅 라이브러리로 로그 파일을 직접 관리했다면, 이제는 컨테이너 환경에서는 stdout/stderr로 로그를 출력하고 Docker가 수집하도록 하는 것이 12-Factor App 원칙에 부합하는 방식입니다. JSON File 로깅의 핵심 특징은 구조화된 데이터 저장, 자동 타임스탬프 추가, 스트림 구분(stdout/stderr)입니다.

각 로그 라인은 {"log": "...", "stream": "stdout", "time": "..."} 형태로 저장되어, 나중에 로그 분석 도구로 파싱하기 쉽습니다. 이러한 특징들이 중요한 이유는 장애 발생 시 정확한 시점의 로그를 빠르게 찾을 수 있기 때문입니다.

코드 예제

# Docker 데몬 전역 설정 (/etc/docker/daemon.json)
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "5",
    "compress": "true",
    "labels": "com.docker.compose.service",
    "env": "ENVIRONMENT"
  }
}

# 설정 적용 후 Docker 재시작
# sudo systemctl restart docker

설명

이것이 하는 일: Docker 데몬의 전역 설정 파일을 통해 모든 컨테이너에 일관된 로깅 정책을 적용하여, 시스템 전체의 로그 관리를 자동화합니다. 첫 번째로, log-driver를 명시적으로 json-file로 설정합니다.

비록 기본값이지만 명시적으로 선언하는 것이 설정의 의도를 분명히 하고, 팀원들이 인프라 코드를 읽을 때 혼란을 줄여줍니다. 이렇게 하는 이유는 나중에 다른 로그 드라이버로 마이그레이션할 때 변경 지점을 명확히 파악할 수 있기 때문입니다.

그 다음으로, log-opts 섹션이 실행되면서 로그 로테이션 정책이 적용됩니다. max-size: "10m"은 각 로그 파일이 10MB에 도달하면 새 파일로 로테이션하고, max-file: "5"는 최대 5개의 백업 파일을 유지합니다.

즉, 컨테이너당 최대 50MB의 로그만 보관되며 오래된 로그는 자동으로 삭제됩니다. compress: "true" 옵션은 로테이션된 로그를 gzip으로 압축하여 디스크 공간을 70-80% 절약합니다.

마지막으로, labelsenv 옵션이 로그 메타데이터에 Docker 레이블과 환경변수를 포함시켜 최종적으로 로그 컨텍스트를 풍부하게 만들어냅니다. 예를 들어, Docker Compose의 서비스 이름이나 배포 환경(dev/staging/production) 정보가 각 로그 라인에 자동으로 추가됩니다.

여러분이 이 코드를 사용하면 시스템 전체에 일관된 로깅 정책이 적용되어 예측 가능한 디스크 사용량을 유지할 수 있습니다. 또한 로그 파일이 압축되어 저장 비용이 절감되고, 메타데이터가 풍부해져서 멀티 서비스 환경에서도 특정 서비스의 로그를 쉽게 필터링할 수 있습니다.

실전 팁

💡 max-size는 애플리케이션의 로그 생성량에 맞게 조정하세요. 트래픽이 많은 API 서버는 50-100MB, 배치 작업은 10-20MB가 적당합니다. 너무 작게 설정하면 로테이션이 자주 발생해서 최근 로그가 빨리 사라집니다.

💡 /etc/docker/daemon.json 파일을 수정한 후에는 반드시 Docker 데몬을 재시작해야 하며, 이미 실행 중인 컨테이너는 재시작해야 새 설정이 적용됩니다. 프로덕션 환경에서는 무중단 배포 전략과 함께 계획하세요.

💡 JSON 로그 파일은 /var/lib/docker/containers/ 디렉토리에 저장되므로, 이 파티션의 디스크 공간을 모니터링하세요. Prometheus의 node_exporter로 디스크 사용률 알람을 설정하는 것이 좋습니다.

💡 개발 환경에서는 max-file: "1"로 설정하여 최소한의 로그만 보관하고, 프로덕션에서는 max-file: "5-10"으로 설정하여 충분한 히스토리를 유지하세요. 장애 분석에는 최소 24시간의 로그가 필요합니다.

💡 docker logs --tail 100 --follow container_name 명령으로 실시간 로그를 모니터링할 수 있지만, 프로덕션에서는 Grafana Loki나 ELK 스택 같은 중앙 집중식 시스템을 사용하는 것이 확장성 면에서 유리합니다.


3. Syslog 드라이버

시작하며

여러분이 10대 이상의 서버에서 수십 개의 Docker 컨테이너를 운영하고 있다고 가정해봅시다. 장애가 발생했을 때 각 서버에 SSH로 접속해서 일일이 로그를 확인하는 것은 비효율적일 뿐만 아니라, 시간이 지나면서 로그가 사라질 수도 있습니다.

이런 문제는 분산 시스템에서 매우 흔합니다. 로그가 각 호스트에 흩어져 있으면 전체적인 시스템 동작을 파악하기 어렵고, 여러 서비스 간의 요청 흐름을 추적하는 것은 거의 불가능합니다.

특히 컨테이너가 오토스케일링으로 생성되고 삭제되는 환경에서는 더욱 심각합니다. 바로 이럴 때 필요한 것이 Syslog 드라이버입니다.

Syslog는 오랜 역사를 가진 표준 로깅 프로토콜로, 모든 컨테이너 로그를 중앙 Syslog 서버로 전송하여 통합 관리할 수 있게 해줍니다.

개요

간단히 말해서, Syslog 드라이버는 Docker 컨테이너의 로그를 Syslog 프로토콜을 사용하여 원격 로그 서버로 실시간 전송하는 기능입니다. 실무에서는 중앙 집중식 로깅이 필수입니다.

예를 들어, Kubernetes 클러스터에서 파드가 여러 노드에 분산 배포되는 경우, Syslog 서버 하나로 모든 로그를 수집하면 kubectl logs에 의존하지 않고도 로그를 영구적으로 보관하고 검색할 수 있습니다. 기존에는 Logrotate와 rsyslog를 사용해서 서버 로그를 관리했다면, 이제는 Docker 컨테이너가 직접 Syslog 서버와 통신하여 로그를 전송합니다.

이는 호스트 파일 시스템을 전혀 사용하지 않으므로 디스크 공간 문제에서 자유롭습니다. Syslog 드라이버의 핵심 특징은 네트워크 기반 전송(TCP/UDP), 표준 Syslog 포맷 지원, 실시간 스트리밍입니다.

TCP를 사용하면 로그 손실을 방지할 수 있고, UDP는 더 빠르지만 네트워크 문제 시 로그가 유실될 수 있습니다. 이러한 특징들이 중요한 이유는 규정 준수(compliance)나 감사(audit) 요구사항을 충족하는 데 필수적이기 때문입니다.

코드 예제

# docker-compose.yml에서 Syslog 드라이버 설정
version: '3.8'
services:
  app:
    image: myapp:latest
    logging:
      driver: "syslog"
      options:
        syslog-address: "tcp://syslog-server.example.com:514"
        syslog-facility: "daemon"
        tag: "{{.Name}}/{{.ID}}"
        syslog-format: "rfc5424"
        env: "SERVICE_NAME,VERSION"
    environment:
      - SERVICE_NAME=myapp
      - VERSION=1.2.3

설명

이것이 하는 일: Docker 컨테이너의 모든 stdout/stderr 출력을 네트워크를 통해 중앙 Syslog 서버로 전송하여, 분산된 컨테이너들의 로그를 통합 관리합니다. 첫 번째로, driver: "syslog"를 지정하여 JSON file 대신 Syslog 프로토콜을 사용하도록 설정합니다.

이렇게 하는 이유는 로그를 호스트에 저장하지 않고 즉시 원격 서버로 전송하여, 컨테이너가 삭제되거나 호스트가 재부팅되어도 로그가 안전하게 보존되기 때문입니다. 그 다음으로, syslog-address 옵션이 실행되면서 로그 전송 대상을 지정합니다.

tcp://syslog-server.example.com:514는 TCP 프로토콜을 사용하여 514 포트로 연결하며, TCP를 사용하면 연결이 끊어졌을 때 Docker가 재시도하여 로그 손실을 최소화합니다. tag 옵션은 각 로그 메시지에 컨테이너 이름과 ID를 포함시켜 출처를 명확히 합니다.

마지막으로, syslog-format: "rfc5424" 옵션이 현대적인 Syslog 표준 포맷을 사용하도록 하여 최종적으로 구조화된 데이터(structured data)를 지원합니다. 이는 메타데이터를 더 풍부하게 전달할 수 있게 해줍니다.

env 옵션으로 지정한 환경변수들도 Syslog 메시지에 포함되어 전송됩니다. 여러분이 이 코드를 사용하면 모든 컨테이너 로그가 중앙 서버로 자동 전송되어 단일 지점에서 검색하고 분석할 수 있습니다.

또한 로그 보관 정책을 Syslog 서버에서 통합 관리할 수 있고, 여러 환경(개발/스테이징/프로덕션)의 로그를 서로 다른 Syslog 서버나 파일로 분리하여 저장할 수 있습니다.

실전 팁

💡 TCP와 UDP 중 선택할 때는 로그의 중요도를 고려하세요. 금융 거래나 보안 이벤트 로그는 반드시 TCP를 사용하여 손실을 방지하고, 일반적인 디버그 로그는 UDP로 성능을 높일 수 있습니다.

💡 Syslog 드라이버를 사용하면 docker logs 명령이 작동하지 않습니다. 개발 환경에서는 불편할 수 있으므로, 로컬에서는 json-file을 사용하고 프로덕션에서만 syslog로 전환하는 것을 추천합니다.

💡 Syslog 서버가 다운되거나 네트워크가 불안정하면 컨테이너 성능에 영향을 줄 수 있습니다. syslog-tls-skip-verify나 버퍼링 옵션을 활용하여 장애를 격리하세요.

💡 tag 옵션에서 {{.Name}}/{{.ID}}/{{.ImageName}}처럼 Go 템플릿을 사용하면 로그에 풍부한 메타데이터를 자동으로 추가할 수 있어, Syslog 서버에서 필터링이 쉬워집니다.

💡 Syslog 서버로는 rsyslog, syslog-ng, Fluentd(in_syslog 플러그인) 등을 사용할 수 있으며, 이들을 Elasticsearch와 연동하면 강력한 로그 검색 시스템을 구축할 수 있습니다.


4. Fluentd 통합

시작하며

여러분이 마이크로서비스 아키텍처를 운영하면서 애플리케이션 로그, 액세스 로그, 시스템 메트릭을 서로 다른 저장소(Elasticsearch, S3, CloudWatch)에 보내야 하는 상황을 상상해보세요. 각 대상마다 별도의 에이전트를 설치하고 설정 파일을 관리하는 것은 복잡하고 유지보수하기 어렵습니다.

이런 문제는 데이터 파이프라인이 복잡해질수록 더욱 심각해집니다. 로그 포맷을 변환하거나, 민감 정보를 마스킹하거나, 특정 패턴의 로그만 필터링해야 할 때마다 여러 곳의 설정을 수정해야 하는 악몽 같은 상황이 발생합니다.

바로 이럴 때 필요한 것이 Fluentd 통합입니다. Fluentd는 통합 로깅 레이어로서 다양한 소스에서 데이터를 수집하고, 가공하고, 여러 목적지로 라우팅할 수 있는 강력한 데이터 파이프라인입니다.

개요

간단히 말해서, Fluentd 통합은 Docker 컨테이너의 로그를 Fluentd 데몬으로 전송하여, 로그 수집과 처리를 분리하고 유연한 데이터 파이프라인을 구축하는 방법입니다. 실무에서는 로그 처리 로직을 애플리케이션에서 분리하는 것이 중요합니다.

예를 들어, 수백 개의 마이크로서비스에서 생성되는 로그를 실시간으로 파싱하고, JSON 구조를 정규화하고, PII(개인식별정보)를 마스킹한 후 Elasticsearch와 S3에 동시에 전송해야 하는 경우, Fluentd를 사용하면 이 모든 로직을 중앙에서 관리할 수 있습니다. 기존에는 각 애플리케이션에 로깅 라이브러리를 포함시키고 복잡한 appender 설정을 관리했다면, 이제는 애플리케이션은 단순히 stdout으로 로그를 출력하고 Fluentd가 모든 복잡한 처리를 담당합니다.

Fluentd의 핵심 특징은 플러그인 기반 아키텍처, 버퍼링과 재시도 메커니즘, 다중 출력 라우팅입니다. 500개 이상의 플러그인이 존재하여 거의 모든 시스템과 통합할 수 있고, 버퍼링 덕분에 다운스트림 시스템이 일시적으로 장애가 나도 로그를 잃지 않습니다.

이러한 특징들이 중요한 이유는 엔터프라이즈급 로깅 인프라의 안정성과 확장성을 보장하기 때문입니다.

코드 예제

# docker-compose.yml - Fluentd 로깅 설정
version: '3.8'
services:
  web:
    image: nginx:latest
    logging:
      driver: "fluentd"
      options:
        fluentd-address: "localhost:24224"
        fluentd-async: "true"
        fluentd-buffer-limit: "1mb"
        tag: "docker.{{.Name}}"
        labels: "com.myapp.env,com.myapp.service"
    labels:
      com.myapp.env: "production"
      com.myapp.service: "web-server"
    depends_on:
      - fluentd

  fluentd:
    image: fluent/fluentd:v1.16-1
    volumes:
      - ./fluentd/conf:/fluentd/etc
      - ./fluentd/log:/fluentd/log
    ports:
      - "24224:24224"
      - "24224:24224/udp"

설명

이것이 하는 일: Docker 컨테이너가 생성하는 모든 로그를 Fluentd 데몬으로 전송하여, 중앙 집중식 로그 처리 파이프라인을 통해 다양한 목적지로 라우팅하고 변환합니다. 첫 번째로, driver: "fluentd"를 지정하여 Docker가 Fluentd 프로토콜을 사용하도록 설정합니다.

fluentd-address는 Fluentd 데몬의 주소를 가리키며, 같은 Docker Compose 스택 내에서 실행되는 Fluentd 컨테이너의 24224 포트로 연결됩니다. 이렇게 하는 이유는 로그 수집 로직을 애플리케이션 컨테이너와 완전히 분리하여, 나중에 로그 처리 방식을 변경할 때 애플리케이션 재배포 없이 Fluentd 설정만 수정하면 되기 때문입니다.

그 다음으로, fluentd-async: "true"fluentd-buffer-limit 옵션이 실행되면서 비동기 전송과 버퍼링을 활성화합니다. 이는 Fluentd가 느리거나 일시적으로 응답하지 않을 때 애플리케이션 컨테이너가 블로킹되지 않도록 보호합니다.

버퍼가 1MB에 도달하면 오래된 로그부터 삭제되므로, 메모리 부족 문제를 예방합니다. 마지막으로, tag 옵션이 각 로그 이벤트에 태그를 붙여서 최종적으로 Fluentd에서 라우팅 규칙을 적용할 수 있게 만들어냅니다.

예를 들어, docker.nginx-web 태그를 가진 로그는 Fluentd 설정에서 특정 필터와 출력 플러그인으로 처리하도록 라우팅할 수 있습니다. labels 옵션으로 지정한 Docker 레이블도 Fluentd 이벤트에 포함되어 메타데이터로 활용됩니다.

여러분이 이 코드를 사용하면 모든 컨테이너 로그가 Fluentd로 모이고, Fluentd 설정 파일에서 필터링, 파싱, 라우팅을 자유롭게 변경할 수 있습니다. 또한 Fluentd의 재시도 메커니즘 덕분에 Elasticsearch가 일시적으로 다운되어도 로그가 버퍼에 보관되었다가 나중에 전송되므로 데이터 손실을 방지할 수 있습니다.

실전 팁

💡 Fluentd를 사용할 때는 반드시 fluentd-async: "true"를 설정하세요. 동기 모드에서는 Fluentd가 응답할 때까지 애플리케이션이 블로킹되어 심각한 성능 저하가 발생합니다.

💡 Fluentd 컨테이너의 리소스 제한을 적절히 설정하세요. 수백 개의 컨테이너에서 로그를 수신하는 Fluentd는 CPU와 메모리를 상당히 사용하므로, 프로덕션에서는 전용 호스트나 충분한 리소스를 할당해야 합니다.

💡 Fluentd 설정 파일에서 <buffer> 섹션을 활용하여 디스크 기반 버퍼를 설정하면, Fluentd가 재시작되어도 버퍼링된 로그가 유지됩니다. 이는 업그레이드나 장애 상황에서 매우 유용합니다.

💡 태그 네이밍 컨벤션을 팀 내에서 표준화하세요. 예를 들어, {environment}.{service}.{component} 형식으로 태그를 지정하면 Fluentd에서 와일드카드 매칭(<match production.*.web>)으로 유연하게 라우팅할 수 있습니다.

💡 Fluentd의 filter 플러그인으로 신용카드 번호, 비밀번호 같은 민감한 정보를 자동으로 마스킹하세요. 정규표현식 기반 필터를 사용하면 GDPR이나 PCI-DSS 같은 규정 준수 요구사항을 충족할 수 있습니다.


5. 컨테이너 메트릭 수집

시작하며

여러분이 프로덕션 환경에서 갑자기 API 응답 시간이 느려지는 장애를 겪었다고 상상해보세요. 로그를 확인해도 명확한 에러가 없고, 애플리케이션 코드는 최근에 변경된 것이 없습니다.

이럴 때 컨테이너의 CPU 사용률이나 메모리 사용량을 알 수 없다면 원인을 찾기 매우 어렵습니다. 이런 문제는 리소스 부족이나 메모리 누수처럼 서서히 진행되는 장애에서 흔히 발생합니다.

컨테이너가 사용하는 실제 리소스를 실시간으로 모니터링하지 않으면, 문제가 심각해진 후에야 알게 되고 그때는 이미 사용자에게 영향을 준 상태입니다. 바로 이럴 때 필요한 것이 컨테이너 메트릭 수집입니다.

CPU, 메모리, 네트워크, 디스크 I/O 같은 리소스 사용량을 지속적으로 수집하면 문제를 조기에 발견하고 예방할 수 있습니다.

개요

간단히 말해서, 컨테이너 메트릭 수집은 각 컨테이너가 소비하는 시스템 리소스를 시계열 데이터로 수집하여, 성능 모니터링과 용량 계획에 활용하는 것입니다. 실무에서는 컨테이너별 리소스 사용량을 정확히 파악하는 것이 비용 최적화의 핵심입니다.

예를 들어, 100개의 마이크로서비스 컨테이너가 실행되는 환경에서 각 컨테이너의 실제 CPU와 메모리 사용 패턴을 분석하면, 과도하게 할당된 리소스를 회수하거나 부족한 부분을 증설할 수 있습니다. 클라우드 환경에서는 이것이 직접적인 비용 절감으로 이어집니다.

기존에는 서버 전체의 리소스만 모니터링했다면, 이제는 컨테이너 단위로 세밀하게 추적합니다. Docker는 cgroups를 통해 각 컨테이너의 리소스 사용량을 격리하고 측정하므로, 한 서버에서 실행되는 수십 개의 컨테이너를 개별적으로 모니터링할 수 있습니다.

컨테이너 메트릭의 핵심 지표는 CPU 사용률(%), 메모리 사용량(bytes), 네트워크 I/O(bytes/sec), 디스크 I/O(operations/sec)입니다. 이러한 지표들이 중요한 이유는 SLA(Service Level Agreement)를 충족하고 있는지 확인하고, 오토스케일링 정책의 트리거로 사용되기 때문입니다.

또한 메모리 누수나 CPU 스파이크 같은 이상 징후를 조기에 탐지할 수 있습니다.

코드 예제

# docker stats 명령어로 실시간 메트릭 확인
# docker stats --no-stream --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"

# Python 스크립트로 메트릭 수집 및 Prometheus 전송
import docker
from prometheus_client import start_http_server, Gauge
import time

# Prometheus 메트릭 정의
container_cpu = Gauge('container_cpu_usage_percent', 'CPU usage', ['container_name'])
container_memory = Gauge('container_memory_usage_bytes', 'Memory usage', ['container_name'])
container_network_rx = Gauge('container_network_rx_bytes', 'Network RX', ['container_name'])

client = docker.from_env()

def collect_metrics():
    for container in client.containers.list():
        stats = container.stats(stream=False)
        # CPU 계산
        cpu_delta = stats['cpu_stats']['cpu_usage']['total_usage'] - stats['precpu_stats']['cpu_usage']['total_usage']
        system_delta = stats['cpu_stats']['system_cpu_usage'] - stats['precpu_stats']['system_cpu_usage']
        cpu_percent = (cpu_delta / system_delta) * len(stats['cpu_stats']['cpu_usage']['percpu_usage']) * 100.0

        # 메트릭 업데이트
        container_cpu.labels(container_name=container.name).set(cpu_percent)
        container_memory.labels(container_name=container.name).set(stats['memory_stats']['usage'])
        container_network_rx.labels(container_name=container.name).set(stats['networks']['eth0']['rx_bytes'])

# Prometheus exporter 시작
start_http_server(8000)
while True:
    collect_metrics()
    time.sleep(15)  # 15초마다 수집

설명

이것이 하는 일: Docker API를 통해 각 컨테이너의 리소스 사용량을 주기적으로 수집하고, Prometheus 포맷으로 노출하여 모니터링 시스템과 통합합니다. 첫 번째로, Docker 클라이언트를 초기화하고 Prometheus의 Gauge 메트릭을 정의합니다.

Gauge는 증가하거나 감소할 수 있는 값(CPU %, 메모리 바이트)을 표현하는 데 적합합니다. labels로 컨테이너 이름을 추가하면, 나중에 Grafana에서 컨테이너별로 필터링하거나 그룹화할 수 있습니다.

이렇게 하는 이유는 멀티 컨테이너 환경에서 어떤 컨테이너가 리소스를 많이 사용하는지 즉시 파악할 수 있기 때문입니다. 그 다음으로, collect_metrics() 함수가 실행되면서 모든 실행 중인 컨테이너를 순회하며 stats() API를 호출합니다.

이 API는 cgroups에서 직접 읽어온 실시간 리소스 사용량을 반환합니다. CPU 사용률은 두 시점의 차이(delta)를 계산하여 백분율로 변환하고, 메모리는 현재 사용 중인 바이트 수, 네트워크는 수신된 총 바이트 수를 추출합니다.

마지막으로, 수집된 값들이 Prometheus 메트릭 객체에 설정되어 최종적으로 HTTP 엔드포인트(http://localhost:8000/metrics)를 통해 노출됩니다. Prometheus 서버는 이 엔드포인트를 주기적으로 스크래핑하여 시계열 데이터베이스에 저장하고, Grafana는 이 데이터를 시각화합니다.

15초마다 수집하므로 거의 실시간에 가까운 모니터링이 가능합니다. 여러분이 이 코드를 사용하면 모든 컨테이너의 리소스 사용 패턴을 시각화하고, 임계값 기반 알림을 설정할 수 있습니다.

예를 들어, 메모리 사용량이 80%를 초과하면 Slack으로 알림을 받거나, CPU 사용률이 지속적으로 높으면 자동으로 스케일 아웃을 트리거할 수 있습니다. 또한 수집된 히스토리 데이터를 분석하여 용량 계획을 수립하고, 리소스 제한(CPU/memory limits)을 적절히 조정할 수 있습니다.

실전 팁

💡 docker stats 명령은 개발 환경에서 빠르게 확인하기 좋지만, 프로덕션에서는 cAdvisor나 Prometheus의 Docker 익스포터 같은 전문 도구를 사용하세요. 이들은 더 많은 메트릭과 안정적인 수집 메커니즘을 제공합니다.

💡 메트릭 수집 간격은 용도에 맞게 조정하세요. 실시간 알림이 필요하면 5-10초, 일반적인 모니터링은 30-60초가 적당합니다. 너무 짧으면 Prometheus 저장소 크기가 급격히 증가합니다.

💡 컨테이너의 리소스 제한(limits)과 요청(requests)을 설정한 경우, 제한 대비 실제 사용률을 함께 모니터링하세요. 사용률이 지속적으로 제한에 근접하면 throttling이 발생할 수 있습니다.

💡 네트워크 I/O 메트릭은 DDoS 공격이나 비정상적인 트래픽 패턴을 탐지하는 데 유용합니다. 평소 대비 10배 이상 증가하면 자동 알림을 설정하세요.

💡 메모리 메트릭에서 cache와 rss를 구분하세요. 캐시는 필요시 회수 가능하지만 RSS(Resident Set Size)는 실제로 애플리케이션이 사용 중인 메모리이므로, OOM Killer의 판단 기준이 됩니다.


6. Prometheus 연동

시작하며

여러분이 마이크로서비스 환경에서 수집한 메트릭 데이터를 어디에 저장하고 어떻게 쿼리할지 고민해본 적 있나요? MySQL이나 MongoDB 같은 일반 데이터베이스에 저장하면 시계열 데이터의 특성상 성능이 급격히 떨어지고, 저장 공간도 빠르게 소진됩니다.

이런 문제는 모니터링 데이터가 증가할수록 더욱 심각해집니다. 100개의 컨테이너에서 매 10초마다 10개의 메트릭을 수집하면 시간당 360만 개의 데이터 포인트가 생성되는데, 일반 데이터베이스로는 감당하기 어렵습니다.

또한 "지난 1주일간 평균 CPU 사용률" 같은 쿼리를 효율적으로 처리하기도 어렵습니다. 바로 이럴 때 필요한 것이 Prometheus 연동입니다.

Prometheus는 시계열 데이터에 최적화된 데이터베이스와 강력한 쿼리 언어(PromQL)를 제공하여, 대규모 메트릭을 효율적으로 저장하고 분석할 수 있게 해줍니다.

개요

간단히 말해서, Prometheus 연동은 Docker 컨테이너와 애플리케이션의 메트릭을 Prometheus 서버가 주기적으로 수집(scrape)하도록 설정하여, 시계열 데이터베이스에 저장하고 쿼리 가능하게 만드는 것입니다. 실무에서는 Pull 기반 모니터링이 더 안정적입니다.

예를 들어, 컨테이너가 메트릭을 Push 하는 방식은 대상 서버가 다운되면 메트릭이 손실되지만, Prometheus가 Pull 하는 방식은 컨테이너가 잠시 응답하지 않아도 다음 수집 주기에 다시 시도하므로 더 견고합니다. 또한 Service Discovery를 통해 동적으로 생성되는 컨테이너를 자동으로 발견하고 모니터링할 수 있습니다.

기존에는 각 서비스마다 StatsD나 Graphite 에이전트를 설치하고 설정했다면, 이제는 Prometheus 클라이언트 라이브러리로 /metrics 엔드포인트를 노출하기만 하면 Prometheus가 자동으로 수집합니다. Prometheus의 핵심 특징은 다차원 데이터 모델(labels), 강력한 쿼리 언어(PromQL), 로컬 스토리지 최적화입니다.

레이블을 사용하면 같은 메트릭을 서비스, 환경, 버전별로 세밀하게 필터링할 수 있고, PromQL로 복잡한 집계와 통계를 계산할 수 있습니다. 이러한 특징들이 중요한 이유는 대규모 분산 시스템에서도 빠른 쿼리 성능을 유지하면서 수천 개의 메트릭을 관리할 수 있기 때문입니다.

코드 예제

# prometheus.yml - Prometheus 설정 파일
global:
  scrape_interval: 15s
  evaluation_interval: 15s
  external_labels:
    cluster: 'production'
    region: 'us-west-2'

scrape_configs:
  # Docker 컨테이너 메트릭 (cAdvisor)
  - job_name: 'cadvisor'
    static_configs:
      - targets: ['cadvisor:8080']
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance
        replacement: 'docker-host-1'

  # 애플리케이션 메트릭
  - job_name: 'app-metrics'
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 30s
    relabel_configs:
      - source_labels: [__meta_docker_container_label_monitoring]
        regex: 'true'
        action: keep
      - source_labels: [__meta_docker_container_name]
        target_label: container
      - source_labels: [__meta_docker_container_label_service]
        target_label: service

설명

이것이 하는 일: Prometheus 서버가 주기적으로 타겟 엔드포인트를 방문하여 메트릭을 수집하고, 레이블을 추가하여 시계열 데이터베이스에 저장합니다. 첫 번째로, global 섹션에서 기본 수집 간격과 평가 간격을 설정합니다.

scrape_interval: 15s는 Prometheus가 15초마다 모든 타겟을 방문하여 메트릭을 수집하도록 합니다. external_labels는 모든 메트릭에 자동으로 추가되는 레이블로, 여러 Prometheus 서버를 운영하거나 Federation을 구성할 때 데이터 출처를 구분하는 데 사용됩니다.

이렇게 하는 이유는 글로벌 분산 환경에서 어느 클러스터와 리전에서 발생한 메트릭인지 명확히 하기 위함입니다. 그 다음으로, scrape_configs 섹션이 실행되면서 수집 대상을 정의합니다.

cadvisor job은 cAdvisor가 노출하는 Docker 컨테이너 메트릭을 수집하고, relabel_configs로 인스턴스 이름을 더 의미 있는 값으로 변경합니다. app-metrics job은 Docker Service Discovery를 사용하여 실행 중인 모든 컨테이너를 자동으로 발견하는데, __meta_docker_container_label_monitoring=true 레이블이 있는 컨테이너만 필터링합니다.

마지막으로, relabel_configs가 Docker 메타데이터를 Prometheus 레이블로 변환하여 최종적으로 쿼리 가능한 차원을 생성합니다. 예를 들어, 컨테이너 이름과 서비스 레이블이 메트릭에 추가되어, PromQL에서 container_cpu_usage{service="web-api"}처럼 쿼리할 수 있게 됩니다.

컨테이너가 동적으로 추가되거나 삭제되어도 Prometheus가 30초마다 재발견하므로 자동으로 모니터링 대상이 업데이트됩니다. 여러분이 이 코드를 사용하면 Docker 환경의 모든 메트릭이 자동으로 Prometheus에 수집되고, 레이블을 통해 다차원 분석이 가능해집니다.

예를 들어, "production 환경의 web-api 서비스만 필터링하여 지난 1시간 평균 CPU 사용률" 같은 복잡한 쿼리를 PromQL로 간단히 표현할 수 있습니다. 또한 Service Discovery 덕분에 오토스케일링으로 컨테이너가 증가해도 수동으로 설정을 변경할 필요가 없습니다.

실전 팁

💡 scrape_interval은 애플리케이션의 특성에 맞게 조정하세요. 빠르게 변하는 메트릭(HTTP 요청 수)은 5-15초, 느리게 변하는 메트릭(디스크 사용량)은 60초가 적당합니다. 너무 짧으면 Prometheus와 타겟 모두에 부하가 증가합니다.

💡 Docker Service Discovery를 사용할 때는 반드시 필터링 레이블(monitoring=true)을 설정하세요. 모든 컨테이너를 수집하려고 하면 내부 시스템 컨테이너나 일시적인 작업 컨테이너까지 포함되어 노이즈가 증가합니다.

💡 relabel_configs는 강력하지만 복잡합니다. 정규표현식 기반 변환을 사용할 때는 반드시 Prometheus UI의 Service Discovery 페이지에서 결과를 확인하여 의도대로 작동하는지 검증하세요.

💡 Prometheus의 로컬 스토리지는 기본적으로 15일간 데이터를 보관합니다. 장기 보관이 필요하면 Thanos나 Cortex 같은 원격 스토리지 솔루션을 사용하거나, --storage.tsdb.retention.time 플래그로 보관 기간을 늘리세요.

💡 고가용성이 필요한 프로덕션 환경에서는 Prometheus를 최소 2개 이상 운영하고, 각각 동일한 타겟을 수집하도록 설정하세요. 한 대가 다운되어도 다른 대가 메트릭을 계속 수집합니다.


7. Grafana 대시보드

시작하며

여러분이 Prometheus에 수천 개의 메트릭을 수집했지만, PromQL을 직접 입력해서 쿼리하고 결과를 해석하는 것이 불편하다고 느껴본 적 있나요? 특히 비개발자인 운영팀이나 경영진에게 시스템 상태를 보고할 때, 숫자와 텍스트만으로는 전달력이 떨어집니다.

이런 문제는 모니터링 데이터를 활용하는 데 큰 장벽이 됩니다. 아무리 정확한 메트릭을 수집해도, 쉽게 시각화하고 인사이트를 발견할 수 없다면 의미가 없습니다.

또한 장애 상황에서 여러 차트를 동시에 보면서 패턴을 파악해야 하는데, PromQL 콘솔만으로는 한계가 있습니다. 바로 이럴 때 필요한 것이 Grafana 대시보드입니다.

Grafana는 시계열 데이터를 아름다운 차트와 대시보드로 시각화하고, 실시간으로 업데이트되며, 임계값 기반 알림까지 제공하는 강력한 도구입니다.

개요

간단히 말해서, Grafana 대시보드는 Prometheus에 저장된 메트릭 데이터를 다양한 시각화 패널(그래프, 게이지, 테이블)로 표현하고, 여러 패널을 조합하여 종합적인 모니터링 뷰를 제공하는 웹 기반 플랫폼입니다. 실무에서는 역할별로 다른 대시보드를 제공하는 것이 중요합니다.

예를 들어, 개발자에게는 애플리케이션 메트릭(응답 시간, 에러율, 처리량)을 중심으로, 인프라 팀에게는 리소스 메트릭(CPU, 메모리, 디스크)을, 경영진에게는 비즈니스 메트릭(활성 사용자, 거래량)을 보여주는 대시보드를 각각 구성합니다. 기존에는 커스텀 모니터링 대시보드를 직접 개발하거나 Kibana, Datadog 같은 상용 도구를 사용했다면, 이제는 오픈소스인 Grafana로 동일하거나 더 나은 수준의 시각화를 무료로 구현할 수 있습니다.

Grafana의 핵심 특징은 다양한 데이터 소스 지원(Prometheus, Elasticsearch, MySQL 등), 템플릿 변수를 통한 동적 대시보드, 알림 채널 통합(Slack, PagerDuty, Email)입니다. 템플릿 변수를 사용하면 하나의 대시보드에서 드롭다운으로 환경이나 서비스를 선택하여 즉시 해당 메트릭을 볼 수 있습니다.

이러한 특징들이 중요한 이유는 수백 개의 서비스를 개별 대시보드 없이 효율적으로 모니터링할 수 있기 때문입니다.

코드 예제

# docker-compose.yml - Grafana와 Prometheus 통합
version: '3.8'
services:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=30d'
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin123
      - GF_USERS_ALLOW_SIGN_UP=false
      - GF_SERVER_ROOT_URL=http://monitoring.example.com
    ports:
      - "3000:3000"
    depends_on:
      - prometheus

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "8080:8080"

volumes:
  prometheus_data:
  grafana_data:

설명

이것이 하는 일: Prometheus, Grafana, cAdvisor를 Docker Compose로 통합 배포하여, 컨테이너 메트릭 수집부터 시각화까지 완전한 모니터링 스택을 구성합니다. 첫 번째로, Prometheus 서비스가 설정 파일과 데이터 볼륨을 마운트하여 시작됩니다.

--storage.tsdb.retention.time=30d 옵션은 30일간 메트릭을 보관하도록 설정하며, prometheus_data 볼륨에 저장하여 컨테이너를 재시작해도 데이터가 유지됩니다. 이렇게 하는 이유는 장기 트렌드 분석과 월별 리포트 작성에 충분한 히스토리를 확보하기 위함입니다.

그 다음으로, Grafana 서비스가 실행되면서 Prometheus를 데이터 소스로 자동 등록합니다. /etc/grafana/provisioning 디렉토리를 마운트하면 데이터 소스와 대시보드를 코드로 관리할 수 있어(Infrastructure as Code), 환경 재구성 시 수동 설정 없이 즉시 사용할 수 있습니다.

GF_USERS_ALLOW_SIGN_UP=false는 보안을 위해 공개 회원가입을 차단하고, 관리자만 사용자를 추가할 수 있게 합니다. 마지막으로, cAdvisor 서비스가 호스트의 Docker 소켓과 시스템 디렉토리를 마운트하여 최종적으로 모든 컨테이너의 리소스 메트릭을 수집하고 8080 포트로 노출합니다.

Prometheus가 이 엔드포인트를 스크랩하여 메트릭을 저장하고, Grafana가 이를 시각화하는 완전한 파이프라인이 형성됩니다. 여러분이 이 코드를 사용하면 단 한 번의 docker-compose up 명령으로 전체 모니터링 스택이 구동되고, 웹 브라우저에서 http://localhost:3000으로 접속하여 즉시 대시보드를 사용할 수 있습니다.

Grafana의 기본 대시보드 템플릿(Docker Container & Host Metrics)을 import하면 5분 안에 프로덕션급 모니터링 환경을 구축할 수 있습니다. 또한 알림 규칙을 설정하여 CPU 사용률이 80%를 넘거나 컨테이너가 재시작되면 Slack으로 실시간 알림을 받을 수 있습니다.

실전 팁

💡 Grafana의 Provisioning 기능을 반드시 사용하세요. /etc/grafana/provisioning/datasources/etc/grafana/provisioning/dashboards 디렉토리에 YAML 파일로 설정을 관리하면, GitOps 워크플로우에 통합하고 팀원 간 공유가 쉬워집니다.

💡 템플릿 변수를 적극 활용하세요. 예를 들어, $container 변수로 모든 컨테이너 목록을 드롭다운으로 제공하면 수백 개의 컨테이너를 하나의 대시보드에서 전환하며 볼 수 있습니다. PromQL에서 label_values(container_cpu_usage, container) 같은 쿼리로 동적으로 값을 가져옵니다.

💡 대시보드는 목적별로 분리하세요. 전체 시스템 개요(Overview), 서비스별 상세(Service Detail), 장애 대응(Incident Response) 대시보드를 각각 만들어 상황에 맞게 사용하면 인지 부하가 줄어듭니다.

💡 Grafana 알림의 Notification Channel을 여러 개 설정하세요. Critical 알림은 PagerDuty로 즉시 온콜 엔지니어에게, Warning은 Slack 채널로, Info는 이메일로 전송하는 식으로 중요도에 따라 채널을 분리합니다.

💡 대시보드 성능을 위해 쿼리 결과를 캐싱하고, 시간 범위를 제한하세요. "지난 1년" 같은 넓은 범위는 Prometheus에 큰 부하를 주므로, 기본값을 "지난 6시간"으로 설정하고 필요시에만 확장하도록 유도합니다.


8. 로그 로테이션

시작하며

여러분이 어느 날 출근해서 프로덕션 서버의 디스크 사용률이 95%에 도달했다는 알림을 받았다고 상상해보세요. 급하게 확인해보니 /var/lib/docker/containers 디렉토리에 수십 GB의 로그 파일이 쌓여있습니다.

이런 상황에서는 서비스를 중단하고 수동으로 파일을 삭제해야 하는 긴급 상황이 발생합니다. 이런 문제는 로그 관리 정책이 없는 환경에서 매우 흔하게 발생합니다.

특히 고트래픽 애플리케이션은 하루에 수 GB의 로그를 생성할 수 있으며, 몇 주만 방치해도 디스크가 가득 차서 새로운 데이터를 쓸 수 없게 되고 결국 서비스 장애로 이어집니다. 바로 이럴 때 필요한 것이 로그 로테이션입니다.

로그 파일을 일정 크기나 기간마다 자동으로 분할하고, 오래된 파일은 압축하거나 삭제하여 디스크 공간을 효율적으로 관리하는 필수 운영 기법입니다.

개요

간단히 말해서, 로그 로테이션은 로그 파일이 무한정 커지는 것을 방지하기 위해 크기나 시간 기준으로 새 파일로 분할하고, 설정된 개수만 보관하며 나머지는 자동으로 삭제하는 메커니즘입니다. 실무에서는 예측 가능한 디스크 사용량 유지가 비용 관리의 핵심입니다.

예를 들어, 100개의 컨테이너 각각에 "최대 10MB 파일 5개 보관" 정책을 적용하면, 최악의 경우에도 100 × 10MB × 5 = 5GB만 사용한다는 것을 보장할 수 있습니다. 클라우드 환경에서는 이것이 직접적인 스토리지 비용 절감으로 이어집니다.

기존에는 Linux의 logrotate 데몬을 사용해서 애플리케이션 로그 파일을 관리했다면, 이제는 Docker의 로그 드라이버 옵션으로 컨테이너 로그를 자동으로 로테이션합니다. 이는 별도의 에이전트 설치나 cron job 없이 작동합니다.

로그 로테이션의 핵심 파라미터는 max-size(파일당 최대 크기), max-file(보관할 파일 개수), compress(압축 여부)입니다. max-size를 초과하면 현재 파일이 닫히고 새 파일이 생성되며, max-file을 초과하는 가장 오래된 파일은 자동으로 삭제됩니다.

compress 옵션을 활성화하면 로테이션된 파일이 gzip으로 압축되어 70-80%의 공간을 절약합니다. 이러한 파라미터들이 중요한 이유는 장애 분석에 필요한 충분한 로그 히스토리를 유지하면서도 디스크 고갈을 방지하는 균형을 맞추기 때문입니다.

코드 예제

# /etc/docker/daemon.json - Docker 데몬 전역 로그 로테이션 설정
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "50m",
    "max-file": "10",
    "compress": "true",
    "labels": "production,service_name",
    "env": "ENVIRONMENT"
  }
}

# docker-compose.yml - 서비스별 커스텀 로테이션 정책
version: '3.8'
services:
  high_traffic_api:
    image: myapi:latest
    logging:
      driver: "json-file"
      options:
        max-size: "100m"  # 트래픽이 많아서 더 큰 파일
        max-file: "20"     # 더 긴 히스토리 보관
        compress: "true"

  batch_job:
    image: mybatch:latest
    logging:
      driver: "json-file"
      options:
        max-size: "10m"    # 배치는 로그가 적음
        max-file: "3"      # 짧은 히스토리로 충분
        compress: "true"

설명

이것이 하는 일: Docker 데몬과 개별 컨테이너 수준에서 로그 로테이션 정책을 설정하여, 각 서비스의 특성에 맞게 디스크 사용량을 자동으로 제어합니다. 첫 번째로, /etc/docker/daemon.json 파일에서 전역 기본 정책을 설정합니다.

max-size: "50m"은 로그 파일이 50MB에 도달하면 자동으로 로테이션되도록 하고, max-file: "10"은 최대 10개의 백업 파일을 보관합니다. 즉, 각 컨테이너는 최대 500MB의 로그를 유지하며, 11번째 파일이 생성되면 가장 오래된 파일이 자동으로 삭제됩니다.

이렇게 하는 이유는 모든 컨테이너에 일관된 기본 정책을 적용하여 예상치 못한 디스크 고갈을 방지하기 위함입니다. 그 다음으로, compress: "true" 옵션이 실행되면서 로테이션된 로그 파일이 gzip으로 압축됩니다.

텍스트 기반 로그는 보통 80% 이상 압축되므로, 실질적으로 100MB 상당의 로그를 20MB 정도로 저장할 수 있습니다. 압축은 CPU를 약간 사용하지만 디스크 I/O와 저장 비용 절감 효과가 훨씬 큽니다.

마지막으로, Docker Compose 파일에서 서비스별 커스텀 정책을 적용하여 최종적으로 각 워크로드의 특성에 맞는 최적화된 로그 관리를 구현합니다. high_traffic_api는 초당 수백 건의 요청을 처리하므로 더 큰 파일 크기와 더 많은 백업 파일을 허용하여 충분한 디버깅 정보를 보관합니다.

반면 batch_job는 하루에 몇 번만 실행되므로 최소한의 로그만 유지하여 리소스를 절약합니다. 여러분이 이 코드를 사용하면 디스크 공간 부족으로 인한 서비스 장애를 사전에 방지할 수 있습니다.

또한 각 서비스의 로그 생성 패턴에 맞춰 정책을 조정하여, 중요한 서비스는 더 긴 히스토리를 보관하고 덜 중요한 서비스는 최소한으로 유지할 수 있습니다. 압축 기능 덕분에 클라우드 스토리지 비용도 크게 절감되며, 규정 준수를 위해 일정 기간 로그를 보관해야 하는 경우에도 효율적으로 대응할 수 있습니다.

실전 팁

💡 max-size는 애플리케이션의 로그 생성 속도를 측정한 후 설정하세요. docker stats 명령으로 실제 로그 증가율을 확인하고, 최소 1시간 이상의 로그가 한 파일에 담기도록 크기를 조정합니다. 너무 작으면 로테이션이 너무 자주 발생합니다.

💡 장애 분석을 위해서는 최소 24-48시간의 로그 히스토리가 필요합니다. max-size × max-file의 총 용량이 이 기간을 커버하는지 계산하세요. 예: 시간당 10MB 로그 생성 → 48시간 = 480MB → max-size: "50m", max-file: "10" (총 500MB)

💡 /etc/docker/daemon.json 수정 후에는 반드시 sudo systemctl restart docker로 데몬을 재시작하고, 이미 실행 중인 컨테이너도 재시작해야 새 설정이 적용됩니다. 프로덕션에서는 블루-그린 배포나 롤링 업데이트로 무중단 적용을 계획하세요.

💡 디스크 공간 모니터링을 설정하세요. Prometheus의 node_filesystem_avail_bytes 메트릭으로 사용 가능한 디스크 공간을 추적하고, 80% 이상 사용 시 알림을 보내도록 설정하면 문제를 조기에 발견할 수 있습니다.

💡 로그 아카이빙 전략을 고려하세요. 로테이션으로 삭제되는 로그를 S3나 Azure Blob Storage 같은 객체 스토리지로 자동 업로드하면, 저렴한 비용으로 장기 보관하고 필요시 검색할 수 있습니다.


9. 멀티 컨테이너 로깅

시작하며

여러분이 마이크로서비스 아키텍처에서 사용자 요청 하나가 API Gateway → 인증 서비스 → 비즈니스 로직 → 데이터베이스 순으로 여러 컨테이너를 거쳐 처리된다고 상상해보세요. 장애가 발생했을 때 각 컨테이너의 로그를 따로따로 확인하면서 전체 흐름을 재구성하는 것은 거의 불가능에 가깝습니다.

이런 문제는 분산 시스템에서 가장 어려운 과제 중 하나입니다. 각 서비스가 독립적으로 로그를 남기면 시간대가 조금씩 어긋나거나, 로그 포맷이 달라서 상관관계를 파악하기 어렵습니다.

특히 동시에 수백 개의 요청이 처리되는 환경에서는 특정 요청의 로그만 추적하는 것이 불가능합니다. 바로 이럴 때 필요한 것이 멀티 컨테이너 로깅 전략입니다.

Docker Compose를 활용하여 여러 컨테이너의 로그를 통합 수집하고, Correlation ID를 통해 분산 트레이싱을 구현하며, 중앙 로깅 시스템으로 모든 로그를 집계하는 체계적인 접근이 필요합니다.

개요

간단히 말해서, 멀티 컨테이너 로깅은 여러 컨테이너가 협력하여 하나의 서비스를 구성할 때, 모든 컨테이너의 로그를 통합 관리하고 요청 흐름을 추적 가능하게 만드는 로깅 아키텍처입니다. 실무에서는 분산 트레이싱이 장애 해결의 핵심입니다.

예를 들어, 주문 처리 시간이 평소보다 10배 느려졌을 때, Correlation ID로 전체 서비스 체인을 추적하면 "결제 서비스에서 외부 API 타임아웃이 발생"처럼 정확한 병목 지점을 즉시 파악할 수 있습니다. 기존에는 각 서비스마다 별도의 로그 파일을 생성하고 수동으로 합쳐서 분석했다면, 이제는 Docker Compose의 로깅 설정과 Fluentd 같은 로그 집계 도구를 사용하여 자동으로 통합합니다.

멀티 컨테이너 로깅의 핵심 요소는 통합 로그 수집기(Fluentd/Logstash), Correlation ID 전파, 구조화된 로깅 포맷(JSON)입니다. 모든 서비스가 동일한 JSON 구조로 로그를 출력하고, 요청 ID를 헤더나 컨텍스트로 전달하면, 나중에 Elasticsearch에서 request_id: "abc123"으로 검색하여 전체 요청 흐름을 한눈에 볼 수 있습니다.

이러한 요소들이 중요한 이유는 수백 개의 마이크로서비스를 운영하는 환경에서 관찰 가능성(Observability)을 확보하는 유일한 방법이기 때문입니다.

코드 예제

# docker-compose.yml - 멀티 컨테이너 통합 로깅 스택
version: '3.8'

services:
  # 로그 수집기
  fluentd:
    image: fluent/fluentd:v1.16-1
    volumes:
      - ./fluentd/conf:/fluentd/etc
    ports:
      - "24224:24224"
    environment:
      - FLUENTD_CONF=fluent.conf

  # API Gateway
  api_gateway:
    image: myapp/gateway:latest
    logging:
      driver: fluentd
      options:
        fluentd-address: localhost:24224
        tag: "service.gateway"
    environment:
      - SERVICE_NAME=api-gateway
      - LOG_LEVEL=info
    depends_on:
      - fluentd

  # 인증 서비스
  auth_service:
    image: myapp/auth:latest
    logging:
      driver: fluentd
      options:
        fluentd-address: localhost:24224
        tag: "service.auth"
    environment:
      - SERVICE_NAME=auth-service
      - LOG_LEVEL=info
      - CORRELATION_ID_HEADER=X-Request-ID
    depends_on:
      - fluentd

  # 비즈니스 로직 서비스
  business_service:
    image: myapp/business:latest
    logging:
      driver: fluentd
      options:
        fluentd-address: localhost:24224
        tag: "service.business"
    environment:
      - SERVICE_NAME=business-service
      - LOG_LEVEL=debug
    depends_on:
      - fluentd
      - auth_service

  # Elasticsearch (로그 저장)
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.11.0
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - es_data:/usr/share/elasticsearch/data

  # Kibana (로그 검색 UI)
  kibana:
    image: docker.elastic.co/kibana/kibana:8.11.0
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elasticsearch

volumes:
  es_data:

설명

이것이 하는 일: 여러 마이크로서비스 컨테이너의 로그를 Fluentd가 중앙 집중식으로 수집하고, Elasticsearch에 인덱싱하여, Kibana에서 통합 검색과 시각화를 가능하게 합니다. 첫 번째로, Fluentd 컨테이너가 24224 포트로 실행되어 모든 서비스 컨테이너로부터 로그를 수신할 준비를 합니다.

각 서비스는 fluentd 로그 드라이버를 사용하여 stdout/stderr를 Fluentd로 전송하며, tag 옵션으로 서비스를 구분합니다. 이렇게 하는 이유는 나중에 Fluentd 설정에서 <filter service.auth> 같은 태그 기반 라우팅으로 서비스별 로그 처리 규칙을 다르게 적용할 수 있기 때문입니다.

그 다음으로, 각 서비스 컨테이너가 CORRELATION_ID_HEADER 환경변수를 통해 요청 ID를 전파하는 메커니즘을 설정합니다. API Gateway가 최초 요청을 받으면 UUID를 생성하여 X-Request-ID 헤더에 담고, 이후 호출하는 모든 서비스에 이 헤더를 전달합니다.

각 서비스는 로그에 이 ID를 포함시켜 출력하므로, 나중에 Kibana에서 request_id: "550e8400-e29b-41d4-a716-446655440000"으로 검색하면 Gateway → Auth → Business 전체 흐름의 로그가 시간순으로 표시됩니다. 마지막으로, Fluentd가 수집한 로그를 Elasticsearch로 전송하여 최종적으로 인덱싱하고 검색 가능하게 만듭니다.

Elasticsearch는 전문 검색 엔진으로 수백만 건의 로그에서도 밀리초 단위로 쿼리 결과를 반환합니다. Kibana는 Elasticsearch를 데이터 소스로 연결하여 웹 UI를 제공하며, 로그 레벨별 필터링, 시간 범위 선택, 정규표현식 검색 등 강력한 분석 기능을 제공합니다.

여러분이 이 코드를 사용하면 분산된 마이크로서비스의 로그를 단일 지점(Kibana)에서 통합 검색할 수 있고, Correlation ID 덕분에 복잡한 요청 흐름도 쉽게 재구성할 수 있습니다. 예를 들어, 사용자가 "주문이 실패했다"고 신고했을 때, 주문 ID나 사용자 ID로 검색하면 모든 관련 서비스의 로그를 시간순으로 보면서 정확히 어디서 실패했는지 몇 분 안에 파악할 수 있습니다.

또한 Kibana의 시각화 기능으로 "시간대별 에러 발생 빈도", "서비스별 로그 볼륨" 같은 대시보드를 만들어 실시간 모니터링할 수 있습니다.

실전 팁

💡 Correlation ID는 애플리케이션 레벨에서 구현해야 합니다. Express.js라면 미들웨어로, Spring Boot라면 Filter로 모든 요청에 자동으로 ID를 생성하고 전파하도록 코드를 작성하세요. 이것은 인프라가 아닌 애플리케이션의 책임입니다.

💡 구조화된 로깅을 사용하세요. console.log("User login failed")보다는 logger.info({event: "user_login", status: "failed", user_id: 123, request_id: "..."})처럼 JSON 형태로 로그를 출력하면 Elasticsearch에서 각 필드로 검색하고 집계할 수 있습니다.

💡 Fluentd의 버퍼 설정을 최적화하세요. 고트래픽 환경에서는 메모리 버퍼만으로는 부족하므로, 디스크 기반 버퍼(<buffer type file>)를 사용하여 Elasticsearch가 일시적으로 느려져도 로그 손실을 방지합니다.

💡 Elasticsearch 인덱스 관리 정책을 설정하세요. ILM(Index Lifecycle Management)으로 7일 후 인덱스를 읽기 전용으로 전환하고, 30일 후 삭제하도록 자동화하면 저장 공간을 효율적으로 관리할 수 있습니다.

💡 OpenTelemetry를 고려하세요. 더 발전된 단계에서는 Correlation ID만으로는 부족하고, Span과 Trace 개념을 도입하여 서비스 간 호출 관계, 소요 시간, 에러 전파 경로를 자동으로 추적하는 분산 트레이싱 시스템을 구축하는 것이 좋습니다.


10. 프로덕션 모니터링 전략

시작하며

여러분이 지금까지 배운 로깅과 모니터링 기술들을 실제 프로덕션 환경에 적용하려고 할 때, "어디서부터 시작해야 할지" 막막하게 느껴진 적 있나요? 단순히 도구를 설치하는 것과 실제로 장애를 예방하고 빠르게 대응할 수 있는 시스템을 구축하는 것은 전혀 다른 문제입니다.

이런 문제는 체계적인 전략 없이 시작할 때 흔히 발생합니다. 수많은 메트릭을 수집하지만 정작 중요한 순간에 어떤 지표를 봐야 할지 모르거나, 알림이 너무 많아서(alert fatigue) 무시하게 되거나, 로그는 쌓이지만 검색이 어려워서 활용하지 못하는 상황이 벌어집니다.

바로 이럴 때 필요한 것이 프로덕션 모니터링 전략입니다. 이것은 단순히 도구 설정이 아니라, 무엇을 측정할지(What), 어떻게 수집할지(How), 언제 알림을 받을지(When), 누가 대응할지(Who)를 정의하는 종합적인 접근입니다.

개요

간단히 말해서, 프로덕션 모니터링 전략은 시스템의 건강 상태를 지속적으로 관찰하고, 문제를 조기에 발견하며, 신속하게 대응할 수 있도록 설계된 체계적인 프레임워크입니다. 실무에서는 네 가지 골든 시그널(Four Golden Signals)에 집중하는 것이 효과적입니다.

이는 Latency(응답 시간), Traffic(처리량), Errors(에러율), Saturation(리소스 포화도)입니다. 예를 들어, 전자상거래 사이트라면 "체크아웃 API의 95 백분위수 응답 시간", "초당 주문 건수", "결제 실패율", "데이터베이스 CPU 사용률" 같은 핵심 지표를 우선적으로 모니터링합니다.

기존에는 서버가 다운되거나 명확한 에러가 발생해야 알 수 있었다면, 이제는 SLI(Service Level Indicator)와 SLO(Service Level Objective)를 정의하여 목표치 대비 현재 상태를 실시간으로 추적하고, 목표 위반 전에 사전 조치를 취합니다. 프로덕션 모니터링의 핵심 구성 요소는 메트릭 수집(Prometheus), 로그 집계(ELK/Loki), 분산 트레이싱(Jaeger/Zipkin), 알림 관리(Alertmanager/PagerDuty), 시각화(Grafana)입니다.

이들이 유기적으로 연결되어야 하며, 예를 들어 Grafana 대시보드에서 CPU 스파이크를 발견하면 → 해당 시간대 로그를 Kibana에서 검색 → Correlation ID로 분산 트레이싱 → 근본 원인 파악의 흐름이 자연스럽게 이어져야 합니다. 이러한 구성 요소들이 중요한 이유는 단일 도구만으로는 복잡한 분산 시스템의 전체 상황을 파악할 수 없기 때문입니다.

코드 예제

# alerting-rules.yml - Prometheus 알림 규칙
groups:
  - name: docker_containers
    interval: 30s
    rules:
      # 컨테이너 다운 감지
      - alert: ContainerDown
        expr: up{job="docker"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Container {{ $labels.container }} is down"
          description: "Container has been down for more than 1 minute"

      # 높은 CPU 사용률
      - alert: HighCPUUsage
        expr: rate(container_cpu_usage_seconds_total[5m]) > 0.8
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High CPU usage on {{ $labels.container }}"
          description: "CPU usage is above 80% for 5 minutes"

      # 메모리 부족
      - alert: HighMemoryUsage
        expr: (container_memory_usage_bytes / container_spec_memory_limit_bytes) > 0.9
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "High memory usage on {{ $labels.container }}"
          description: "Memory usage is above 90%"

      # 에러율 증가
      - alert: HighErrorRate
        expr: rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05
        for: 3m
        labels:
          severity: warning
        annotations:
          summary: "High error rate on {{ $labels.service }}"
          description: "Error rate is above 5% for 3 minutes"

      # 디스크 공간 부족
      - alert: DiskSpaceRunningOut
        expr: (node_filesystem_avail_bytes{mountpoint="/var/lib/docker"} / node_filesystem_size_bytes) < 0.1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "Disk space running out on Docker partition"
          description: "Less than 10% disk space remaining"

# alertmanager.yml - 알림 라우팅 설정
route:
  group_by: ['alertname', 'cluster']
  group_wait: 10s
  group_interval: 10s
  repeat_interval: 12h
  receiver: 'team-slack'
  routes:
    - match:
        severity: critical
      receiver: 'pagerduty'
    - match:
        severity: warning
      receiver: 'team-slack'

receivers:
  - name: 'team-slack'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK'
        channel: '#ops-alerts'
        title: '{{ .GroupLabels.alertname }}'
        text: '{{ range .Alerts }}{{ .Annotations.description }}{{ end }}'

  - name: 'pagerduty'
    pagerduty_configs:
      - service_key: 'YOUR_PAGERDUTY_KEY'
        description: '{{ .GroupLabels.alertname }}'

설명

이것이 하는 일: Prometheus 알림 규칙으로 시스템의 이상 징후를 자동 감지하고, Alertmanager가 중요도에 따라 적절한 채널(Slack/PagerDuty)로 알림을 전송하여 신속한 대응을 가능하게 합니다. 첫 번째로, 각 알림 규칙이 특정 메트릭의 임계값과 지속 시간을 정의합니다.

ContainerDown 규칙은 up 메트릭이 0(다운 상태)이 1분 이상 지속되면 발동하는데, 이렇게 하는 이유는 일시적인 네트워크 블립이나 짧은 재시작을 무시하고 진짜 문제만 알리기 위함입니다. for: 1m은 이 조건이 1분간 계속 참이어야 알림이 트리거되도록 하여 false positive를 줄입니다.

그 다음으로, 각 알림에 severity 레이블이 지정되면서 중요도가 분류됩니다. critical은 서비스에 즉각적인 영향을 주는 문제(컨테이너 다운, 메모리 고갈)이고, warning은 곧 문제가 될 수 있는 상황(CPU 사용률 증가, 에러율 상승)입니다.

Alertmanager는 이 레이블을 기반으로 라우팅하여, critical은 PagerDuty로 온콜 엔지니어를 깨우고, warning은 Slack 채널로만 알립니다. 마지막으로, Alertmanager의 group_by 설정이 유사한 알림을 묶어서 최종적으로 알림 폭탄(alert storm)을 방지합니다.

예를 들어, 네트워크 장애로 10개 컨테이너가 동시에 다운되면 10개의 개별 알림 대신 "ContainerDown 그룹에 10개 인스턴스" 형태로 하나의 알림만 전송됩니다. repeat_interval: 12h는 같은 알림이 해결되지 않았을 때 12시간마다 다시 알리므로, 문제를 잊지 않게 합니다.

여러분이 이 코드를 사용하면 시스템 문제를 수동으로 확인하지 않아도 자동으로 감지되고, 적절한 사람에게 적시에 알림이 전달되어 MTTR(Mean Time To Recovery)을 크게 단축할 수 있습니다. 예를 들어, 새벽 3시에 데이터베이스 컨테이너가 메모리 부족으로 재시작을 반복하면 온콜 엔지니어가 PagerDuty로 즉시 알림을 받아 대응할 수 있고, 사용자가 문제를 알아차리기 전에 해결할 수 있습니다.

또한 알림 히스토리를 분석하여 자주 발생하는 문제를 파악하고 근본 원인을 제거하는 개선 작업도 가능합니다.

실전 팁

💡 알림 피로(alert fatigue)를 방지하세요. 알림은 "즉시 행동이 필요한 것"만 설정하고, 단순 정보성 메트릭은 대시보드로만 보여줍니다. 경험칙: 일주일에 10번 이상 발생하는 알림은 임계값을 재조정하거나 제거하세요.

💡 SLO(Service Level Objective)를 정의하고 에러 버짓(error budget)을 추적하세요. 예: "API 응답 시간의 99%가 200ms 이하" → 이 목표를 충족하지 못하면 알림 → 새 기능 개발 중단하고 안정성 개선에 집중하는 정책을 수립합니다.

💡 Runbook을 알림에 링크하세요. annotations에 해결 방법을 담은 문서 URL을 포함시키면, 온콜 엔지니어가 새벽에 알림을 받아도 무엇을 해야 할지 즉시 알 수 있습니다. 예: "Wiki: https://wiki.example.com/runbooks/high-memory"

💡 정기적으로 알림 규칙을 리뷰하고 개선하세요. 매월 발생한 알림을 분석하여 false positive(거짓 양성)는 규칙을 조정하고, 놓친 장애(false negative)는 새 규칙을 추가합니다. 이것은 지속적인 개선 프로세스입니다.

💡 메트릭, 로그, 트레이싱을 연결하는 워크플로우를 팀에 교육하세요. "Grafana에서 이상 징후 발견 → Kibana에서 해당 시간대 로그 검색 → Jaeger에서 느린 요청 트레이스 분석" 같은 표준 절차를 문서화하고 실습하면 장애 대응 속도가 크게 향상됩니다.


#Docker#Logging#Monitoring#Container#DevOps