마이크로서비스 환경에서 "이 요청이 어디서 느려지는가"를 확인하려면 서비스 간 요청 경로를 추적할 수 있어야 합니다. OpenTelemetry는 이 추적 데이터를 벤더 종속 없이 수집하는 오픈소스 표준입니다. Trace, Span, Context Propagation의 동작 원리를 이해하면 분산 시스템의 병목을 체계적으로 진단할 수 있습니다.
핵심 요약
- OpenTelemetry(OTel)는 Metrics, Logs, Traces를 수집하는 CNCF Graduated 오픈소스 표준입니다. 2026년 5월 CNCF 졸업(Graduated) 단계에 도달했습니다.
- 분산 트레이싱은 하나의 요청이 여러 서비스를 거치는 경로를 Trace ID로 연결하여 추적하는 기술입니다.
- OpenTelemetry는 계측(Instrumentation)과 백엔드(Storage/Analysis)를 분리합니다. SDK로 데이터를 생성하고, Collector로 처리한 뒤, 원하는 백엔드(Jaeger, Datadog, Tempo 등)로 전송합니다.
- W3C Trace Context 표준(
traceparent헤더)을 통해 서비스 간 Context를 전파합니다. - Collector는 Receiver → Processor → Exporter 파이프라인으로 구성되며, 배포 패턴(Agent, Gateway)에 따라 확장성과 운영 복잡도가 달라집니다.
1. 왜 필요한가
5개 마이크로서비스로 구성된 이커머스 시스템을 운영하고 있다고 가정합니다. 사용자가 "결제 완료까지 8초 이상 걸린다"고 신고합니다.
각 서비스의 로그를 확인하면:
- API Gateway: 요청 수신 → 정상 (50ms)
- Order Service: 주문 생성 → 정상 (100ms)
- Inventory Service: 재고 확인 → 정상 (80ms)
- Payment Service: 결제 요청 → ??? (로그에 에러 없음)
- Notification Service: 알림 발송 → 정상 (200ms)
개별 서비스는 에러를 남기지 않지만, 전체 응답 시간은 8초입니다. "어디에서 시간이 소요되는가?"를 파악하려면 하나의 요청이 서비스를 거치는 전체 경로를 한 화면에서 볼 수 있어야 합니다.
이것이 분산 트레이싱이 해결하는 문제입니다.
그런데 트레이싱을 도입하려면 또 다른 문제가 생깁니다:
| 문제 | 설명 |
|---|---|
| 벤더 종속 | Datadog SDK로 계측하면 나중에 Jaeger로 바꿀 때 전체 코드 수정 필요 |
| 팀별 도구 파편화 | A팀은 Zipkin, B팀은 Jaeger를 사용하면 Trace가 연결되지 않음 |
| 일관성 부재 | 서비스마다 다른 형식으로 데이터를 내보내면 중앙에서 분석 불가 |
OpenTelemetry는 이 문제들을 해결합니다. 계측은 OTel SDK 하나로 통일하고, 백엔드는 자유롭게 선택하거나 교체할 수 있습니다.
2. OpenTelemetry란 무엇인가
OpenTelemetry(OTel)는 Observability 데이터(Metrics, Logs, Traces)를 생성, 수집, 전송하기 위한 오픈소스 표준 프레임워크입니다.
배경
OpenTelemetry는 두 개의 CNCF 프로젝트가 합쳐져 탄생했습니다:
- OpenTracing (2016): 분산 트레이싱 API 표준
- OpenCensus (2018): Google이 주도한 메트릭 + 트레이싱 라이브러리
두 프로젝트가 겹치는 영역이 많아 2019년에 합쳐져 OpenTelemetry가 되었습니다. 2026년 5월 21일 CNCF Graduated 프로젝트로 승격되어 프로덕션 사용 준비가 완료된 성숙 단계에 도달했습니다.
OpenTelemetry가 하는 것과 하지 않는 것
| OTel이 하는 것 | OTel이 하지 않는 것 |
|---|---|
| 텔레메트리 데이터 생성 (SDK) | 데이터 저장 |
| 데이터 수집 및 처리 (Collector) | 데이터 분석/시각화 |
| 표준 프로토콜 정의 (OTLP) | 알림 발생 |
| 자동 계측 (Auto-instrumentation) | 대시보드 제공 |
| 다양한 백엔드로 내보내기 (Exporter) | 장애 대응 자동화 |
핵심 설계 원칙은 "계측과 백엔드의 분리"입니다. 애플리케이션 코드에서 데이터를 생성하는 부분(SDK)과, 그 데이터를 저장하고 분석하는 부분(Backend)을 독립적으로 선택할 수 있습니다.
지원 언어
OpenTelemetry SDK는 주요 언어를 지원합니다:
| 언어 | Traces 상태 | Metrics 상태 | Logs 상태 |
|---|---|---|---|
| Java | Stable | Stable | Stable |
| Python | Stable | Stable | Stable |
| Go | Stable | Stable | Stable |
| JavaScript/Node.js | Stable | Stable | Stable |
| .NET (C#) | Stable | Stable | Stable |
| C++ | Stable | Stable | Stable |
| Rust | Stable | Stable | Stable |
| PHP | Stable | Stable | Stable |
| Ruby | Stable | Stable | Stable |
| Swift | Stable | Stable | Experimental |
| Erlang/Elixir | Stable | Stable | Experimental |
Java, Python, .NET, PHP, JavaScript는 코드 수정 없이 자동 계측(Zero-code instrumentation)도 지원합니다.
3. 전체 아키텍처
OpenTelemetry의 전체 구조는 세 계층으로 나뉩니다:
계층별 역할
| 계층 | 구성 요소 | 역할 |
|---|---|---|
| 생성 (Instrumentation) | SDK, Auto-instrumentation | 애플리케이션에서 텔레메트리 데이터 생성 |
| 수집/처리 (Collection) | Collector (Receiver → Processor → Exporter) | 데이터를 받아서 가공하고 내보냄 |
| 저장/분석 (Backend) | Jaeger, Prometheus, Grafana, Datadog 등 | 데이터를 저장하고 시각화/분석 |
이 구조의 장점:
- 백엔드 교체가 자유로움: Jaeger에서 Grafana Tempo로 바꿔도 애플리케이션 코드를 수정하지 않음
- 데이터 처리를 중앙화: Collector에서 샘플링, 필터링, 메타데이터 추가를 한 곳에서 관리
- 언어에 구애받지 않음: Java, Python, Go 등 각 서비스 언어에 맞는 SDK를 사용하되, 출력 형식은 OTLP로 통일
4. 분산 트레이싱 핵심 개념
분산 트레이싱은 OpenTelemetry의 세 Signal(Traces, Metrics, Logs) 중 가장 먼저 안정화된 영역이며, 분산 시스템 디버깅의 핵심입니다.
Trace와 Span
| 용어 | 설명 | 비유 |
|---|---|---|
| Trace | 하나의 요청에 대한 전체 처리 경로 | 택배 배송의 전체 경로 (출발지 → 도착지) |
| Span | 하나의 작업 단위 | 각 중간 경유지에서의 처리 (집하 → 허브 → 배달) |
| Root Span | Trace의 첫 번째 Span (부모 없음) | 최초 출발지 |
| Child Span | 다른 Span에 의해 생성된 Span | 경유지에서 다음 경유지로 넘기는 과정 |
하나의 Trace는 여러 Span의 트리 구조입니다:
Trace (trace_id: abc-123)
├─ Span A: API Gateway (root span)
│ ├─ Span B: Order Service
│ │ ├─ Span D: DB Query (SELECT orders)
│ │ └─ Span E: Payment Service 호출
│ │ └─ Span F: 외부 PG API 호출
│ └─ Span C: Notification Service
Span의 구조
각 Span은 다음 정보를 포함합니다:
{
"name": "POST /api/orders",
"context": {
"trace_id": "5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "051581bf3cb55c13"
},
"parent_span_id": "e457b5a2e4d86bd1",
"start_time": "2026-06-08T10:15:30.123Z",
"end_time": "2026-06-08T10:15:30.456Z",
"duration_ms": 333,
"status": "OK",
"kind": "SERVER",
"attributes": {
"http.method": "POST",
"http.url": "/api/orders",
"http.status_code": 201,
"service.name": "order-service",
"db.system": "postgresql"
},
"events": [
{
"name": "order.validated",
"timestamp": "2026-06-08T10:15:30.200Z"
}
]
}
주요 필드:
| 필드 | 설명 |
|---|---|
| trace_id | 요청 전체를 식별하는 128bit ID (모든 서비스가 공유) |
| span_id | 개별 작업을 식별하는 64bit ID |
| parent_span_id | 이 Span을 호출한 부모 Span의 ID |
| kind | SERVER, CLIENT, PRODUCER, CONSUMER, INTERNAL 중 하나 |
| attributes | 키-값 쌍으로 된 메타데이터 (Semantic Conventions 기반) |
| events | Span 실행 중 발생한 시점별 이벤트 |
| status | OK, ERROR, UNSET |
Span Kind의 의미
| Kind | 설명 | 예시 |
|---|---|---|
| SERVER | 서버로서 요청을 수신 | HTTP 서버가 요청을 처리 |
| CLIENT | 클라이언트로서 요청을 발신 | HTTP 클라이언트가 다른 서비스 호출 |
| PRODUCER | 비동기 메시지를 생성 | Kafka Producer가 메시지 발행 |
| CONSUMER | 비동기 메시지를 소비 | Kafka Consumer가 메시지 처리 |
| INTERNAL | 내부 작업 (네트워크 호출 없음) | 함수 내부 로직 |
5. Context Propagation: 서비스 간 추적 연결
분산 트레이싱에서 가장 중요한 메커니즘은 Context Propagation입니다. 서비스 A가 서비스 B를 호출할 때 "이 요청은 같은 Trace의 일부"라는 정보를 전달해야 합니다.
W3C Trace Context 표준
OpenTelemetry는 W3C Trace Context 표준을 기본 전파 형식으로 사용합니다. HTTP 요청 시 traceparent 헤더에 Context를 담아 전달합니다.
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
^^-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-^^^^^^^^^^^^^^^^-^^
| | | |
| trace-id (128bit) span-id (64bit) flags
version (sampled)
| 필드 | 설명 |
|---|---|
| version | 항상 00 (현재 버전) |
| trace-id | 요청 전체를 식별하는 ID (32 hex chars) |
| span-id | 현재 Span ID (16 hex chars) — 수신 서비스가 parent로 사용 |
| flags | 01 = sampled (수집 대상), 00 = not sampled |
전파 과정 예시
- API Gateway: 요청 수신 → Trace ID가 없으면 새로 생성 →
traceparent헤더에 포함하여 다음 서비스 호출 - Order Service:
traceparent헤더에서 trace-id와 parent span-id를 추출 → 새 Span 생성 (parent = 수신한 span-id) → 다음 호출 시 자신의 span-id를 traceparent에 담아 전달 - Payment Service: 동일한 과정 반복
이렇게 모든 서비스가 같은 trace-id를 공유하면서 각자의 Span을 기록하면, 나중에 수집기에서 trace-id로 모든 Span을 조합하여 전체 경로를 재구성할 수 있습니다.
전파가 실패하는 경우
| 원인 | 증상 | 해결 |
|---|---|---|
| 미들웨어가 traceparent 헤더를 제거 | Trace가 서비스 경계에서 끊어짐 | 프록시/게이트웨이에서 헤더 보존 설정 |
| 비동기 메시징(Kafka)에서 전파 누락 | Consumer Span이 별도 Trace로 분리 | Message 헤더에 traceparent 포함 |
| 서비스가 OTel SDK를 사용하지 않음 | 해당 서비스의 Span이 누락 | SDK 적용 또는 프록시 레벨에서 Span 생성 |
| 레거시 시스템이 커스텀 헤더 사용 | X-B3-TraceId와 traceparent 혼재 | Bridge Propagator 설정 |
서비스 메시(Istio, Linkerd)를 사용하면 애플리케이션 코드 수정 없이 사이드카 프록시가 자동으로 traceparent 헤더를 전파합니다. 다만 애플리케이션 내부의 상세 Span(DB 쿼리, 비즈니스 로직)은 여전히 SDK로 계측해야 합니다.
6. OpenTelemetry Collector
Collector는 OpenTelemetry의 핵심 구성 요소입니다. 애플리케이션에서 생성한 텔레메트리 데이터를 수신하고, 가공하고, 원하는 백엔드로 내보내는 중간 에이전트 역할을 합니다.
왜 Collector를 사용하는가
SDK에서 직접 백엔드로 보내지 않고 Collector를 중간에 두는 이유:
| 이점 | 설명 |
|---|---|
| 백엔드 변경 시 코드 수정 불필요 | Collector 설정만 바꾸면 됨 |
| 중앙 집중 처리 | 샘플링, 필터링, 메타데이터 추가를 한 곳에서 관리 |
| 버퍼링/재시도 | 백엔드 장애 시 데이터 유실 방지 |
| 다중 백엔드 전송 | 하나의 데이터를 여러 백엔드로 동시에 전송 가능 |
| 리소스 절약 | 배치 처리로 네트워크 호출 횟수 감소 |
Collector 파이프라인 구조
Collector는 세 가지 핵심 컴포넌트로 구성됩니다:
Receiver: 데이터를 수신하는 입구
| Receiver | 프로토콜 | 용도 |
|---|---|---|
| otlp | gRPC, HTTP | OTel SDK에서 보내는 데이터 수신 (기본) |
| jaeger | Thrift, gRPC | 기존 Jaeger 에이전트에서 전환 시 |
| prometheus | Scrape | Prometheus 메트릭 수집 |
| filelog | 파일 읽기 | 로그 파일 수집 |
Processor: 데이터를 가공하는 중간 처리
| Processor | 역할 |
|---|---|
| batch | 데이터를 묶어서 일괄 전송 (네트워크 효율화) |
| filter | 조건에 맞지 않는 데이터 제거 (헬스체크 등) |
| tail_sampling | 조건부 수집 (에러 100%, 정상 10%) |
| attributes | 속성 추가/수정/삭제 (환경 정보, 팀 태그 등) |
| resource | 리소스 정보 추가 (service.name, k8s.namespace 등) |
| memory_limiter | 메모리 사용량 제한 (OOM 방지) |
Exporter: 데이터를 내보내는 출구
| Exporter | 대상 |
|---|---|
| otlp | OTLP 호환 백엔드 (Tempo, SigNoz, Datadog 등) |
| prometheus | Prometheus Remote Write |
| jaeger | Jaeger 백엔드 |
| loki | Grafana Loki |
| debug | 콘솔 출력 (디버깅용) |
설정 예시
# 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
filter:
traces:
span:
- 'attributes["http.route"] == "/health"'
tail_sampling:
decision_wait: 10s
policies:
- name: errors
type: status_code
status_code: {status_codes: [ERROR]}
- name: slow-traces
type: latency
latency: {threshold_ms: 3000}
- name: default
type: probabilistic
probabilistic: {sampling_percentage: 10}
exporters:
otlp/tempo:
endpoint: tempo:4317
tls:
insecure: true
prometheus:
endpoint: 0.0.0.0:8889
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, filter, tail_sampling, batch]
exporters: [otlp/tempo]
metrics:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [prometheus]
이 설정의 의미:
- OTLP gRPC(4317)와 HTTP(4318)로 데이터 수신
- 헬스체크 요청(
/health)은 필터링으로 제거 - 에러와 3초 이상 지연 Trace는 100% 수집, 나머지는 10% 샘플링
- Traces는 Grafana Tempo로, Metrics는 Prometheus로 전송
7. 배포 패턴
Collector를 어떻게 배포하느냐에 따라 운영 복잡도와 확장성이 달라집니다.
Agent 패턴
각 노드(또는 Pod)에 Collector를 배치하는 방식입니다. Kubernetes에서는 DaemonSet으로 배포합니다.
# DaemonSet으로 Agent 배포
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: otel-agent
spec:
selector:
matchLabels:
app: otel-agent
template:
spec:
containers:
- name: otel-collector
image: otel/opentelemetry-collector-contrib:0.115.0
ports:
- containerPort: 4317 # OTLP gRPC
- containerPort: 4318 # OTLP HTTP
장점:
- 로컬 네트워크로 데이터 전송 (지연 낮음)
- 노드별 리소스 메타데이터 자동 수집 가능
단점:
- 노드 수에 비례하여 Collector 인스턴스 증가
- Tail-based Sampling 불가 (전체 Trace를 한 Agent가 볼 수 없음)
Gateway 패턴
중앙에 Collector 클러스터를 배치하고, 모든 서비스가 이곳으로 데이터를 전송합니다.
장점:
- Tail-based Sampling 가능 (전체 Trace를 모아서 판단)
- 중앙 집중 관리 (설정 변경을 한 곳에서)
- 다중 백엔드 라우팅
단점:
- 단일 장애점(SPOF) 위험 → 고가용성 구성 필요
- 네트워크 홉 추가
Agent + Gateway 조합 (권장)
실무에서는 두 패턴을 조합합니다:
- Agent (DaemonSet): 각 노드에서 데이터 수집, 기본 배치 처리 후 Gateway로 전송
- Gateway (Deployment): 중앙에서 Tail-based Sampling, 메타데이터 보강, 다중 백엔드 라우팅
이 조합이 권장되는 이유:
- Agent가 로컬에서 빠르게 수집하여 애플리케이션 성능에 미치는 영향을 최소화
- Gateway에서 전체 Trace를 모아 지능적인 샘플링 결정
- Gateway를 수평 확장하여 트래픽 증가에 대응
8. 실무 적용 시나리오: Kubernetes에서 OTel 도입
스타트업의 Kubernetes 환경(EKS)에서 5개 마이크로서비스를 운영한다고 가정합니다. 현재 로그만 CloudWatch로 수집하고 있고, 서비스 간 지연 문제가 자주 발생합니다.
단계별 도입 전략
1단계 — Auto-instrumentation으로 시작 (코드 수정 최소)
Java/Python/Node.js 서비스라면 OTel Auto-instrumentation Agent를 사용하여 코드 수정 없이 기본 Span을 생성할 수 있습니다.
# Kubernetes에서 Java 서비스에 Auto-instrumentation 적용
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
template:
metadata:
annotations:
instrumentation.opentelemetry.io/inject-java: "true"
spec:
containers:
- name: order-service
image: order-service:v1.2
env:
- name: OTEL_SERVICE_NAME
value: "order-service"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://otel-agent:4317"
이것만으로 HTTP 요청/응답, DB 쿼리, gRPC 호출에 대한 Span이 자동 생성됩니다.
2단계 — 핵심 비즈니스 로직에 Manual Span 추가
Auto-instrumentation은 프레임워크 수준의 Span만 생성합니다. "결제 검증 → 포인트 차감 → PG 호출"처럼 비즈니스 로직 내부를 추적하려면 수동으로 Span을 추가합니다.
# Python 예시: 결제 처리 내부 추적
from opentelemetry import trace
tracer = trace.get_tracer("payment-service")
def process_payment(order_id, amount):
with tracer.start_as_current_span("validate_payment") as span:
span.set_attribute("order.id", order_id)
span.set_attribute("payment.amount", amount)
validate_card()
with tracer.start_as_current_span("call_pg_api") as span:
span.set_attribute("pg.provider", "nice-pay")
result = call_external_pg(amount)
span.set_attribute("pg.response_code", result.code)
return result
3단계 — 샘플링과 비용 최적화
트래픽이 많아지면 모든 Trace를 수집하는 것은 비용적으로 비현실적입니다. Gateway에서 Tail-based Sampling을 적용합니다:
- 에러가 발생한 Trace: 100% 수집
- 3초 이상 지연된 Trace: 100% 수집
- 정상 Trace: 10% 샘플링
4단계 — 대시보드와 알림 연동
수집된 Trace 데이터를 기반으로:
- Grafana에서 서비스 의존성 맵 시각화
- p99 응답시간 알림 설정
- 에러율 급증 시 관련 Trace로 바로 점프
9. Semantic Conventions: 표준화된 속성 이름
OpenTelemetry는 Span 속성(Attributes)의 이름을 표준화하여 서로 다른 언어, 서로 다른 팀이 만든 서비스의 데이터를 일관되게 분석할 수 있도록 합니다. 이를 Semantic Conventions라고 합니다.
주요 Semantic Conventions
HTTP 관련:
| 속성 | 설명 | 예시 |
|---|---|---|
| http.request.method | HTTP 메서드 | GET, POST |
| url.full | 전체 URL | https://api.example.com/orders |
| http.response.status_code | 응답 코드 | 200, 500 |
| server.address | 서버 호스트 | api.example.com |
데이터베이스 관련:
| 속성 | 설명 | 예시 |
|---|---|---|
| db.system | DB 종류 | postgresql, mysql, redis |
| db.operation.name | 작업 유형 | SELECT, INSERT |
| db.collection.name | 테이블/컬렉션 | orders, users |
Kubernetes 관련:
| 속성 | 설명 | 예시 |
|---|---|---|
| k8s.namespace.name | 네임스페이스 | production |
| k8s.pod.name | Pod 이름 | order-service-7b4d5c-xkf2 |
| k8s.deployment.name | Deployment 이름 | order-service |
| k8s.node.name | 노드 이름 | ip-10-0-1-42 |
Semantic Conventions를 따르면 서로 다른 서비스에서 온 데이터를 db.system = "postgresql"로 일관되게 필터링할 수 있습니다. 팀마다 database_type, db_kind, dbSystem 등 제각각 이름을 붙이면 분석이 어렵습니다.
10. OTLP: OpenTelemetry Protocol
OTLP(OpenTelemetry Protocol)는 텔레메트리 데이터를 전송하는 표준 프로토콜입니다. SDK → Collector, Collector → Backend 간 통신에 사용됩니다.
OTLP vs 다른 프로토콜
| 프로토콜 | 데이터 유형 | 특징 |
|---|---|---|
| OTLP gRPC | Traces, Metrics, Logs | 바이너리 인코딩, 양방향 스트리밍, 성능 최적 |
| OTLP HTTP | Traces, Metrics, Logs | HTTP/1.1 호환, 프록시 통과 용이 |
| Jaeger Thrift | Traces만 | 레거시, 점차 OTLP로 전환 |
| Zipkin JSON | Traces만 | 레거시, 경량 |
| Prometheus Remote Write | Metrics만 | Prometheus 생태계 호환 |
OTLP의 장점:
- Traces, Metrics, Logs를 하나의 프로토콜로 통합
- Protobuf 기반 바이너리 인코딩으로 네트워크 효율성 높음
- gRPC 사용 시 양방향 스트리밍과 헤더 압축 지원
새로 구성하는 환경이라면 OTLP gRPC를 기본으로 사용합니다. 로드밸런서나 프록시가 gRPC를 지원하지 않는 환경에서만 OTLP HTTP를 선택합니다. Jaeger, Zipkin 형식은 기존 시스템과의 호환이 필요한 경우에만 사용합니다.
11. 보안 고려사항
Trace 데이터에는 요청 URL, 쿼리 파라미터, 헤더 값이 포함될 수 있습니다. 민감 정보가 Span 속성에 기록되지 않도록 설계 단계에서 정책을 수립해야 합니다.
민감 정보 노출 위험
| 위험 | 예시 | 대응 |
|---|---|---|
| URL에 토큰 포함 | /api/users?token=abc123이 url.full 속성에 기록됨 |
URL 파라미터 마스킹 또는 경로만 기록 |
| 요청 헤더에 인증 정보 | Authorization 헤더가 Span 속성에 기록됨 | 민감 헤더 수집 제외 설정 |
| DB 쿼리에 사용자 데이터 | SELECT * FROM users WHERE email='user@email.com'이 db.statement에 기록됨 |
쿼리 난독화(Obfuscation) 적용 |
| Span 속성에 PII 포함 | user.email, user.phone을 커스텀 속성으로 기록 | PII 속성 사용 금지 정책 |
Collector에서의 보안 처리
processors:
attributes:
actions:
# 민감 속성 삭제
- key: http.request.header.authorization
action: delete
- key: user.email
action: delete
# URL 쿼리 파라미터 제거
- key: url.full
action: hash # 해시 처리
통신 보안
- SDK → Collector: 클러스터 내부 통신이면 mTLS 또는 서비스 메시로 암호화
- Collector → Backend: TLS 필수 (외부 SaaS 백엔드로 전송 시)
- Collector 엔드포인트 접근 제어: NetworkPolicy로 허용된 Pod만 접근 가능하도록 제한
12. 비용/운영 고려사항
분산 트레이싱의 데이터 볼륨은 트래픽에 비례하여 급증합니다. 초당 10,000 요청 × 5 서비스 × 평균 3개 Span = 초당 150,000 Span입니다. 샘플링 없이 100% 수집하면 저장 비용이 빠르게 증가합니다.
비용 구조
| 항목 | 비용 발생 요인 | 최적화 방향 |
|---|---|---|
| Collector 리소스 | CPU/메모리 사용량 | 배치 크기 조정, 불필요 데이터 필터링 |
| 네트워크 전송 | Span 수 × Span 크기 | 샘플링, 속성 수 제한 |
| 백엔드 저장 | 수집된 Span 총량 × 보존 기간 | 샘플링 비율 조정, 보존 정책 |
| 쿼리 비용 | 조회 빈도와 쿼리 복잡도 | 인덱싱 최적화 |
샘플링 전략 비교
| 전략 | 장점 | 단점 | 적합 환경 |
|---|---|---|---|
| Head-based (10%) | 구현 간단, 예측 가능한 비용 | 에러 Trace를 놓칠 수 있음 | 트래픽이 많고 비용 민감 |
| Tail-based (조건부) | 중요한 Trace를 놓치지 않음 | 메모리 사용량 높음, Gateway 필요 | 에러/지연 분석이 중요 |
| Head + Tail 조합 | 균형 잡힌 수집 | 구성 복잡도 증가 | 대부분의 운영 환경 |
운영 체크리스트
- [ ] Collector에 memory_limiter 설정 (OOM 방지)
- [ ] 백엔드 장애 시 Collector의 재시도 정책 확인
- [ ] 샘플링 비율을 트래픽 변화에 맞게 조정
- [ ] Span 속성 수와 크기 제한 (불필요하게 큰 데이터 방지)
- [ ] Collector 자체의 모니터링 (자신의 메트릭을 Prometheus로 노출)
- [ ] 보존 기간 정의: Trace 7~14일, 중요 Trace 30일
13. 기존 도구와의 관계
OpenTelemetry와 Jaeger/Zipkin
Jaeger와 Zipkin은 트레이싱 백엔드(저장/시각화)입니다. OpenTelemetry는 데이터 생성/수집 표준입니다. 경쟁이 아니라 보완 관계입니다.
[AS-IS] 서비스 → Jaeger SDK → Jaeger Backend
[TO-BE] 서비스 → OTel SDK → OTel Collector → Jaeger Backend (또는 Tempo, Datadog)
전환 시 장점:
- Jaeger SDK를 OTel SDK로 교체하면 나중에 Tempo나 Datadog으로 백엔드를 바꿀 수 있음
- Jaeger 자체도 OTLP 수신을 지원하므로 호환됨
OpenTelemetry와 Prometheus
Prometheus는 메트릭 수집/저장 도구입니다. OpenTelemetry는 Prometheus 형식의 메트릭도 수집할 수 있고(prometheus receiver), Prometheus로 내보낼 수도 있습니다(prometheus exporter).
- 기존 Prometheus 환경에 트레이싱을 추가하고 싶다면: OTel SDK + Collector로 Trace를 수집하고, 메트릭은 기존 Prometheus 유지
- 새 환경을 구축한다면: OTel SDK로 Metrics와 Traces를 모두 수집 → Collector에서 각각 Prometheus와 Tempo로 라우팅
OpenTelemetry와 Datadog/New Relic
상용 APM 도구들은 이미 OTLP 수신을 지원합니다. OTel SDK로 계측하고 Collector에서 Datadog Exporter로 전송하면 됩니다.
장점:
- 나중에 비용 문제로 오픈소스 백엔드로 전환할 때 코드 수정 없음
- 멀티 벤더 전략 가능 (Traces는 Datadog, Metrics는 Prometheus)
14. 자주 하는 실수
1. Collector 없이 SDK에서 직접 백엔드로 전송
초기에는 간단해 보이지만, 백엔드 변경 시 모든 서비스의 환경 변수를 수정해야 합니다. Collector를 중간에 두면 설정 한 곳만 바꾸면 됩니다.
2. 모든 속성을 무분별하게 추가
Span에 속성을 많이 추가할수록 데이터 크기가 커집니다. 분석에 실제로 필요한 속성만 추가하고, Semantic Conventions를 따릅니다.
3. 샘플링을 고려하지 않고 시작
개발 환경에서 100% 수집으로 시작했다가 프로덕션에서 트래픽이 늘어나면 비용이 폭발합니다. 처음부터 샘플링 정책을 설계합니다.
4. traceparent 전파를 확인하지 않음
SDK를 설치했지만 서비스 간 Trace가 연결되지 않는 경우가 많습니다. 프록시나 메시지 큐에서 traceparent 헤더가 전달되는지 확인해야 합니다.
5. Collector의 리소스 제한을 설정하지 않음
Collector에 memory_limiter를 설정하지 않으면, 백엔드 장애 시 데이터가 Collector에 쌓여 OOM이 발생합니다. 반드시 리소스 제한을 설정합니다.
6. Auto-instrumentation만으로 충분하다고 생각
Auto-instrumentation은 HTTP, DB, gRPC 같은 프레임워크 수준의 Span만 생성합니다. "결제 검증에 3초 걸렸다"를 확인하려면 비즈니스 로직에 Manual Span을 추가해야 합니다.
15. 정리
- OpenTelemetry는 Observability 데이터를 벤더 중립적으로 수집하는 CNCF Graduated 표준입니다. 계측(SDK)과 백엔드(Storage)를 분리하여 도구 교체를 자유롭게 합니다.
- 분산 트레이싱의 핵심은 Trace ID를 서비스 간 전파하여 하나의 요청 경로를 재구성하는 것입니다. W3C Trace Context(
traceparent헤더)가 표준 전파 형식입니다. - Collector는 Receiver → Processor → Exporter 파이프라인으로 구성됩니다. 샘플링, 필터링, 메타데이터 보강을 중앙에서 관리합니다.
- 배포 패턴은 Agent(DaemonSet) + Gateway(Deployment) 조합이 운영 환경에서 적합합니다.
- 비용 관리가 중요합니다. Tail-based Sampling(에러/지연 100% + 정상 10%)으로 중요한 데이터를 놓치지 않으면서 비용을 제어합니다.
- 신규 서비스는 처음부터 OTel SDK로 계측하는 것이 유리합니다. 기존 서비스는 Auto-instrumentation으로 시작하고 점진적으로 Manual Span을 추가합니다.
관련 글
- Observability란 무엇인가: Monitoring, Logging, Tracing의 차이
- CloudWatch vs Datadog vs Grafana: 모니터링 도구 선택 기준
- Prometheus + Grafana로 Kubernetes 모니터링 구성하기
- 알림 설계: 노이즈를 줄이고 의미 있는 알림만 받는 방법
참고 문서
'Observability' 카테고리의 다른 글
| 알림 설계: 노이즈를 줄이고 의미 있는 알림만 받는 방법 (0) | 2026.06.07 |
|---|---|
| Prometheus + Grafana로 Kubernetes 모니터링 구성하기 (0) | 2026.06.06 |
| CloudWatch vs Datadog vs Grafana: 모니터링 도구 선택 기준 (0) | 2026.06.01 |
| Observability란 무엇인가: Monitoring, Logging, Tracing의 차이 (0) | 2026.05.31 |