OpenTelemetry와 Loki

벤더 중립 계측과 레이블 기반 로그 수집 파이프라인

OpenTelemetry는 로그·메트릭·트레이스를 수집하는 벤더 중립 표준이고, Loki는 레이블 기반 경량 로그 저장소다. 둘을 조합하면 여러 서비스의 로그를 단일 파이프라인으로 수집해 사용자·회사별로 필터링하는 구조를 만들 수 있다.

Observability

개념

Observability(관측 가능성) 는 시스템 내부 상태를 외부 출력만으로 추론할 수 있는 정도를 뜻한다. 세 가지 신호로 구성된다.

신호 예시
Logs 이벤트 기록 에러 메시지, 요청 로그
Metrics 수치 측정값 CPU 사용률, 초당 요청 수
Traces 요청의 전체 경로 추적 API → DB → 외부 호출 span

OpenTelemetry(OTel) 는 이 세 신호를 수집·전송하기 위한 오픈소스 표준이다. CNCF가 관리한다. 핵심은 벤더 중립성 — 코드는 OTel API로 한 번만 계측하고, 백엔드(Datadog, Grafana, Jaeger 등)는 나중에 자유롭게 교체할 수 있다.

Loki 는 Grafana Labs가 만든 경량 로그 저장소다. Prometheus 방식을 로그에 적용한 것으로, 로그 내용 전체를 인덱싱하는 Elasticsearch와 달리 레이블만 인덱싱하고 내용은 압축 저장한다. 덕분에 용량이 작고 운영이 단순하다.

왜 필요한가

기존에는 서비스마다 Datadog SDK, Jaeger SDK, Prometheus 클라이언트를 각각 붙여야 했다. 벤더를 바꾸면 코드를 다 뜯어야 하고, 서비스가 늘수록 파이프라인이 제각각이 된다.

OTel은 이 문제를 계측 레이어와 백엔드를 분리해서 해결한다. 서비스는 OTel SDK만 알면 되고, 어디로 보낼지는 Collector 설정으로 제어한다.

구조

서비스 (애플리케이션)
  └─ OTel SDK          ← 신호 생성
       └─ OTel Collector ← 수집·파싱·필터링·라우팅
            └─ 백엔드    ← Loki(로그), Prometheus(메트릭), Jaeger/Tempo(트레이스)

OTel Collector 가 핵심이다. 세 가지 구성요소로 동작한다.

  • Receiver: 데이터를 받는 입구. filelog(파일), otlp(SDK 직전송), docker_stats
  • Processor: 파싱, 레이블 추가, 필터링, 배치 처리
  • Exporter: 백엔드로 전송. loki, prometheus, otlp

Loki 레이블 설계

Loki는 레이블 조합으로 로그 스트림을 식별한다. 레이블이 쿼리의 기본 단위다.

# 쿼리 예시
{service="mattermost", company_id="acme"}
{service="mattermost", user_id="alice"} |= "error"

레이블 설계 원칙:

  • 카디널리티가 낮은 값만 레이블로 — user_id(수백~수천)는 괜찮지만 request_id(무한)는 레이블 금지
  • 필터링에 자주 쓰는 축(회사, 서비스, 레벨)을 레이블로

Mattermost 로그 수집 전략

Mattermost가 Docker로 배포된 상황에서 로그를 회사·사용자별로 대시보드에 보여주는 파이프라인.

전체 흐름

Mattermost (Docker)
  → OTel Collector (filelog receiver로 로그 파일 읽기)
    → 파싱: company_id / user_id 레이블 추출
      → Loki (레이블 기반 저장)
        → 대시보드 백엔드 API (Loki HTTP API 래핑)
          → 프론트엔드

OTel Collector 설정 골격

receivers:
  filelog:
    include: [/mattermost/logs/*.log]
    operators:
      - type: json_parser          # Mattermost는 JSON 로그 출력
      - type: add
        field: attributes.service
        value: mattermost

processors:
  attributes:
    actions:
      - key: company_id
        from_attribute: body.team_name   # 팀 = 회사 단위
        action: upsert
      - key: user_id
        from_attribute: body.caller_user_id
        action: upsert

exporters:
  loki:
    endpoint: http://loki:3100/loki/api/v1/push

service:
  pipelines:
    logs:
      receivers: [filelog]
      processors: [attributes]
      exporters: [loki]

대시보드 백엔드

Loki HTTP API를 그대로 프론트에 노출하지 않고, 백엔드에서 래핑해서 내린다.

GET /api/logs?company_id=acme&user_id=alice&start=...&end=...
  → 백엔드가 Loki QueryRange API 호출
    → GET http://loki:3100/loki/api/v1/query_range
         ?query={service="mattermost",company_id="acme",user_id="alice"}
         &start=...&end=...

이렇게 하면:

  • 인증·접근 제어를 백엔드에서 처리 가능
  • Loki 주소가 외부에 노출되지 않음
  • 나중에 Loki 외 다른 소스를 합쳐서 내릴 때 API 인터페이스 유지

다른 서비스 추가 시

Collector에 receiver만 추가하면 된다. Loki와 대시보드 백엔드는 그대로.

receivers:
  filelog/service-a:
    include: [/service-a/logs/*.log]
  filelog/service-b:
    include: [/service-b/logs/*.log]
    operators:
      - type: add
        field: attributes.service
        value: service-b

왜 Loki인가 (Elasticsearch 대비)

Loki Elasticsearch
인덱싱 레이블만 전체 텍스트
저장 용량 작음 (압축)
운영 부담 낮음 높음 (샤딩, 힙 튜닝 등)
전문 검색 불가 가능
레이블 필터 빠름 가능하지만 과함

"회사별·사용자별 로그를 보여준다"는 요구사항은 레이블 필터로 충분히 커버된다. Elasticsearch의 전문 검색이 필요한 상황이 아니면 Loki가 운영 비용 대비 낫다.

언제 쓰나

  • 여러 서비스의 로그를 단일 파이프라인으로 통합해야 할 때
  • 나중에 모니터링 백엔드를 교체할 가능성이 있을 때 (OTel 계층이 보호막)
  • 레이블 기반 필터링(서비스, 환경, 사용자, 회사)으로 충분한 규모일 때

단일 서비스, 단순 로그 조회라면 OTel Collector 없이 Promtail → Loki 직연결이 더 단순하다. OTel은 여러 서비스를 통합하거나 메트릭·트레이스까지 묶을 때 진가가 나온다.

더 보기

sunshinemoon · 2026