본문 바로가기
Infra2026년 4월 22일11분 읽기

Observability 스택 구축 — OpenTelemetry + Tempo + Loki + Mimir

YS
김영삼
조회 1
Observability 스택 구축 — OpenTelemetry + Tempo + Loki + Mimir

핵심 요약

Observability는 더 이상 옵션이 아니다. 클라우드 네이티브 환경에서 디버깅·SLO·incident response의 baseline. 2026년 표준은 OpenTelemetry로 수집 + LGTM 스택으로 저장.

  • Collector: OpenTelemetry Collector (단일 표준)
  • Logs: Loki (cardinality 친화)
  • Traces: Tempo (cost 친화)
  • Metrics: Mimir 또는 Prometheus
  • UI: Grafana

1. OpenTelemetry Collector — 중앙 수집

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
  prometheus:
    config:
      scrape_configs:
        - job_name: kubernetes-pods
          kubernetes_sd_configs:
            - role: pod

processors:
  memory_limiter:
    check_interval: 1s
    limit_mib: 1500
  batch:
    send_batch_size: 8192
    timeout: 1s
  transform:
    metric_statements:
      - context: datapoint
        statements:
          - replace_pattern(attributes["http.target"], "/api/users/\\d+", "/api/users/:id")

exporters:
  loki:
    endpoint: http://loki:3100/loki/api/v1/push
  otlp/tempo:
    endpoint: tempo:4317
  prometheusremotewrite:
    endpoint: http://mimir:9009/api/v1/push

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp/tempo]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [loki]
    metrics:
      receivers: [otlp, prometheus]
      processors: [memory_limiter, transform, batch]
      exporters: [prometheusremotewrite]

2. Loki — 로그

Loki는 인덱스를 라벨에만 (전문 인덱싱 없음). 비용 90% 저렴.

# loki-config.yaml
auth_enabled: false
server:
  http_listen_port: 3100
schema_config:
  configs:
    - from: 2024-01-01
      store: tsdb
      object_store: s3
      schema: v13
      index:
        prefix: index_
        period: 24h
storage_config:
  aws:
    bucketnames: my-loki-storage
    region: ap-northeast-2
limits_config:
  retention_period: 90d
  ingestion_rate_mb: 10

로그 쿼리 (LogQL)

# 에러 로그만
{app="api"} |= "error"

# 5xx 응답
{app="api"} | json | status_code >= 500

# 요청 비율
rate({app="api"}[5m])

3. Tempo — Trace

Tempo는 trace ID 기반 lookup만. 인덱스 비용 0. trace 정보 자체를 object storage(S3)에 저장.

# tempo-config.yaml
distributor:
  receivers:
    otlp:
      protocols:
        grpc: { endpoint: 0.0.0.0:4317 }

storage:
  trace:
    backend: s3
    s3:
      bucket: my-tempo
      endpoint: s3.ap-northeast-2.amazonaws.com

compactor:
  compaction:
    compaction_window: 1h
    block_retention: 720h  # 30일

4. Mimir — Metrics

Prometheus 호환 + 멀티 테넌트 + 거의 무한 cardinality.

# mimir-config.yaml
target: all
multitenancy_enabled: false
blocks_storage:
  backend: s3
  s3:
    bucket_name: my-mimir
limits:
  ingestion_rate: 10000
  max_global_series_per_user: 10_000_000
  out_of_order_time_window: 1h

5. 애플리케이션 측 Instrumentation

Node.js

const { NodeSDK } = require('@opentelemetry/sdk-node')
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc')

const sdk = new NodeSDK({
  serviceName: 'my-api',
  traceExporter: new OTLPTraceExporter({ url: 'http://otel-collector:4317' }),
  instrumentations: [getNodeAutoInstrumentations()],
})
sdk.start()

Python

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor

provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint="otel-collector:4317")))
trace.set_tracer_provider(provider)

FastAPIInstrumentor.instrument_app(app)

6. SLO 정의

# Latency SLO
histogram_quantile(0.99,
  rate(http_request_duration_seconds_bucket[5m])
) < 0.5  # p99 < 500ms

# Error budget
sum(rate(http_requests_total{status=~"5.."}[1h]))
  / sum(rate(http_requests_total[1h]))
  < 0.01  # 99% availability

7. 알람 — Alertmanager

groups:
  - name: api_slo
    rules:
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m]))
            / sum(rate(http_requests_total[5m])) > 0.01
        for: 10m
        labels: { severity: critical }
        annotations:
          summary: "Error rate > 1% for 10m"

8. cardinality 관리

관측성 운영의 가장 큰 함정. 라벨에 user_id·request_id 넣으면 series 폭발.

  • http.path를 그대로 라벨로 ❌ → /api/users/:id 로 normalize ✅
  • error message 라벨 ❌ → error.code 라벨 ✅
  • cardinality 폭발 시 비용 100배·메모리 고갈

9. 비용 비교 (월 1억 events)

스택월 비용
Datadog~$15,000
New Relic~$10,000
Grafana Cloud~$3,000
self-hosted (S3 + EC2)~$800

10. 도입 단계

  1. Collector 배포
  2. 한 서비스에 trace instrumentation
  3. Loki + Tempo + Grafana
  4. SLO 정의 + 알림
  5. 전체 서비스로 확장
  6. cardinality 정책 표준화

자주 묻는 질문

Datadog 대비 self-hosted 단점?

운영 부담. 2~3명의 SRE 시간이 필요. 단 비용은 1/10. 100명 이상 회사는 self-hosted, 작으면 Datadog/Grafana Cloud.

Grafana 대시보드를 직접 만들어야 하나?

대부분 자동 dashboards 가능. https://grafana.com/grafana/dashboards 검색.

Loki vs ELK?Loki는 cardinality 친화·비용 1/10. ELK는 전문 검색 강함. 문자열 grep이 충분하면 Loki, 정교한 검색이 필요하면 ELK.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록