OpenTelemetry의 API/SDK/Collector 3계층 구조, W3C TraceContext 기반 컨텍스트 전파, 리소스와 시맨틱 컨벤션, 배포 패턴을 심층적으로 분석합니다.
OpenTelemetry 아키텍처의 가장 핵심적인 설계 결정은 API와 SDK의 분리입니다. 이 분리는 라이브러리 개발자와 애플리케이션 개발자의 역할을 명확히 구분합니다.
API 계층은 텔레메트리 데이터를 생성하기 위한 인터페이스만을 정의합니다. 구현 로직은 포함하지 않으며, 의존성이 최소화되어 있습니다. 라이브러리 작성자는 이 API만 사용하여 계측 코드를 작성합니다.
from opentelemetry import trace
# API만 사용 -- SDK가 없으면 no-op(아무 동작 안 함)
tracer = trace.get_tracer("my-library", "1.0.0")
with tracer.start_as_current_span("process-request") as span:
span.set_attribute("http.method", "GET")
span.set_attribute("http.url", "https://api.example.com/users")
# 비즈니스 로직 수행SDK가 등록되지 않으면 API 호출은 **no-op(무동작)**으로 처리됩니다. 이 덕분에 라이브러리는 OTel API에 의존해도 성능 오버헤드가 없고, 최종 애플리케이션에서 SDK 설치 여부를 자유롭게 결정할 수 있습니다.
SDK는 API의 실제 구현체로, 텔레메트리 데이터의 처리, 샘플링, 배치, 내보내기를 담당합니다. 애플리케이션의 진입점(entry point)에서 SDK를 초기화하고, 필요한 설정을 주입합니다.
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
# 리소스 정의 -- 이 텔레메트리가 어디서 오는지 식별
resource = Resource.create({
"service.name": "user-service",
"service.version": "2.1.0",
"deployment.environment": "production",
})
# SDK 초기화
provider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="http://collector:4317"))
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)분산 추적의 핵심은 **컨텍스트 전파(Context Propagation)**입니다. 서비스 A에서 시작된 요청이 서비스 B, C를 거칠 때, 동일한 Trace ID를 유지하여 하나의 트레이스로 연결해야 합니다.
OpenTelemetry는 W3C TraceContext 표준을 기본 전파 형식으로 사용합니다. HTTP 요청 시 두 개의 헤더를 통해 컨텍스트를 전달합니다.
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
tracestate: congo=t61rcWkgMzEtraceparent 헤더는 네 부분으로 구성됩니다.
| 필드 | 값 | 설명 |
|---|---|---|
| version | 00 | 프로토콜 버전 |
| trace-id | 4bf92f...4736 | 128비트 트레이스 식별자 |
| parent-id | 00f067...02b7 | 64비트 부모 스팬 식별자 |
| trace-flags | 01 | 샘플링 플래그 (01 = 샘플링됨) |
tracestate 헤더는 벤더별 추가 정보를 키-값 쌍으로 전달합니다. 여러 벤더의 도구가 공존하는 환경에서 각자의 메타데이터를 유지할 수 있게 합니다.
Baggage는 트레이스 컨텍스트와 함께 전파되는 사용자 정의 키-값 쌍입니다. 예를 들어, 요청을 보낸 사용자의 테넌트 ID나 기능 플래그 값을 서비스 간에 전달할 수 있습니다.
from opentelemetry import baggage, context
# Baggage 설정
ctx = baggage.set_baggage("tenant.id", "acme-corp")
ctx = baggage.set_baggage("feature.flag", "new-checkout", context=ctx)
# 다운스트림 서비스에서 Baggage 읽기
tenant = baggage.get_baggage("tenant.id")Baggage는 모든 다운스트림 서비스로 전파되므로, 민감한 정보(사용자 개인정보, 인증 토큰 등)를 Baggage에 담지 마세요. 또한 Baggage 크기가 커지면 모든 HTTP 요청의 헤더 크기가 증가하므로, 필수적인 데이터만 전달해야 합니다.
OpenTelemetry는 여러 전파 형식을 지원하며, 필요에 따라 조합할 수 있습니다.
from opentelemetry.propagators.composite import CompositePropagator
from opentelemetry.propagators.b3 import B3MultiFormat
from opentelemetry.propagate import set_global_textmap
from opentelemetry.propagators.textmap import DefaultTextMapPropagator
# W3C TraceContext + B3 동시 지원 (Zipkin 호환)
propagator = CompositePropagator([
DefaultTextMapPropagator(), # W3C TraceContext
B3MultiFormat(), # B3 (Zipkin 호환)
])
set_global_textmap(propagator)**리소스(Resource)**는 텔레메트리 데이터를 생성하는 엔티티에 대한 불변 속성 집합입니다. 쉽게 말해 "이 데이터가 어디서 왔는지"를 식별하는 메타데이터입니다.
from opentelemetry.sdk.resources import Resource
from opentelemetry.semconv.resource import ResourceAttributes
resource = Resource.create({
ResourceAttributes.SERVICE_NAME: "payment-service",
ResourceAttributes.SERVICE_VERSION: "3.2.1",
ResourceAttributes.SERVICE_NAMESPACE: "checkout",
ResourceAttributes.DEPLOYMENT_ENVIRONMENT: "production",
ResourceAttributes.HOST_NAME: "payment-pod-7f8b9",
ResourceAttributes.CLOUD_PROVIDER: "aws",
ResourceAttributes.CLOUD_REGION: "ap-northeast-2",
})리소스는 SDK 초기화 시 한 번 설정되며, 해당 SDK가 생성하는 모든 텔레메트리 데이터에 자동으로 첨부됩니다. 이를 통해 수천 개의 스팬 중에서 특정 서비스, 특정 환경, 특정 호스트의 데이터만 필터링할 수 있습니다.
**인스트루멘테이션 스코프(Instrumentation Scope)**는 텔레메트리를 생성하는 계측 라이브러리를 식별합니다. Tracer나 Meter를 생성할 때 지정하는 이름과 버전이 바로 이 스코프입니다.
# 각 라이브러리/모듈이 고유한 스코프를 가짐
http_tracer = trace.get_tracer("http-instrumentation", "0.42.0")
db_tracer = trace.get_tracer("sqlalchemy-instrumentation", "0.42.0")
custom_tracer = trace.get_tracer("my-app.checkout", "1.0.0")이를 통해 "어떤 계측 라이브러리가 이 스팬을 생성했는가"를 추적하고, 특정 라이브러리의 계측을 선택적으로 비활성화할 수 있습니다.
시맨틱 컨벤션은 텔레메트리 데이터의 속성 이름과 값을 표준화하는 규칙입니다. 이 규칙이 없으면 같은 HTTP 상태 코드를 누구는 http.status, 누구는 status_code, 누구는 response.code로 기록하여 상관관계 분석이 불가능해집니다.
| 네임스페이스 | 속성 예시 | 설명 |
|---|---|---|
http | http.request.method, http.response.status_code | HTTP 요청/응답 |
db | db.system, db.statement | 데이터베이스 |
rpc | rpc.system, rpc.method | RPC 호출 |
messaging | messaging.system, messaging.operation | 메시지 큐 |
server | server.address, server.port | 서버 정보 |
url | url.full, url.path | URL 정보 |
시맨틱 컨벤션은 지속적으로 진화하고 있으며, 2026년 기준으로 HTTP, 데이터베이스, 메시징 관련 컨벤션이 Stable 상태입니다. 최신 명세는 opentelemetry.io/docs/specs/semconv에서 확인할 수 있습니다.
from opentelemetry.semconv.trace import SpanAttributes
with tracer.start_as_current_span("http-request") as span:
# 시맨틱 컨벤션에 따른 표준 속성 설정
span.set_attribute(SpanAttributes.HTTP_REQUEST_METHOD, "POST")
span.set_attribute(SpanAttributes.URL_FULL, "https://api.example.com/orders")
span.set_attribute(SpanAttributes.HTTP_RESPONSE_STATUS_CODE, 201)
span.set_attribute(SpanAttributes.SERVER_ADDRESS, "api.example.com")
span.set_attribute(SpanAttributes.SERVER_PORT, 443)OTel Collector를 배포하는 두 가지 기본 패턴이 있습니다. 실무에서는 이 두 패턴을 조합하여 사용하는 경우가 많습니다.
Agent 모드에서는 각 애플리케이션 인스턴스 옆에 Collector가 함께 실행됩니다. Kubernetes에서는 DaemonSet으로 각 노드에 배포하거나, Pod의 사이드카 컨테이너로 실행합니다.
장점
단점
Gateway 모드에서는 독립적인 Collector 클러스터가 모든 텔레메트리 데이터를 중앙에서 수신합니다.
장점
단점
실무에서는 Agent 모드로 각 노드에서 1차 수집/배치 처리를 하고, Gateway로 전송하여 중앙에서 Tail Sampling, 라우팅, 변환을 수행하는 2계층 구조를 권장합니다. 이 조합은 두 패턴의 장점을 모두 취할 수 있습니다.
**OTLP(OpenTelemetry Protocol)**는 텔레메트리 데이터 전송을 위한 표준 프로토콜입니다. gRPC와 HTTP/JSON 두 가지 전송 방식을 지원합니다.
| 전송 방식 | 포트 | 특징 |
|---|---|---|
| gRPC | 4317 | 고성능, 스트리밍 지원, 프로덕션 권장 |
| HTTP/protobuf | 4318 | 방화벽 친화적, 브라우저 지원 |
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"OTLP의 핵심 설계 원칙은 다음과 같습니다.
이번 장에서는 OpenTelemetry의 핵심 아키텍처를 살펴보았습니다. API와 SDK의 분리는 라이브러리 생태계의 호환성을 보장하고, W3C TraceContext 기반 컨텍스트 전파는 서비스 간 추적을 가능하게 합니다. 리소스와 시맨틱 컨벤션은 데이터의 일관성과 검색 가능성을 높이며, Agent와 Gateway 배포 패턴은 다양한 운영 환경에 대응합니다.
다음 장에서는 3대 신호 중 첫 번째인 분산 추적(Distributed Tracing)을 깊이 있게 다룹니다. 스팬의 내부 구조, 스팬 종류, 샘플링 전략을 학습하고 Python으로 직접 계측하는 실습을 진행합니다.
이 글이 도움이 되셨나요?
관련 주제 더 보기
스팬의 내부 구조와 종류, 부모-자식 관계, 샘플링 전략(Head/Tail/Rate)을 학습하고 Python으로 분산 추적을 직접 구현합니다.
로깅에서 APM, 관측 가능성으로 이어지는 모니터링의 진화를 살펴보고, OpenTelemetry가 탄생한 배경과 3대 신호, 벤더 중립의 가치를 이해합니다.
OpenTelemetry 메트릭의 종류(Counter, Gauge, Histogram), 카디널리티 관리, Exemplars를 통한 메트릭-트레이스 연결, Prometheus 호환을 학습합니다.