OpenTelemetry란?
OpenTelemetry(OTel)는 CNCF가 관리하는 관측 가능성(Observability) 표준으로, Traces, Metrics, Logs 세 가지 신호를 통합 수집하는 벤더 중립 프레임워크입니다. Spring Boot 3.x에서는 Micrometer Tracing을 통해 OpenTelemetry를 네이티브로 지원하며, 기존 Spring Cloud Sleuth를 완전히 대체합니다.
이 글에서는 Spring Boot + OpenTelemetry 연동, 분산 추적(Distributed Tracing) 설정, 커스텀 Span 생성, 그리고 Jaeger/Tempo 백엔드 연동까지 실전 중심으로 다루겠습니다.
Spring Cloud Sleuth → Micrometer Tracing
| 항목 | Sleuth (Spring Boot 2.x) | Micrometer Tracing (3.x) |
|---|---|---|
| 프로젝트 | Spring Cloud Sleuth | Micrometer Tracing + OTel Bridge |
| 추적 ID 전파 | B3 Propagation | W3C Trace Context (기본) |
| 백엔드 | Zipkin 중심 | OTel Collector → 모든 백엔드 |
| 자동 계측 | Spring 전용 | OTel Java Agent (범용) |
| 상태 | 유지보수 종료 | 활발한 개발 중 |
의존성 설정
// build.gradle.kts — Spring Boot 3.2+
dependencies {
// Micrometer Tracing + OTel Bridge
implementation("io.micrometer:micrometer-tracing-bridge-otel")
// OTel Exporter (OTLP 프로토콜)
implementation("io.opentelemetry:opentelemetry-exporter-otlp")
// Spring Boot Actuator (메트릭 + 헬스체크)
implementation("org.springframework.boot:spring-boot-starter-actuator")
// 자동 계측: JDBC, WebClient, RestClient 등
implementation("io.micrometer:micrometer-tracing")
}
// 또는 Spring Boot BOM으로 버전 관리
dependencyManagement {
imports {
mavenBom("io.opentelemetry:opentelemetry-bom:1.35.0")
}
}
application.yml 설정
# application.yml
management:
tracing:
sampling:
probability: 1.0 # 개발: 100%, 운영: 0.1 (10%)
propagation:
type: w3c # W3C Trace Context (기본)
otlp:
tracing:
endpoint: http://otel-collector:4318/v1/traces
metrics:
endpoint: http://otel-collector:4318/v1/metrics
endpoints:
web:
exposure:
include: health,metrics,prometheus
# 서비스 이름 설정 (추적 UI에 표시)
spring:
application:
name: order-service
# 로그에 traceId, spanId 자동 포함
logging:
pattern:
level: "%5p [${spring.application.name},%X{traceId},%X{spanId}]"
자동 계측 범위
Spring Boot + Micrometer Tracing은 아래 컴포넌트를 자동으로 계측합니다.
// 자동 계측 대상 (설정만으로 Span 자동 생성):
// ✅ Spring MVC 컨트롤러 (HTTP 요청/응답)
// ✅ RestClient / WebClient (외부 HTTP 호출)
// ✅ JDBC (SQL 쿼리)
// ✅ Spring Data JPA Repository
// ✅ @Async 비동기 메서드
// ✅ @Scheduled 스케줄러
// ✅ Spring Kafka / RabbitMQ
// ✅ Spring Security 필터
// 로그 출력 예시:
// INFO [order-service,6f3c4b2a1d8e9f0a,2c5d7e8f1a3b4c6d]
// OrderController: Creating order for user 42
//
// traceId: 6f3c4b2a1d8e9f0a (요청 전체 추적)
// spanId: 2c5d7e8f1a3b4c6d (개별 작업 단위)
커스텀 Span 생성
import io.micrometer.observation.annotation.Observed;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.Span;
@Service
@RequiredArgsConstructor
public class OrderService {
private final Tracer tracer;
private final PaymentClient paymentClient;
private final InventoryClient inventoryClient;
// 방법 1: @Observed 어노테이션 (간단)
@Observed(name = "order.process",
contextualName = "process-order",
lowCardinalityKeyValues = {"order.type", "standard"})
public OrderResult processOrder(OrderRequest request) {
validateOrder(request);
PaymentResult payment = paymentClient.charge(request);
inventoryClient.reserve(request.getItems());
return createOrder(request, payment);
}
// 방법 2: Tracer API (세밀한 제어)
public OrderResult processOrderManual(OrderRequest request) {
Span span = tracer.nextSpan()
.name("order.validate")
.tag("order.id", request.getOrderId())
.tag("user.id", String.valueOf(request.getUserId()))
.start();
try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
validateOrder(request);
span.event("validation.complete");
// 하위 Span 자동 연결
PaymentResult payment = paymentClient.charge(request);
span.event("payment.charged");
return createOrder(request, payment);
} catch (Exception e) {
span.error(e); // 에러 기록
throw e;
} finally {
span.end(); // 반드시 종료
}
}
}
서비스 간 Context 전파
// RestClient 사용 시 자동 전파 (Spring Boot 3.2+)
@Configuration
public class RestClientConfig {
@Bean
public RestClient restClient(RestClient.Builder builder) {
return builder
.baseUrl("http://payment-service:8080")
.build();
// Micrometer Tracing이 자동으로
// traceparent 헤더 추가
}
}
// HTTP 헤더로 전파되는 W3C Trace Context:
// traceparent: 00-6f3c4b2a1d8e9f0a3b4c5d6e7f8a9b0c-2c5d7e8f1a3b4c6d-01
// 버전-traceId-parentSpanId-flags
// Kafka 메시지에도 자동 전파
@Service
public class OrderEventPublisher {
private final KafkaTemplate<String, OrderEvent> kafka;
public void publishOrderCreated(Order order) {
// 메시지 헤더에 traceId가 자동 포함
kafka.send("order-events", order.getId(),
new OrderEvent("CREATED", order));
}
}
// Consumer에서 자동으로 trace context 복원
@KafkaListener(topics = "order-events")
public void handleOrderEvent(OrderEvent event) {
// 동일 traceId로 추적 가능!
log.info("Processing event: {}", event);
}
OTel Collector 구성
# otel-collector-config.yml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 5s
send_batch_size: 1024
# 민감 정보 필터링
attributes:
actions:
- key: db.statement
action: hash # SQL 해시 처리
- key: http.request.header.authorization
action: delete # 인증 헤더 제거
exporters:
# Jaeger로 내보내기
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true
# Grafana Tempo로 내보내기
otlp/tempo:
endpoint: tempo:4317
# Prometheus 메트릭
prometheus:
endpoint: 0.0.0.0:8889
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, attributes]
exporters: [otlp/jaeger, otlp/tempo]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheus]
Kubernetes 배포 구성
# OTel Collector를 DaemonSet 또는 Deployment로 배포
apiVersion: apps/v1
kind: Deployment
metadata:
name: otel-collector
spec:
replicas: 2
template:
spec:
containers:
- name: collector
image: otel/opentelemetry-collector-contrib:0.95.0
ports:
- containerPort: 4317 # gRPC
- containerPort: 4318 # HTTP
- containerPort: 8889 # Prometheus
volumeMounts:
- name: config
mountPath: /etc/otelcol-contrib/config.yaml
subPath: config.yaml
volumes:
- name: config
configMap:
name: otel-collector-config
---
# 서비스 환경변수로 Collector 주소 주입
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
template:
spec:
containers:
- name: app
env:
- name: MANAGEMENT_OTLP_TRACING_ENDPOINT
value: "http://otel-collector:4318/v1/traces"
- name: MANAGEMENT_TRACING_SAMPLING_PROBABILITY
value: "0.1" # 운영: 10% 샘플링
샘플링 전략
| 전략 | 설정 | 적합한 환경 |
|---|---|---|
| 전수 수집 | probability: 1.0 | 개발/스테이징 |
| 비율 샘플링 | probability: 0.1 | 일반 운영 (10%) |
| 에러 우선 | Tail Sampling (Collector) | 에러 추적 집중 |
| 경로 기반 | 커스텀 Sampler | /health 제외 등 |
마무리
Spring Boot 3.x + OpenTelemetry는 MSA 환경에서 분산 추적의 표준이 되었습니다. Micrometer Tracing Bridge를 통해 기존 Spring 생태계와 자연스럽게 통합되며, OTel Collector를 중간에 두면 Jaeger, Tempo, Datadog 등 어떤 백엔드로든 유연하게 전환할 수 있습니다. 운영 환경에서는 반드시 샘플링 비율 조정과 민감 정보 필터링을 설정하고, 로그의 traceId를 활용해 로그-트레이스 연계 분석을 구현하세요.