Logic in Code,
Freedom in Travel.

인생 뭐 있나 사람 사는거 다 똑같지

DevOps/모니터링

[모니터링] - Grafana Loki로 도커 컨테이너 로그 보기

귀찮은 개발자 2024. 2. 15. 23:24 계산 중...
목차 (Table of Contents)

마이크로서비스 아키텍처가 사실상 일반화 되면서 수십개의 이상의 컨테이너를 운영하는 것이 일상이 되었다. 이러한 환경에서 로그 관리는 필수지만 내부 보안 정책이나 권한, 인력 문제 등으로 동시에 관리한다는 것도 쉽지 않은 일이다. Grafana Loki 는 이 문제를 조금이나마 해결하기 위한 오픈소스이자 경량화된 로그 집계 시스템이다. 

Loki란?

Loki는 Grafana Labs 에서 개발한 로그 집계 시스템으로, "Prometheus for logs"라는 컨셉으로 설계되었다. Elasticsearch 와 달리 로그 내용을 인덱싱하지 않고 메타데이터(레이블)만 인덱싱하여 비용 효율적이고 운영이 간단하다. 

주요 특징

  • 경량성: 로그 내용이 아닌 레이블만 인덱싱
  • 비용 효율성: 저장 공간과 메모리 사용량 최소화
  • Grafana 통합: 별도 대시보드 없이 Grafana에서 직접 조회
  • LogQL: 강력하고 직관적인 쿼리 언어

아키텍처 구성

Loki 스택은 크게 세 가지 컴포넌트로 구성된다. 

  1. Loki: 로그를 저장하고 쿼리를 처리하는 서버
  2. Promtail: 로그를 수집하여 Loki로 전송하는 에이전트
  3. Grafana: 로그를 시각화하고 쿼리하는 UI

Docker Compose로 Loki 스택 구축하기

1. docker-compose.yml 작성

version: "3.8"

networks:
  loki:

services:
  loki:
    image: grafana/loki:2.9.0
    container_name: loki
    ports:
      - "3100:3100"
    command: -config.file=/etc/loki/local-config.yaml
    networks:
      - loki

  promtail:
    image: grafana/promtail:2.9.0
    container_name: promtail
    volumes:
      - /var/log:/var/log
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - ./promtail-config.yml:/etc/promtail/config.yml
    command: -config.file=/etc/promtail/config.yml
    networks:
      - loki
    depends_on:
      - loki

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    networks:
      - loki
    depends_on:
      - loki

  # 로그 생성용 예제 애플리케이션
  nginx:
    image: nginx:alpine
    container_name: nginx-app
    ports:
      - "8080:80"
    labels:
      logging: "promtail"
      logging_jobname: "nginx"
    networks:
      - loki

2. Promtail 설정 (promtail-config.yml)

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  # Docker 컨테이너 로그 수집
  - job_name: docker
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
        filters:
          - name: label
            values: ["logging=promtail"]

    relabel_configs:
      - source_labels: ['__meta_docker_container_name']
        regex: '/(.*)'
        target_label: 'container'

      - source_labels: ['__meta_docker_container_log_stream']
        target_label: 'stream'

      - source_labels: ['__meta_docker_container_label_logging_jobname']
        target_label: 'job'

3. Docker 로깅 드라이버 설정

개별 컨테이너에 Loki 로깅 드라이버를 직접 설정할 수 있다. 

services:
  myapp:
    image: myapp:latest
    logging:
      driver: loki
      options:
        loki-url: "http://localhost:3100/loki/api/v1/push"
        loki-batch-size: "400"
        labels: "app,environment"
        loki-retries: "2"

실행 및 확인

스택 실행

# Promtail 설정 파일 생성
cat > promtail-config.yml << 'EOF'
# 위의 promtail-config.yml 내용 붙여넣기
EOF

# Docker Compose 실행
docker-compose up -d

# 로그 확인
docker-compose logs -f

Grafana 설정

  1. 브라우저에서 http://localhost:3000 접속
  2. 기본 계정으로 로그인 (admin/admin)
  3. Configuration → Data Sources → Add data source
  4. Loki 선택
  5. URL에 http://loki:3100 입력
  6. Save & Test

LogQL 쿼리 실습

Grafana의 Explore 메뉴에서 다음 쿼리들을 실행해보세요:

기본 쿼리

# 특정 컨테이너의 모든 로그
{container="nginx-app"}

# 특정 job의 로그
{job="nginx"}

# 여러 조건 조합
{container="nginx-app", stream="stdout"}

필터링

# 에러 로그만 조회
{container="nginx-app"} |= "error"

# 특정 패턴 제외
{container="nginx-app"} != "health"

# 정규식 필터
{container="nginx-app"} |~ "POST /api/.*"

집계 쿼리

# 최근 5분간 로그 수
count_over_time({container="nginx-app"}[5m])

# 에러 발생 비율
sum(rate({container="nginx-app"} |= "error"[5m]))

# 컨테이너별 로그 수
sum by (container) (count_over_time({job="nginx"}[1h]))

파싱 및 분석

# JSON 로그 파싱
{container="nginx-app"} | json | status_code >= 500

# 로그 레벨별 분류
{container="nginx-app"} 
  | pattern `<_> level=<level> <_>` 
  | level = "error"

# 응답 시간 분석
{container="nginx-app"} 
  | json 
  | response_time > 1000

실전 활용 팁

1. 효율적인 레이블 설계

labels:
  - environment=production
  - team=backend
  - service=api-gateway
  - version=v1.2.3

레이블이 너무 많으면 cardinality가 높아져 성능이 저하된다는 문제가 있기 때문에 꼭 필요한 메타데이터만 레이블로 사용해야한다. 

2. 로그 보존 정책 설정

# loki-config.yaml
limits_config:
  retention_period: 744h  # 31일

table_manager:
  retention_deletes_enabled: true
  retention_period: 744h

3. 알림 설정

Grafana에서 로그 기반 알림을 설정할 수 있다

# 1분간 에러가 10개 이상 발생하면 알림
sum(count_over_time({container=~".+"} |= "error"[1m])) > 10

4. 대시보드 구성 예시

컨테이너 로그 모니터링 대시보드:

  • 전체 로그 볼륨 (시계열)
  • 컨테이너별 로그 수
  • 로그 레벨 분포 (INFO, WARN, ERROR)
  • 최근 에러 로그 테이블
  • 응답 시간 히트맵

성능 최적화

Promtail 최적화

limits_config:
  readline_rate_enabled: true
  readline_rate: 10000
  readline_burst: 20000

Loki 쿼리 최적화

  1. 시간 범위 제한: 너무 긴 시간 범위는 피하기
  2. 레이블 필터 우선: 텍스트 필터보다 레이블 필터를 먼저 사용
  3. 인덱스 활용: {container="app"} (O) vs {} |= "app" (X)
# good
{container="nginx-app"} |= "error" [5m]

# bad
{} |= "nginx-app" |= "error" [24h]

트러블슈팅

로그가 보이지 않을 때

# Promtail 로그 확인
docker logs promtail

# Loki 상태 확인
curl http://localhost:3100/ready

# Docker 소켓 권한 확인
ls -l /var/run/docker.sock

메모리 부족 문제

# docker-compose.yml
services:
  loki:
    deploy:
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 512M

프로덕션 환경 고려사항

1. 고가용성 구성

프로덕션 환경에서는 Loki를 마이크로서비스 모드로 실행해야한다. 

  • Distributor: 로그 수집 엔드포인트
  • Ingester: 로그 저장
  • Querier: 쿼리 처리

2. 오브젝트 스토리지 연동

장기 보관을 위해 S3, GCS 등과 연동

storage_config:
  aws:
    s3: s3://region/bucket
    s3forcepathstyle: true

3. 보안 설정

auth_enabled: true

server:
  http_tls_config:
    cert_file: /path/to/cert.pem
    key_file: /path/to/key.pem

마치며

Grafana Loki는 도커 환경에서 로그 관리를 위한 강력하면서도 가벼운 솔루션이다. Elasticsearch 보다 리소스 효율적이면서 Grafana와 잘 맞아서 사용하기도 좋다. 여기서 가장 중요하건 적절한 레이블 설계와 효율적인 쿼리를 작성하는 것인다 참으로 어려우면서 쉬운 것 같다. 

참고 자료