본문 바로가기
서버 인프라 실무

OpenTelemetry + Grafana Tempo로 분산 트레이싱 구축: Spring Boot 연동, OTel Collector, 지표·로그 연계

by yamoojin83 2025. 10. 29.

OpenTelemetry + Grafana Tempo로 분산 트레이싱 구축: Spring Boot 연동, OTel Collector, 지표·로그 연계

로그와 메트릭만으로는 요청이 어디서 지연되는지를 한 눈에 보기 어렵습니다. 분산 트레이싱을 붙이면 요청 한 건이 Gateway → API → Service → DB/외부API로 흘러가는 여정을 스팬(Span) 단위로 추적하고, 문제 구간(느린 쿼리, 외부 호출, 스레드 대기)을 타임라인에서 바로 찾아낼 수 있습니다. 이 글은 Ubuntu 24.04 기준으로 Grafana Tempo(트레이싱 백엔드), OpenTelemetry Collector(게이트웨이), Spring Boot 애플리케이션 연동까지 단계별로 구성합니다. 마지막에 Prometheus/Loki와 연결해 지표·로그·트레이스를 한 화면에서 넘나드는 방법도 정리합니다.


1) 아키텍처 개요

[Spring Boot] --OTLP gRPC/HTTP--> [OpenTelemetry Collector] --(ingest/배치)--> [Tempo]
                                                    |                                       ^
                                                    +--> [Prometheus exemplar] --------------|
                                                    +--> [Loki traceID 라벨] -----------------|
  • Application: OpenTelemetry SDK/Auto-Instrumentation로 trace 생성(HTTP, JDBC, gRPC 등).
  • OTel Collector: 여러 앱에서 오는 트레이스를 받아(OTLP) 배치, 샘플링, 라우팅.
  • Tempo: 인덱스 없는(traceID 기반) 트레이스 저장/조회. 장점은 가볍고 운영 간단.
  • Grafana: Explore → Traces(Tempo), Metrics(Prometheus), Logs(Loki) 탭을 연결(“코릴레이션”).

2) Tempo 단일 인스턴스 설치

# 사용자/디렉터리
sudo useradd --system --no-create-home --shell /usr/sbin/nologin tempo
sudo mkdir -p /etc/tempo /var/lib/tempo
sudo chown -R tempo:tempo /etc/tempo /var/lib/tempo

# /etc/tempo/tempo.yml (최소 예시)
server:
  http_listen_port: 3200
  grpc_listen_port: 3201

distributor:
  receivers:
    otlp:
      protocols:
        http:
        grpc:

storage:
  trace:
    backend: local
    local:
      path: /var/lib/tempo/traces

compactor:
  compaction:
    block_retention: 168h   # 7일 보관 예시

querier:
  frontend_worker:
    frontend_address: 127.0.0.1:9095

query_frontend:
  max_outstanding_per_tenant: 200

metrics_generator:
  registry:
    external_labels:
      cluster: dc1
# systemd
sudo tee /etc/systemd/system/tempo.service >/dev/null <<'S'
[Unit]
Description=Grafana Tempo
After=network-online.target
[Service]
User=tempo
ExecStart=/usr/local/bin/tempo -config.file=/etc/tempo/tempo.yml
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
S

sudo systemctl daemon-reload
sudo systemctl enable --now tempo
systemctl status tempo --no-pager

포트: 수집은 3201 gRPC / 3200 HTTP, 조회는 Grafana 데이터소스에서 http://127.0.0.1:3200를 사용합니다.


3) OpenTelemetry Collector(게이트웨이) 구성

# 사용자/디렉터리
sudo useradd --system --no-create-home --shell /usr/sbin/nologin otel
sudo mkdir -p /etc/otelcol /var/lib/otelcol
sudo chown -R otel:otel /etc/otelcol /var/lib/otelcol

# /etc/otelcol/config.yaml
receivers:
  otlp:
    protocols:
      http:
      grpc:

processors:
  batch:
    timeout: 5s
    send_batch_size: 8192
  memory_limiter:
    check_interval: 5s
    limit_percentage: 60
    spike_limit_percentage: 20
  attributes:
    actions:
      - key: service.namespace
        value: "shop"
        action: upsert

exporters:
  otlp:
    endpoint: 127.0.0.1:3201
    tls:
      insecure: true
  logging:
    loglevel: warn

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch, attributes]
      exporters: [otlp, logging]
# systemd
sudo tee /etc/systemd/system/otelcol.service >/dev/null <<'S'
[Unit]
Description=OpenTelemetry Collector
After=network-online.target
[Service]
User=otel
ExecStart=/usr/local/bin/otelcol --config=/etc/otelcol/config.yaml
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
S

sudo systemctl daemon-reload
sudo systemctl enable --now otelcol

Collector는 수집 포인트(4317/4318)를 열고, Tempo로 전달합니다. 향후 샘플링(tail_sampling)이나 멀티 라우팅(Tempo+Jaeger 동시)도 여기서 처리합니다.


4) Spring Boot 애플리케이션 연동

4-1) Auto-Instrumentation(Java Agent)

# build.gradle
dependencies {
  runtimeOnly("io.opentelemetry.javaagent:opentelemetry-javaagent:2.7.0") // 버전은 환경에 맞게
}

# 실행 옵션 (systemd 서비스 등)
JAVA_TOOL_OPTIONS="-javaagent:/path/opentelemetry-javaagent.jar"
OTEL_SERVICE_NAME="order-api"
OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:4317"
OTEL_METRICS_EXPORTER=none
OTEL_LOGS_EXPORTER=none

장점: 코드 수정 없이 HTTP 클라이언트/서버, JDBC, gRPC 등 주요 라이브러리가 자동 계측됩니다. 세밀한 제어가 필요하면 SDK 방식으로 전환합니다.

4-2) SDK 방식(선택)

implementation("io.opentelemetry:opentelemetry-sdk:1.44.1")
implementation("io.opentelemetry:opentelemetry-exporter-otlp:1.44.1")
@Bean
SdkTracerProvider tracerProvider() {
  return SdkTracerProvider.builder()
      .setResource(Resource.getDefault().merge(
          Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, "order-api"))))
      .build();
}

@Bean
OpenTelemetrySdk openTelemetry(SdkTracerProvider provider) {
  OtlpGrpcSpanExporter exporter = OtlpGrpcSpanExporter.builder()
      .setEndpoint("http://127.0.0.1:4317")
      .build();
  return OpenTelemetrySdk.builder()
      .setTracerProvider(provider)
      .build();
}

5) TraceID를 로그/지표와 연결(코릴레이션)

5-1) 로그에 trace_id 필드 추가

# logback-spring.xml
<configuration>
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
      <providers>
        <timestamp/>
        <mdc/>
        <pattern>
          <pattern>{"level":"%level","logger":"%logger","msg":"%msg"}</pattern>
        </pattern>
      </providers>
    </encoder>
  </appender>
  <root level="INFO"><appender-ref ref="CONSOLE"/></root>
</configuration>

OpenTelemetry Java 에이전트는 MDC에 trace_id, span_id를 넣습니다. Loki 스크레이프에서 해당 필드를 라벨 또는 파싱 필드로 보관하면 Grafana에서 Logs ↔ Traces를 상호 이동할 수 있습니다.

5-2) Prometheus exemplar(선택)

HTTP 요청 히스토그램에 exemplar로 traceID를 매달면, Grafana 그래프의 특정 지점 → 해당 트레이스로 점프할 수 있습니다. Spring Boot Micrometer + OTel 브릿지 조합을 고려하세요.


6) Grafana 데이터소스 연결

  1. Connections → Data sources → Tempo 선택
  2. URL: http://127.0.0.1:3200 → Save & Test
  3. Explore → Traces에서 Service name = order-api 조회

이미 Prometheus와 Loki가 연결되어 있다면, Explore 우측 패널의 “Logs for this trace”, “Metrics for this trace” 링크가 활성화됩니다.


7) 샘플링 전략: 비용·가시성 균형

  • 고정 비율(예: 10%): 간단하지만 낮은 QPS 서비스에서는 표본 수가 부족할 수 있음.
  • Tail Sampling(Collector): 에러/느린 요청만 사후 선별 저장 → 비용 절감 + 유의미 샘플 확보.
# /etc/otelcol/config.yaml (발췌)
processors:
  tailsampling:
    decision_wait: 5s
    policies:
      - name: errors
        type: status_code
        status_code:
          status_codes: [ERROR]
      - name: latency
        type: latency
        latency:
          threshold_ms: 800

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, tailsampling, batch]
      exporters: [otlp]

8) 성능·운영 팁

  • Collector 앞단: 앱 수가 많아지면 Gateway 1대 → N대 수평 확장, 로드밸런서로 OTLP 트래픽 분산.
  • Tempo 스토리지: 단일/로컬로 시작하고, 규모 커지면 S3 호환 오브젝트 스토리지 도입 검토.
  • 라벨 가이드: service.name, service.namespace, environment 정도만. 과도한 속성은 비용↑.
  • 보안: OTLP gRPC/HTTP 포트는 내부망으로 제한(UFW/보안그룹). 멀티 테넌트면 텐언트 헤더/리밋 적용.

9) 문제 해결 빠른 분기

  • 트레이스가 안 보임otelcol 로그에서 OTLP → Tempo exporter 에러 확인, Tempo /status 점검.
  • 스팬이 끊김 → 게이트웨이/서비스 간 traceparent 헤더 전달 누락 검토(프록시/게이트웨이에서 헤더 보존).
  • 부하 급증batch 파라미터 조절, 샘플링 도입, Tempo retention/압축 설정 점검.
  • 로그/지표와 점프 실패 → Loki 파서에 trace_id 필드가 들어오는지, Prometheus exemplar 설정 여부 확인.

10) 체크리스트(복붙용)

  • Tempo/OTel Collector 설치 및 서비스 기동
  • Spring Boot: OTel Java Agent 적용, OTEL_EXPORTER_OTLP_ENDPOINT 설정
  • Grafana Tempo 데이터소스 연결(Explore 동작 확인)
  • Tail Sampling: 에러/지연 정책 추가
  • 로그 MDC에 trace_id 포함 → Loki 파싱
  • Prometheus exemplar(선택)로 트레이스-지표 점프

여기까지 적용하면, “에러율이 올랐다 → 느린 구간 스팬 열람 → 관련 로그 열람”까지 3클릭에 끝납니다. 다음 단계로는 트레이스 기반 SLO(RED/USE)를 정의하고, 경보와 런북을 붙여 탐지→진단→조치 시간을 더 줄여보세요.