DevOps/Monitoring

Promtail & Loki 를 이용한 로그 모니터링 시스템 구축기

kkang._.h00n 2026. 5. 6. 21:00

개요

회사에서 서비스가 본격적으로 시작된 후, 서버에서 남기는 로그들이나 오류 트레이스를 확인하는 경우가 많아졌다. 별다른 모니터링 시스템이 구축되지 않았기에 docker 컨테이너에 직접 접근해서 로그를 확인하였지만, 로그를 확인하기 위해 docker 컨테이너에 계속 접근하는 방식은 불편하였고 서버가 이중화되어있었기에 두 서버에 모두 접속하여 확인하여야했다. 

Promtail & Loki 를 이용하여 이중화 된 서버의 로그를 수집하고 Grafana 대시보드 한 곳에서 모니터링 할 수 있는 시스템을 구축하였고, 서버 운영에서 불편한 부분을 해소할 수 있었다. 지금부터 함께 시스템을 구성해보자!

 

 

Promtail & Loki 스택을 선택한 이유

로그 모니터링 시스템을 구축하기 위한 대표적인 스택으로는 ELK 스택이 있다. 우리 팀도 처음에 ELK 스택을 고려하였지만, 아래의 고려사항들로 Promtail & Loki 스택을 선택하게 되었다.

  • 로그를 단순 조회하거나 Label 기준으로 간단한 검색 기능이면 충분했다.
  • 현재 가용할 수 있는 비용과 리소스로 한계가 있었다.

Elasticsearch는 내부적으로 단어를 토큰화하여 역인덱싱 테이블을 생성한다. 그로인해 빠른 검색능력을 자랑한다.

하지만 우리는 단순히 로그를 시간순서로 볼 수만 있으면 되었으며, json으로 넘어오는 로그의 특정 필드만 필터링 할 수 있으면 되었다. 

그리고 사실 비용과 리소스의 한계가 가장 컸다. 이제 막 서비스를 시작하는 단계에서 ELK 스택은 비용이 너무 비쌌다.

ELK 스택을 위해서 AWS의 OpenSearch를 추가적으로 구축하여야 했지만, 비용이 만만치 않았다. Promtail & Loki를 사용하면 로그를 기존에 사용하던 S3로 중앙수집화 하여 리소스를 늘리지 않아도 되었다. 

빠른 검색능력이 현재 우리가 구축하려는 로그 모니터링 시스템에 꼭 필요한지 고려했을 때 그것은 오버엔지니어링이라 판단하였고, ELK 스택보다는 Promtail & Loki 스택이 적합하다고 판단하였다.

 

 

Promtail & Loki 란

Loki는 Prometheus에서 영감을 받아서 Grafana Labs가 개발한 로그 집계 시스템이다. 가장 큰 특징은 로그 본문 전체를 인덱싱하지 않고, 특정 label만 인덱싱한다는 점이다.

위에서 기술하였듯이 Elasticsearch는 모든 텍스트를 인덱싱하기에 저장 공간과 비용이 많이 들지만, Loki는 라벨 기준으로만 인덱스를 만들기에 운영 비용이 훨씬 저렴하다. 또한 수집한 실제 로그는 압축해서 별도의 스토리지(S3 등)에 저장할 수 있다.

 

PromtailLoki 전용 로그 수집 에이전트이다. 서버 당 1개로 매핑되어서 다음과 같은 일을 한다. 

  • 서버에서 저장하는 로그 파일을 tail
  • 라벨을 붙임
  • 파이프라인 단계에서 파싱
  • Loki 서버로 push

 

 

Loki를 통한 로그 중앙 집계 및 관리

들어가기에 앞서 서버는 이중화 되어있었고, 각 서버의 logback을 설정한 상황에서 log 파일이 별도의 폴더에 생성되는 상황이였다. 

우선 별도의 인스턴스에서 Loki를 docker로 실행하였다.

 

services:
  loki:
    image: grafana/loki:latest
    container_name: loki
    ports:
      - "3100:3100"
    volumes:
      # 설정 파일
      - /home/ubuntu/loki/local-config.yaml:/etc/loki/local-config.yaml
      # Compactor 유지
      - /home/ubuntu/loki/compactor:/loki/compactor
      # promtail로부터 전달받은 로그 데이터를 보관하는 디렉토리
      # 만약 S3를 저장소로 할 경우, 해당 설정 X
      - /home/ubuntu/loki/data:/loki
    environment:
      - TZ=Asia/Seoul
    command: -config.file=/etc/loki/local-config.yaml -config.expand-env=true

3100번 포트에서 loki 컨테이너를 실행시킨다. 3개의 디렉토리를 volume으로 설정한다.

local-config.yaml는 loki 설정 파일이다. 뒤에서 설정파일을 작성할 것이다.

/loki/compactor 는 하루 단위로 병합된 작은 인덱스 조각, 보존 기간이 지난 로그들이 정리되는 폴더이다. Compactor가 동작 중에 Loki 서버가 내려갈 것을 우려하여 볼륨을 설정하였다. 

/loki 디렉토리는 loki가 수집한 로그를 저장하는 디렉토리이다. 필자는 기존에 인스턴스에 저장된 일부 로그들이 있기에 볼륨으로 설정하였다. 로그를 처음부터 S3에 저장할 것이라면, 해당 볼륨은 굳이 해주지 않아도 될 듯 하다.

 

auth_enabled: false

server:
  #서버 리스닝 포트
  http_listen_port: 3100

common:
  instance_addr: 127.0.0.1
  path_prefix: /loki
	replication_factor: 1
  ring:
    kvstore:
      store: inmemory

# 1. storage 설정
storage_config:
  filesystem:
    directory: /loki/chunks
	
  # - aws s3
  aws:
    s3: ${S3_URL}
    # S3 버킷 이름
    bucketnames: #{S3_BUCKET}
    # AWS Region
    region: ap-northeast-2
    # AWS 자격 증명
    # (EC2가 IAM 역할이 있으면 EC2 자격증명을 사용)
    access_key_id: ${AWS_ACCESS_KEY_ID}
    secret_access_key: ${AWS_SECRET_ACCESS_KEY}
    s3forcepathstyle: false

# 2. Retention 설정
limits_config:
  # 90일 보관 기간 설정 - (default:0 -> 무제한)
  retention_period: 90d
  # 최대 쿼리 기간 범위
  max_query_length: 721h 
  # 현재로부터 얼마나 오래된 데이터까지 쿼리가 가능한지 -> 상관 없음
  max_query_lookback: 0  


# 3. Compactor 설정
# a. s3에서 데이터를 조회
# b. /loki/compactor에서  compactor 작업 수행
# c. 다시 s3에 업로드
compactor:
  # Retention 활성화 (무제한일 경우 false)
  retention_enabled: true
  # 삭제 전 대기 시간
  retention_delete_delay: 2h
  # 삭제 작업 워커 수
  retention_delete_worker_count: 150
  # Compactor 작업위한 로컬 디렉토리
  working_directory: /loki/compactor
  # Compaction 실행 주기
  compaction_interval: 30m

# 4. 
schema_config:
  configs:	
    # fileSystem 저장소
    - from: 2020-10-24  
      store: tsdb  
      object_store: filesystem  
      schema: v13
      index:
        prefix: index_
        period: 24h	
  
    # s3 저장소
    - from: 2025-12-15
      store: tsdb
      object_store: s3
      schema: v13
      index:
        prefix: index_
        period: 24h

ruler:
  alertmanager_url: http://localhost:9093

 

설정파일을 통해 수집할 로그를 저장하고 처리하는 방식을 커스텀할 수 있다.

첫 번째로 S3 버킷과 자격 증명 데이터를 설정해주어, 수집된 로그를 S3에 저장하도록 하였다. 

두 번째로 Retention 설정을 통해 수집된 로그에 대한 저장기간, 조회할 수 있는 기간 최대 범위 등을 커스텀하였다.

세 번째로 Compactor 설정을 통해 수집된 로그 데이터에 대한 compactor 작업 주기, Retention 활성화 여부, compactor 작업 위치 등을 커스텀하였다. 

마지막으로 12월 15일 이전 데이터는 인스턴스에 저장되어 있었기에, 해당 날짜를 기준으로 이전 데이터는 fileSystem에서 이후 데이터는 S3에서 조회하도록 설정하였다.

 

 

Promtail을 통한 로그 수집

loki에 대한 실행 및 설정은 잘 마무리 되었다. 이제 서버가 실행되고 있는 인스턴스에 promtail을 컨테이너로 띄워서 어플리케이션의 로그를 수집하고 loki에게 보내도록 하자.

 

services:
  
  # 서버 어플리케이션
  application-server:
    ports:
      - "8080:8080"
    .
    .
  
  promtail:
    image: grafana/promtail:latest
    container_name: promtail
    volumes:
      # 어플리케이션 로그 파일 볼륨 데이터
      - {호스트 로그백 저장 경로}:{promtail 내 데이터 저장 경로}
      # 설정 파일
      - /home/ubuntu/promtail/config.yml:/etc/promtail/config.yml
    networks:
      - monitoring_net
    environment:
      - TZ=Asia/Seoul
    command: -config.file=/etc/promtail/config.yml
    # 어플리케이션 실행 확인 후 실행
    depends_on:
      - application-server
      
    .
    .
    
networks:
  monitoring_net:
    driver: bridge

서버 어플리케이션을 실행하는 docker compose 파일에 promtail 컨테이너 실행 스크립트를 추가하였다. 

일단 현재 실행되고 있는 서버 어플리케이션은 logback을 통해 일주일 치 로그를 파일로 저장중이다. 그리고 로그 파일이 인스턴스에 경로로 볼륨이 설정되어 있는 상태이다. 

promtail에서는 해당 서버 어플리케이션 로그 파일을 볼륨으로 설정하였다. 일련의 과정을 정리하면 다음과 같다. 

  • logback -> 로그 파일에 로그를 기록, 볼륨을 통해 인스턴스의 특정 경로에 저장
  • promtail -> 인스턴스의 로그 파일을 볼륨으로 설정, 로그 파일에 기록된 로그를 loki로 push

또한 설정 파일인 config.yml을 볼륨으로 설정하여서, 별도의 설정들이 휘발되지 않도록 하였다. 설정 파일 작성은 아래와 같다.

 

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  ## loki 주소
  - url: http://${LOKI_IP}:3100/loki/api/v1/push

scrape_configs:
# 로그 수집 작업 이름 
- job_name: app-logs
  static_configs:
    # 수집할 로그 파일이 존재하는 대상 호스트
    - targets:
        - localhost
    # loki에서 구분하기 위한 label
    labels:
        host: app
        job: app-1
        # 수집할 로그 파일 경로 지정
        __path__: ./{promtail 내 데이터 저장 경로}/*.log

  # 로그 파싱 파이프라인 추가
  pipeline_stages:
    - regex:
        # 로그백 형식 -> "%d{yyyy-MM-dd HH:mm:ss.SSS} [traceId=%.-4X{traceId}] [thread = %thread] %-5level %msg%n"
        expression: '^(?P<ts>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \[traceId=.*?\] \[.*?\] (?P<level>[A-Z]+) (?P<msg>.*)$'
    - timestamp:
        source: ts
        format: "2006-01-02 15:04:05.000"
        location: Asia/Seoul
    # - msg가 json 타입 형식일 경우
    # - json 타입 형식 내에서 필요한 특정 필드를 추출하여 label화
    # - json 타입이 아니더라도 에러 나지 않음
    - json:
        source: msg
        expressions:
          routeName: routeName
    - labels:
        level:
        routeName:

# 로그 수집 추가 시 추가 설정 
#- job_name: app-logs
#  static_configs:
#  # 수집할 로그 파일이 존재하는 대상 호스트
#  - targets:
#      - localhost
#    labels:
#      job: app-logs
#      # 수집할 로그 파일 경로 지정
#      __path__: ./app-logs/*.log

clients 필드를 통하여 loki로 데이터를 push하기 위한 url을 설정한다. /api/v1/push는 공식문서에 나와있는 데이터 push path이다.

 

scrape_configs 필드 하위를 통하여, 로그 수집 작업에 대한 설정들을 커스텀할 수 있다. 

하위에 static_configs 설정을 통해, 수집할 로그가 존재하는 파일의 경로를 지정할 수 있다. promtail과 어플리케이션 서버가 저장하는 log 파일이 같은 인스턴스에 저장되어 있기에, localhost 하위에 볼륨으로 설정한 로그 파일 저장 경로를 지정한다.

 

2026-00-00 00:00:00.000 INFO {"routeName":"/routeName","user":{"id":1111,"email":"email@gmail.com","appMemberSocialStatus":"KAKAO"},"coach":{},"coachingId":1111,"method":"method","result":true}

서버에서 남기는 로그 형식은 위와 같았다.

 

pipeline_stages 필드를 통해 로그를 파싱해서 특정 조건을 만족하는 로그들만 검색하게 할 수 있다.

expression 필드에 로그 형식을 정규식으로 변환하여 작성한다. 필자는 로그에서 시간, 로그 레벨, 본문을 파싱해서 timestamp, level, msg라는 이름의 검색 가능한 필드로 지정하였다.

참고로 msg 필드는 json 형식인데, json 내의 특정 필드가 특정 값인 것들만 필터링 할 수 있다.

json에서 routeName와 method라는 필드를 필터링해서 검색하고 싶었다. 그럴 때는 json 필드 하위에 source를 등록하고 검색을 가능하게 할 필드들을 설정해주면 된다.

 

다른 서버에 대한 추가적인 로그 수집 작업이 필요하다면, scrape_configs 하위에 job_name 하위 설정들을 동일하게 추가해주면 된다.

 

 

Grafana 설정

datasource에 loki를 추가하자.

 

dashboard 화면에서 new를 눌러서 대시보드를 생성하자.

 

 

최종 결과

시스템 최종 아키텍쳐이다. 이중화된 서버의 로그를 promtail을 통해 수집, 전달하고 loki에서 해당 로그들을 중앙 집계하며, grafana를 통해 로그를 모니터링 할 수 있게 하였다. 

이중화된 서버의 로그들을 한 곳에서 모니터링 하고 필요한 로그들에 대해서 필터링하도록 기능을 구현하며 운영에 조금 더 기여할 수 있었으며, 로그 모니터링 시스템을 현재 상황과 리소스에 맞도록 구축해보며 조금은 더 성장할 수 있었던 것 같다.