마이크로서비스 환경에서 하나의 요청이 여러 서비스를 거치는데, 어디서 지연이 발생하는지 어떻게 추적할 수 있을까요?

OpenTelemetry란

OpenTelemetry(OTel)는 CNCF(Cloud Native Computing Foundation)가 관리하는 옵저버빌리티 표준 입니다. 분산 시스템에서 발생하는 세 가지 관측 신호를 통합합니다.

  • Traces: 요청이 서비스를 거치는 흐름 추적
  • Metrics: 수치 측정 (요청 수, 응답 시간, 리소스 사용)
  • Logs: 이벤트 기록

이전에는 Jaeger(트레이싱), Prometheus(메트릭), ELK(로그)를 각각 설정해야 했지만, OpenTelemetry는 하나의 SDK로 세 가지를 모두 처리합니다.

OpenTelemetry 아키텍처

PLAINTEXT
[서비스 A] → [서비스 B] → [서비스 C]
    ↓              ↓              ↓
    └──── 텔레메트리 데이터 ────────┘

           [OTel Collector]
           ↓       ↓       ↓
       [Jaeger] [Prometheus] [Loki]

              [Grafana]

핵심 구성요소

구성요소역할
SDK애플리케이션에서 텔레메트리 데이터 생성
OTLP데이터 전송 표준 프로토콜
Collector데이터 수신, 가공, 내보내기 중간 프록시
Exporter최종 백엔드(Jaeger, Prometheus 등)로 전달

Spring Boot에서 OpenTelemetry 설정

방법 1: Micrometer + OTel 익스포터 (권장)

Spring Boot 3는 Micrometer를 통해 OpenTelemetry와 통합합니다.

JAVA
// build.gradle
implementation 'org.springframework.boot:spring-boot-starter-actuator'

// 트레이싱
implementation 'io.micrometer:micrometer-tracing-bridge-otel'
implementation 'io.opentelemetry:opentelemetry-exporter-otlp'

// 메트릭
implementation 'io.micrometer:micrometer-registry-otlp'
YAML
# application.yml
management:
  tracing:
    sampling:
      probability: 1.0  # 모든 요청 추적 (운영에서는 0.1 등 낮게)
  otlp:
    tracing:
      endpoint: http://otel-collector:4318/v1/traces
    metrics:
      export:
        endpoint: http://otel-collector:4318/v1/metrics
        step: 30s

방법 2: Java Agent (자동 계측)

코드 수정 없이 Java Agent를 붙이면 HTTP, DB, gRPC 등이 자동으로 추적됩니다.

BASH
java -javaagent:opentelemetry-javaagent.jar \
  -Dotel.service.name=order-service \
  -Dotel.exporter.otlp.endpoint=http://otel-collector:4317 \
  -jar myapp.jar

트레이싱 — 분산 요청 추적

Trace와 Span

PLAINTEXT
Trace: 하나의 요청에 대한 전체 흐름
├── Span A: API Gateway (150ms)
│   ├── Span B: Order Service (100ms)
│   │   ├── Span C: DB Query (30ms)
│   │   └── Span D: Payment Service (50ms)
│   │       └── Span E: PG 연동 (40ms)
│   └── Span F: Notification (20ms)
  • Trace: 전체 요청의 고유 식별자 (Trace ID)
  • Span: 개별 작업 단위 (Span ID, 부모 Span ID, 시작/종료 시간)

자동 계측되는 항목

Spring Boot + Micrometer Tracing이 자동으로 Span을 생성하는 항목:

  • HTTP 요청/응답 (서버, 클라이언트)
  • JDBC 쿼리
  • Spring WebClient/RestClient 호출
  • JMS/Kafka 메시지
  • Spring @Async 메서드

수동 Span 생성

비즈니스 로직 내부의 특정 구간을 추적하고 싶을 때:

JAVA
@Service
@RequiredArgsConstructor
public class OrderService {
    private final ObservationRegistry observationRegistry;

    public Order processOrder(OrderRequest request) {
        // Observation API로 Span 생성
        return Observation.createNotStarted("order.process",
                observationRegistry)
            .lowCardinalityKeyValue("order.type",
                request.getType().name())
            .observe(() -> {
                // 이 블록이 Span으로 기록됨
                Order order = createOrder(request);
                validateInventory(order);
                processPayment(order);
                return order;
            });
    }
}

Span에 커스텀 정보 추가

JAVA
@Service
@RequiredArgsConstructor
public class PaymentService {
    private final Tracer tracer;  // Micrometer Tracer

    public PaymentResult charge(PaymentRequest request) {
        Span currentSpan = tracer.currentSpan();
        if (currentSpan != null) {
            // Span에 태그 추가
            currentSpan.tag("payment.gateway", request.getGateway());
            currentSpan.tag("payment.amount",
                String.valueOf(request.getAmount()));

            // Span 이벤트 추가
            currentSpan.event("결제 시작");
        }

결제 완료 후 결과 상태를 태그로 추가하면 추적 시스템에서 성공/실패를 필터링할 수 있습니다.

JAVA
        PaymentResult result = gateway.charge(request);

        if (currentSpan != null) {
            currentSpan.tag("payment.status", result.getStatus());
            currentSpan.event("결제 완료");
        }

        return result;
    }
}

컨텍스트 전파 (Context Propagation)

서비스 간 호출 시 Trace ID가 자동으로 전파됩니다.

PLAINTEXT
Service A                          Service B
[Span A]                          [Span B]
  → HTTP 요청 →
  Header: traceparent=00-trace123-span456-01
                                   ← trace123과 연결된 Span B 생성

RestClient/WebClient를 사용하면 자동으로 traceparent 헤더가 추가됩니다.

JAVA
@Bean
public RestClient restClient(RestClient.Builder builder) {
    return builder
        .baseUrl("http://payment-service:8080")
        .build();
    // Micrometer가 자동으로 tracing 헤더 추가
}

OTel Collector 설정

YAML
# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    send_batch_size: 1024
    timeout: 5s
  memory_limiter:
    check_interval: 1s
    limit_mib: 512

Exporters에서 수집한 데이터를 트레이스는 Tempo로, 메트릭은 Prometheus로, 로그는 Loki로 내보냅니다.

YAML
exporters:
  # 트레이스 → Jaeger/Tempo
  otlp/tempo:
    endpoint: tempo:4317
    tls:
      insecure: true

  # 메트릭 → Prometheus
  prometheus:
    endpoint: 0.0.0.0:8889

  # 로그 → Loki
  loki:
    endpoint: http://loki:3100/loki/api/v1/push

service.pipelines에서 각 신호(traces, metrics, logs)별로 수집-처리-내보내기 파이프라인을 구성합니다.

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

Docker Compose

YAML
services:
  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317"   # gRPC
      - "4318:4318"   # HTTP
      - "8889:8889"   # Prometheus 메트릭

Tempo와 Grafana를 함께 띄우면 수집된 트레이스를 시각화할 수 있습니다.

YAML
  tempo:
    image: grafana/tempo:latest
    ports:
      - "3200:3200"

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_AUTH_ANONYMOUS_ENABLED=true

Micrometer vs OpenTelemetry

기준MicrometerOpenTelemetry
범위메트릭 중심메트릭 + 트레이싱 + 로그
Spring 통합네이티브Micrometer 브릿지
성숙도높음빠르게 성장 중
벤더 중립다수 백엔드 지원CNCF 표준
추천메트릭 수집분산 추적이 필요할 때

Spring Boot 3에서는 Micrometer가 OpenTelemetry의 브릿지 역할을 하므로, Micrometer API를 사용하면서 OpenTelemetry 백엔드로 데이터를 보내는 것 이 가장 자연스러운 방식입니다.

로그와 트레이스 연결

MDC에 Trace ID를 넣으면 로그에서 특정 요청의 전체 흐름을 추적할 수 있습니다.

YAML
# application.yml
logging:
  pattern:
    console: "%d{HH:mm:ss.SSS} [%thread] [traceId=%X{traceId}] %-5level %logger{36} - %msg%n"

Micrometer Tracing이 자동으로 MDC에 traceIdspanId를 넣어줍니다.

PLAINTEXT
10:23:45.123 [http-nio-8080-1] [traceId=abc123] INFO OrderService - 주문 생성 시작
10:23:45.234 [http-nio-8080-1] [traceId=abc123] INFO PaymentService - 결제 처리
10:23:45.345 [http-nio-8080-1] [traceId=abc123] INFO OrderService - 주문 완료

Grafana에서 traceId=abc123으로 검색하면 로그, 트레이스, 메트릭을 하나의 뷰에서 확인할 수 있습니다.

운영 환경 설정

샘플링 전략

모든 요청을 추적하면 오버헤드가 큽니다.

YAML
management:
  tracing:
    sampling:
      probability: 0.1  # 10%만 샘플링

# 또는 커스텀 샘플러
# 에러가 발생한 요청은 항상 추적, 나머지는 10%

리소스 속성

YAML
# 서비스 식별 정보
management:
  opentelemetry:
    resource-attributes:
      service.name: order-service
      service.version: 1.2.0
      deployment.environment: production

주의할 점

1. Java Agent 방식과 SDK 방식을 동시에 사용하면 Span이 중복 생성된다

Java Agent(-javaagent)가 자동으로 HTTP Span을 생성하는데, 코드에서 Micrometer Tracing도 동일 구간의 Span을 생성하면 같은 요청에 대해 두 개의 Span이 만들어집니다. 한 가지 방식으로 통일하거나, Agent의 특정 계측을 비활성화하여 충돌을 방지해야 합니다.

2. OTel Collector가 다운되면 텔레메트리 데이터가 유실된다

애플리케이션이 Collector에 데이터를 전송하는데, Collector가 장애 상태이면 전송이 실패하여 Trace와 메트릭이 유실됩니다. Collector를 고가용성으로 운영하고, 애플리케이션 측에서 전송 실패 시 재시도하거나 로컬 버퍼링 전략을 구성해야 합니다.

3. 모든 요청을 추적하면 네트워크 대역폭과 저장소 비용이 급증한다

샘플링 비율을 1.0(100%)으로 설정하면 모든 요청의 Trace 데이터가 Collector를 거쳐 백엔드에 저장됩니다. 초당 수천 건의 요청이 있는 서비스에서는 Trace 데이터만으로 GB 단위의 저장소가 필요해집니다. 운영 환경에서는 0.010.1(110%) 수준으로 시작하고, 에러 요청은 항상 추적하는 커스텀 샘플러를 사용하세요.

정리

  • OpenTelemetry 는 트레이스, 메트릭, 로그를 통합하는 CNCF 표준입니다.
  • Spring Boot 3에서는 Micrometer를 통해 OpenTelemetry와 통합 합니다. micrometer-tracing-bridge-otel을 사용하세요.
  • 자동 계측 으로 HTTP, DB, 메시징이 자동 추적되고, 수동 계측 으로 비즈니스 로직 내부를 세밀하게 추적합니다.
  • OTel Collector 를 중간에 두면 데이터를 가공하고 여러 백엔드에 분배할 수 있습니다.
  • 로그에 Trace ID 를 포함시키면 로그, 트레이스, 메트릭을 하나로 연결하여 분석할 수 있습니다.
  • 운영 환경에서는 샘플링 비율 을 적절히 조정하여 오버헤드를 관리하세요.
댓글 로딩 중...