Jaeger로 분산 추적을 시각화하고, Prometheus로 메트릭을 저장/쿼리하며, Grafana로 통합 대시보드를 구성합니다. Docker Compose로 전체 스택을 실습합니다.
OpenTelemetry는 백엔드에 독립적이지만, 실무에서 가장 널리 사용되는 오픈소스 조합은 다음과 같습니다.
| 신호 | 백엔드 | 역할 |
|---|---|---|
| 트레이스 | Jaeger 또는 Grafana Tempo | 분산 추적 저장/검색 |
| 메트릭 | Prometheus | 시계열 메트릭 저장/쿼리 |
| 로그 | Grafana Loki | 로그 저장/검색 |
| 시각화 | Grafana | 통합 대시보드/알림 |
Jaeger는 Uber에서 개발하여 CNCF에 기증한 분산 추적 백엔드입니다. 트레이스 저장, 검색, 시각화, 서비스 의존성 그래프 기능을 제공합니다.
exporters:
otlp/jaeger:
endpoint: "jaeger:4317"
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp/jaeger]Jaeger v2부터는 OTLP를 네이티브로 지원하므로, OTel Collector의 OTLP exporter로 직접 전송할 수 있습니다. 별도의 Jaeger 전용 exporter가 필요하지 않습니다.
Grafana Tempo는 Grafana Labs가 개발한 대규모 분산 추적 백엔드입니다. Jaeger와 달리 인덱싱 없이 오브젝트 스토리지(S3, GCS)에 직접 저장하여 운영 비용을 크게 낮춥니다.
server:
http_listen_port: 3200
distributor:
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
storage:
trace:
backend: local
local:
path: /var/tempo/traces
wal:
path: /var/tempo/wal
metrics_generator:
storage:
path: /var/tempo/generator/wal
remote_write:
- url: http://prometheus:9090/api/v1/writeTempo의 metrics_generator 기능은 트레이스 데이터에서 자동으로 RED 메트릭(Rate, Error, Duration)을 생성합니다. 별도의 메트릭 계측 없이도 서비스별 요청 처리량, 에러율, 지연 시간 메트릭을 얻을 수 있어 초기 도입 시 매우 유용합니다.
Prometheus는 CNCF 졸업 프로젝트이자 시계열 메트릭 저장소의 사실상 표준입니다. Pull 모델(스크레이핑)을 기본으로 하지만, Remote Write를 통한 Push 모델도 지원합니다.
두 가지 연동 방식이 있습니다.
방식 1: Prometheus Exporter (Pull)
Collector가 Prometheus 형식으로 메트릭을 노출하고, Prometheus가 스크레이핑합니다.
exporters:
prometheus:
endpoint: "0.0.0.0:8889"
namespace: "otel"
service:
pipelines:
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheus]방식 2: Prometheus Remote Write (Push)
Collector가 Prometheus에 직접 메트릭을 Push합니다.
exporters:
prometheusremotewrite:
endpoint: "http://prometheus:9090/api/v1/write"
resource_to_telemetry_conversion:
enabled: true
service:
pipelines:
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheusremotewrite]OTel 메트릭을 Prometheus에 저장한 후 PromQL로 쿼리합니다.
# 서비스별 초당 요청 처리량 (RPS)
rate(otel_http_server_request_count_total[5m])
# p99 응답 지연 시간
histogram_quantile(0.99,
rate(otel_http_server_request_duration_seconds_bucket[5m])
)
# 에러율 (5xx 비율)
sum(rate(otel_http_server_request_count_total{http_response_status_code=~"5.."}[5m]))
/
sum(rate(otel_http_server_request_count_total[5m]))
# 서비스별 활성 요청 수
otel_http_server_active_requestsGrafana Loki는 "Prometheus for logs"를 표방하는 로그 집계 시스템입니다. 로그 내용을 인덱싱하지 않고 레이블만 인덱싱하여 저장 비용을 낮춥니다.
exporters:
otlp/loki:
endpoint: "http://loki:3100/otlp"
tls:
insecure: true
service:
pipelines:
logs:
receivers: [otlp]
processors: [batch]
exporters: [otlp/loki]# 특정 서비스의 에러 로그 검색
{service_name="order-service"} |= "ERROR"
# JSON 파싱 후 필터링
{service_name="payment-service"} | json | error_type = "InsufficientFunds"
# 로그에서 trace_id 추출하여 트레이스 연결
{service_name="user-service"} | json | line_format "trace: {{.trace_id}}"
# 분당 에러 로그 수 집계
count_over_time({service_name="order-service"} |= "ERROR" [1m])Grafana는 세 가지 백엔드를 하나의 대시보드에서 통합 시각화합니다.
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus:9090
isDefault: true
jsonData:
exemplarTraceIdDestinations:
- name: trace_id
datasourceUid: tempo
- name: Tempo
type: tempo
url: http://tempo:3200
uid: tempo
jsonData:
tracesToLogs:
datasourceUid: loki
filterByTraceID: true
tracesToMetrics:
datasourceUid: prometheus
- name: Loki
type: loki
url: http://loki:3100
jsonData:
derivedFields:
- datasourceUid: tempo
matcherRegex: "trace_id=(\\w+)"
name: TraceID
url: "$${__value.raw}"Grafana에서의 3대 신호 연결은 다음과 같이 동작합니다.
이 순환적 연결이 관측 가능성의 핵심입니다. 메트릭으로 이상을 감지하고, 트레이스로 원인을 추적하고, 로그로 상세 맥락을 확인하는 워크플로우가 자연스럽게 연결됩니다.
전체 관측 가능성 스택을 Docker Compose로 한 번에 구성합니다.
services:
# --- 애플리케이션 ---
app:
build: .
ports:
- "8080:8080"
environment:
- OTEL_EXPORTER_OTLP_ENDPOINT=http://collector:4317
- OTEL_SERVICE_NAME=demo-app
depends_on:
- collector
# --- OTel Collector ---
collector:
image: otel/opentelemetry-collector-contrib:0.100.0
volumes:
- ./config/otel-collector.yaml:/etc/otelcol/config.yaml
ports:
- "4317:4317"
- "4318:4318"
- "8889:8889"
depends_on:
- tempo
- prometheus
- loki
# --- 트레이스 백엔드 ---
tempo:
image: grafana/tempo:2.4.1
volumes:
- ./config/tempo.yaml:/etc/tempo/config.yaml
command: ["-config.file=/etc/tempo/config.yaml"]
ports:
- "3200:3200"
# --- 메트릭 백엔드 ---
prometheus:
image: prom/prometheus:v2.51.0
volumes:
- ./config/prometheus.yaml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--web.enable-remote-write-receiver"
- "--enable-feature=exemplar-storage"
# --- 로그 백엔드 ---
loki:
image: grafana/loki:2.9.6
ports:
- "3100:3100"
command: ["-config.file=/etc/loki/local-config.yaml"]
# --- 시각화 ---
grafana:
image: grafana/grafana:10.4.1
volumes:
- ./config/grafana-datasources.yaml:/etc/grafana/provisioning/datasources/datasources.yaml
ports:
- "3000:3000"
environment:
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
depends_on:
- tempo
- prometheus
- lokireceivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
processors:
memory_limiter:
check_interval: 1s
limit_mib: 512
spike_limit_mib: 128
batch:
timeout: 1s
send_batch_size: 1024
exporters:
otlp/tempo:
endpoint: "tempo:4317"
tls:
insecure: true
prometheusremotewrite:
endpoint: "http://prometheus:9090/api/v1/write"
resource_to_telemetry_conversion:
enabled: true
otlp/loki:
endpoint: "http://loki:3100/otlp"
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp/tempo]
metrics:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [prometheusremotewrite]
logs:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp/loki]# 전체 스택 시작
docker compose up -d
# 상태 확인
docker compose ps
# 테스트 요청 전송
curl -X POST http://localhost:8080/orders \
-H "Content-Type: application/json" \
-d '{"items": ["item-1"], "total": 25000}'
# Grafana 접속: http://localhost:3000
# Jaeger UI 접속 (사용 시): http://localhost:16686Exemplars가 Prometheus와 Grafana에서 동작하는 전체 흐름을 확인합니다.
global:
scrape_interval: 15s
# Exemplar 저장 활성화 (--enable-feature=exemplar-storage 플래그 필요)이번 장에서는 OTel Collector에서 수집한 3대 신호를 각 백엔드로 연동하는 방법을 학습했습니다. Jaeger/Tempo로 트레이스를, Prometheus로 메트릭을, Loki로 로그를 저장하고, Grafana에서 이 세 신호를 통합 시각화하는 전체 스택을 Docker Compose로 구축했습니다. Exemplars를 통한 메트릭-트레이스 연결도 실제로 확인했습니다.
다음 장에서는 AI 서비스의 관측 가능성을 다룹니다. LLM 호출 추적, 토큰 사용량/비용 모니터링, AI 에이전트 행동 추적, LangChain/LlamaIndex와의 OTel 통합을 학습합니다.
이 글이 도움이 되셨나요?
관련 주제 더 보기
LLM 호출 추적, 토큰 사용량/비용 모니터링, AI 에이전트 행동 추적, LangChain/LlamaIndex OTel 통합을 통한 AI 관측 가능성을 학습합니다.
OTel Collector의 Receiver/Processor/Exporter 파이프라인, 핵심 프로세서 활용법, Kubernetes 환경에서의 DaemonSet/Deployment 배포를 학습합니다.
SLI/SLO/에러 버짓의 개념을 정립하고, 번 레이트 알림(fast-burn/slow-burn), Prometheus 알림 규칙, Grafana 알림 채널을 설계합니다.