- Published on
OpenTelemetry 2026 심층 — OTLP·시맨틱 컨벤션·Collector 파이프라인·자동 계측의 표준화 전쟁이 끝난 자리
- Authors

- Name
- Youngju Kim
- @fjvbn20031
프롤로그 — 표준화 전쟁이 끝났다
2018년 옵저버빌리티 풍경을 떠올려 보자. OpenTracing과 OpenCensus가 평행 우주에서 싸우고 있었고, 트레이스 데이터를 보내려면 벤더마다 다른 SDK(Datadog, New Relic, Lightstep, Honeycomb…)를 깔아야 했다. 라이브러리 작성자는 절망했다. 자기 라이브러리에 계측을 박아 넣고 싶어도, 어느 SDK를 골라야 한다.
2019년 OpenTracing과 OpenCensus가 OpenTelemetry로 합쳐졌다. 그게 7년 전 일이다.
2026년 5월의 풍경은 다르다.
- OTLP는 사실상 유일한 옵저버빌리티 와이어 프로토콜이다. Tempo, Jaeger, Honeycomb, Datadog, New Relic, Dynatrace, Grafana Cloud, SigNoz — 전부 OTLP를 받는다. 벤더의 사설 프로토콜은 레거시 호환용으로만 남았다.
- 시맨틱 컨벤션 v1이 잠겼다. HTTP, 관계형 DB, 메시징, RPC, 시스템 메트릭의 속성 이름들이 더 이상 바뀌지 않는다 —
http.request.method,db.system.name,messaging.system. 대시보드를 짜는 사람이 한숨 돌렸다. - 로그 시그널이 GA가 됐다. 트레이스만 OTel이고 로그는 따로 보내는 시대가 끝났다.
- Profiles가 네 번째 시그널로 진입했다. CNCF 인큐베이션을 거쳐 2024년 말부터 OTLP/profiles로 들어오는 데이터가 늘기 시작했다.
- eBPF 자동 계측(Beyla, Coroot, OpenTelemetry eBPF Collector)이 'SDK 없이 트레이스가 나오는' 길을 열었다. 레거시 바이너리, 손 못 대는 서비스에 한 줄 트레이스가 가능해졌다.
요컨대 표준화 전쟁은 끝났다. 이제는 OTel을 어떻게 깔지의 문제다. 이 글은 OTLP 와이어 포맷부터 production Collector 파이프라인, 언어별 자동 계측, eBPF 경로의 트레이드오프까지 — 2026년 5월 기준 실제로 굴러가는 모양을 정확히 짚는다.
1장 · 풍경 — 왜 OpenTelemetry가 이겼는가
승부를 가른 결정은 셋이다.
- 벤더 중립 와이어 포맷. OTLP는 protobuf 스키마가 공개되어 있고, gRPC와 HTTP 둘 다 위에서 돈다. 어떤 벤더든 OTLP 엔드포인트만 열면 그 즉시 OTel 호환이 된다. 벤더 잠금이 약해진다 — 또는 정확히는, 잠금이 SDK가 아니라 백엔드(쿼리 언어, UI, 가격 정책)에 옮겨갔다.
- 시맨틱 컨벤션의 잠금. HTTP 요청을 어떻게 표현하는지(
http.request.method,url.full,http.response.status_code), DB 쿼리를 어떻게 표현하는지(db.system.name,db.query.text)가 합의됐다. 이게 없었다면 OTel은 그냥 또 하나의 SDK였을 거다. - CNCF 졸업. 2024년 OpenTelemetry는 Kubernetes에 이어 CNCF에서 가장 활발한 프로젝트 두 번째로 졸업한다. 의미가 컸다 — 회사 한 곳이 들고 가지 않는 진짜 중립 표준이라는 신호.
옛 풍경 vs 새 풍경
| 항목 | 2019년 이전 | 2026년 |
|---|---|---|
| 트레이스 표준 | OpenTracing + OpenCensus 평행 | OpenTelemetry 단일 |
| 와이어 프로토콜 | 벤더마다 다름 | OTLP/gRPC + OTLP/HTTP |
| 라이브러리 계측 | 벤더 SDK에 박음 | OTel API에 박고 SDK는 갈아 끼움 |
| 로그 | 별도 파이프라인(Fluentd 등) | OTel 시그널 하나 |
| 메트릭 | Prometheus 별도 진영 | OTLP + Prometheus 호환 |
| 자동 계측 | 일부 언어만 | 자바·파이썬·노드·고·루비·.NET·PHP·러스트 |
| 시맨틱 | 벤더별 | http.*, db.*, messaging.* 잠금 |
2장 · OTLP — 와이어 프로토콜이 가장 중요한 이유
OpenTelemetry의 핵심은 SDK가 아니라 OTLP다. OTLP는 트레이스·메트릭·로그·프로파일 데이터를 백엔드로 보내는 protobuf 스키마와 전송 프로토콜이다.
2.1 두 가지 트랜스포트
- OTLP/gRPC — protobuf over HTTP/2. 기본 포트 4317. 가장 효율적. 서버 환경의 기본값.
- OTLP/HTTP — protobuf 또는 JSON over HTTP/1.1. 기본 포트 4318. 브라우저, Lambda, 방화벽이 gRPC를 막는 환경에서 쓴다.
내용물(스키마)은 같다. 트랜스포트만 다르다. 이 분리가 중요하다 — 브라우저에서 직접 OTLP/HTTP로 트레이스를 보내는 게 가능해졌다.
2.2 와이어 포맷의 모양 (단순화)
message ExportTraceServiceRequest {
repeated ResourceSpans resource_spans = 1;
}
message ResourceSpans {
Resource resource = 1; // 서비스 정보 (service.name, deployment.environment...)
repeated ScopeSpans scope_spans = 2;
}
message ScopeSpans {
InstrumentationScope scope = 1; // 어떤 라이브러리가 만든 스팬인지
repeated Span spans = 2;
}
message Span {
bytes trace_id = 1;
bytes span_id = 2;
string name = 5;
fixed64 start_time_unix_nano = 7;
fixed64 end_time_unix_nano = 8;
repeated KeyValue attributes = 9; // http.request.method = "GET" 등
repeated Event events = 11;
repeated Link links = 13;
Status status = 15;
}
핵심은 Resource → Scope → Span의 세 단계 트리다. 같은 서비스의 같은 라이브러리에서 나온 스팬은 한 묶음으로 보내져 압축률이 좋다.
2.3 메트릭과 로그의 와이어 포맷
같은 패턴이 메트릭·로그·프로파일에 반복된다.
- 메트릭:
ResourceMetrics→ScopeMetrics→Metric(Gauge / Sum / Histogram / ExponentialHistogram / Summary) - 로그:
ResourceLogs→ScopeLogs→LogRecord - 프로파일:
ResourceProfiles→ScopeProfiles→Profile(pprof 호환 스키마)
2.4 OTLP는 푸시인가 풀인가
OTLP는 푸시다. SDK 또는 Collector가 백엔드로 보낸다. 이건 Prometheus(풀)와 가장 큰 철학적 차이.
OTel은 Prometheus 호환을 위해 두 다리를 다 걸친다. Collector에 prometheusreceiver(타깃을 스크래이프)와 prometheusexporter(Prometheus가 와서 가져가게)가 모두 있다. 현실에선 메트릭만 Prometheus로, 트레이스·로그는 OTLP로 푸시하는 하이브리드가 흔하다.
3장 · 시맨틱 컨벤션 — 왜 이게 OTel의 진짜 무기인가
기술적으로 OTLP가 가장 멋있지만, 운영자가 매일 만지는 건 시맨틱 컨벤션이다.
3.1 무엇이 잠겼는가
2024년 9월부터 시작된 v1 stable 잠금 작업이 2025~2026년 사이 다음을 stable로 만들었다.
- HTTP —
http.request.method,http.response.status_code,url.path,url.full,url.scheme,server.address,server.port,user_agent.original. - 데이터베이스 —
db.system.name,db.namespace,db.query.text,db.collection.name,db.operation.name. - 메시징 —
messaging.system,messaging.destination.name,messaging.operation.type,messaging.message.id. - RPC —
rpc.system,rpc.service,rpc.method. - 시스템 메트릭 —
system.cpu.utilization,system.memory.usage,process.runtime.*. - 리소스 —
service.name,service.version,service.instance.id,deployment.environment.name,host.name,os.type,cloud.provider,k8s.pod.name,k8s.namespace.name.
이게 stable로 잠겼다는 말은 — 이름이 더 이상 바뀌지 않는다는 뜻이다. 대시보드, 알람, 쿼리, 백엔드 통합 — 전부 안정해진다.
3.2 왜 잠금이 중요한가
OTel 초기에 잘 알려진 함정이 있었다. HTTP 속성 이름이 http.method였다가 http.request.method로 바뀌었다. 깔린 SDK는 모두 갈아 끼워야 했고, 대시보드는 다 깨졌다. 한 번이 아니라 여러 차례.
v1 잠금은 이 고통을 끝낸다. 이후의 변경은 v2 네임스페이스로 들어가고, v1은 보존된다. Kubernetes의 API 안정성 정책과 같은 원리.
3.3 시맨틱 컨벤션이 강제하는 것
- 벤더 간 이동성. 같은 트레이스를 Tempo에서 Honeycomb으로 옮겨도 쿼리가 그대로 동작.
- 공용 대시보드. Grafana 대시보드 마켓플레이스의 OTel 대시보드는 시맨틱 컨벤션 v1을 가정한다.
- 라이브러리 계측의 신뢰성. 자동 계측이 만드는 속성 이름이 표준이라 운영자가 예측 가능하다.
4장 · 트레이스·메트릭·로그·프로파일 — 네 시그널의 현재
4.1 트레이스 (Tracing)
가장 성숙한 시그널. OTel 1.0의 핵심이고, 2026년에는 새로 들어올 것이 거의 없다. 트레이스의 단위는 스팬(span) — 시작·종료 시각, 속성, 이벤트, 부모 스팬을 가진 작업의 단위.
트레이스 ID로 묶여 분산 시스템 전체를 한 호출의 흐름으로 본다. traceparent 헤더(W3C Trace Context)로 서비스 경계를 넘어 컨텍스트가 전파된다.
4.2 메트릭 (Metrics)
OTel 메트릭은 Prometheus와는 모델이 약간 다르다. Prometheus는 'gauge·counter·histogram·summary'였고, OTel은 'Counter·UpDownCounter·Gauge·Histogram·ExponentialHistogram·ObservableX'로 세분화됐다.
큰 변화 하나 — ExponentialHistogram이 표준에 들어왔다. 기존 Histogram이 미리 정한 bucket boundaries를 쓰는 데 비해, ExponentialHistogram은 동적으로 분포에 맞춰진다. P99 같은 분위수 계산 정확도가 큰 폭으로 좋아진다.
Prometheus 진영도 native histograms로 같은 길을 갔다. 두 진영이 같은 결론에 도달.
4.3 로그 (Logs)
2024년 후반에 GA 진입. 의미는 — 별도 로그 파이프라인을 안 갖춰도 OTel만으로 트레이스·메트릭·로그가 다 보내진다는 것.
핵심 매력은 트레이스 컨텍스트 자동 부착이다. 로그 레코드에 trace_id·span_id가 자동으로 박혀, 트레이스 → 로그, 로그 → 트레이스 점프가 한 번 클릭으로 된다. 옛날엔 로그 라이브러리에 직접 코드를 박아야 했던 일이다.
다만 2026년에도 Fluent Bit / Vector가 죽지는 않았다. OTel의 로그 receiver는 강하지만, 컨테이너 로그 파일 테일링·파싱은 Fluent Bit이 더 성숙하다. 현실의 production은 'Fluent Bit이 파일을 읽어 OTLP로 Collector에 보냄' 또는 'Collector의 filelogreceiver가 직접 읽음'의 두 갈래다.
4.4 프로파일 (Profiles)
2024년 CNCF 인큐베이션을 거쳐 네 번째 시그널로 진입. 트레이스·메트릭·로그에 이어 컨티뉴어스 프로파일링이 OTel의 시그널로 들어왔다.
기술적 핵심: pprof 호환 스키마가 OTLP로 들어왔다. 이게 중요한 이유 — Grafana Pyroscope·Polar Signals Parca 등 기존 컨티뉴어스 프로파일링 도구들이 OTLP/profiles를 받게 되면 한 파이프라인으로 네 시그널을 다 보낼 수 있다.
2026년 5월 기준 상태:
- 와이어 프로토콜(OTLP/profiles): stable에 가까운 베타.
- SDK 지원: Go·Python·Java의 일부 자동 계측이 프로파일 시그널을 실험적으로 출력.
- Collector 지원:
otlpreceiver·otlpexporter가 profiles 시그널을 받는다. 일부 백엔드는 아직 미지원. - eBPF: Parca·Pyroscope가 eBPF로 pprof를 만들어 OTLP로 내보낸다.
요컨대 GA에 가깝지만 아직 완전하지 않다. 시작할 때 'profiles 활성화 가능한 SDK·Collector 버전'을 챙겨두면 좋다.
5장 · Collector 아키텍처 — 옵저버빌리티의 nginx
OpenTelemetry Collector는 OTel 생태계에서 가장 중요한 단일 컴포넌트다. 그 역할이 옵저버빌리티 파이프라인에서 nginx가 HTTP 트래픽에 하는 역할과 같다 — 수신·변환·라우팅.
5.1 세 가지 컴포넌트 타입
+-------------+ +-------------+ +-------------+
| Receivers | ---> | Processors | ---> | Exporters |
+-------------+ +-------------+ +-------------+
otlp batch otlp
prometheus memory_limiter prometheusremotewrite
filelog attributes elasticsearch
jaeger k8sattributes loki
zipkin tail_sampling tempo
kafka filter kafka
transform
- Receivers — 데이터를 받는 어댑터. OTLP가 기본이지만, Prometheus 스크레이프·Jaeger·Zipkin·Fluent Forward·Kafka·SQS 등 무엇으로든 받을 수 있다.
- Processors — 받은 데이터를 변환·필터·보강한다. batch는 거의 필수, memory_limiter는 안전망, k8sattributes는 Kubernetes 메타데이터 자동 부착, tail_sampling은 샘플링 결정을 트레이스 완료 후로 미룬다.
- Exporters — 백엔드로 내보낸다. OTLP가 기본이지만 백엔드별 전용 익스포터도 많다.
5.2 Core vs Contrib
Collector 배포가 두 갈래다.
otelcol(core) — 가장 핵심적인 컴포넌트만. 보안 감사 받기 좋고 바이너리가 작음.otelcol-contrib(contrib) — 모든 커뮤니티 컴포넌트 포함. 99%의 production은 contrib을 쓴다.
권장: 처음엔 contrib으로 시작. 운영이 안정되면 OpenTelemetry Collector Builder (ocb)로 정확히 필요한 컴포넌트만 골라 커스텀 빌드.
5.3 파이프라인의 개념
Collector 설정은 pipelines로 묶인다. 시그널 타입별로 별도 파이프라인.
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, k8sattributes, batch]
exporters: [otlphttp/tempo]
metrics:
receivers: [otlp, prometheus]
processors: [memory_limiter, batch]
exporters: [prometheusremotewrite]
logs:
receivers: [otlp, filelog]
processors: [memory_limiter, k8sattributes, batch]
exporters: [otlphttp/loki]
같은 receiver를 여러 파이프라인이 공유할 수 있고, fan-out·fan-in이 자유롭다.
6장 · Production Collector 설정 — sidecar → gateway → backend
실제 production 패턴 중 가장 흔한 모양은 두 단 계층이다.
+----------------+ OTLP +-----------+ OTLP +-----------+
| App + SDK | -------> | Collector |---------->| Collector |
| (Pod sidecar) | | (sidecar) | | (gateway) |
+----------------+ +-----------+ +-----------+
|
| OTLP / proprietary
v
+------------------+
| Tempo / Jaeger / |
| Honeycomb / etc. |
+------------------+
- sidecar Collector (DaemonSet 또는 사이드카) — 로컬에서 빨리 받아 batch·재시도. 앱이 OTLP를 보내고 응답을 빨리 받아 자기 일을 한다.
- gateway Collector (Deployment) — 클러스터 단위로 모아 tail sampling·메타데이터 보강·라우팅·다중 백엔드 fan-out.
- backend — Tempo / Jaeger / SigNoz / Honeycomb / Datadog. OTLP를 받는 곳.
6.1 sidecar / agent Collector 설정 예
# otelcol-agent-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
prometheus:
config:
scrape_configs:
- job_name: 'self-metrics'
scrape_interval: 30s
static_configs:
- targets: ['localhost:8888']
processors:
memory_limiter:
check_interval: 1s
limit_mib: 400
spike_limit_mib: 100
k8sattributes:
auth_type: serviceAccount
passthrough: false
extract:
metadata:
- k8s.pod.name
- k8s.namespace.name
- k8s.node.name
- k8s.deployment.name
- k8s.cluster.uid
batch:
timeout: 200ms
send_batch_size: 8192
exporters:
otlp/gateway:
endpoint: otelcol-gateway.observability.svc.cluster.local:4317
tls:
insecure: true
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 30s
sending_queue:
enabled: true
num_consumers: 4
queue_size: 5000
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, k8sattributes, batch]
exporters: [otlp/gateway]
metrics:
receivers: [otlp, prometheus]
processors: [memory_limiter, k8sattributes, batch]
exporters: [otlp/gateway]
logs:
receivers: [otlp]
processors: [memory_limiter, k8sattributes, batch]
exporters: [otlp/gateway]
6.2 gateway Collector 설정 (tail sampling 포함)
gateway는 보통 트레이스 전체를 받고 결정한다 — 어떤 트레이스를 보존할지, 어디로 보낼지.
# otelcol-gateway-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
memory_limiter:
check_interval: 1s
limit_mib: 4000
spike_limit_mib: 800
tail_sampling:
decision_wait: 10s
num_traces: 100000
expected_new_traces_per_sec: 1000
policies:
- name: errors
type: status_code
status_code: { status_codes: [ERROR] }
- name: slow-requests
type: latency
latency: { threshold_ms: 1000 }
- name: sample-10pct
type: probabilistic
probabilistic: { sampling_percentage: 10 }
batch:
timeout: 1s
send_batch_size: 16384
exporters:
otlphttp/tempo:
endpoint: http://tempo-distributor.monitoring.svc.cluster.local:4318
prometheusremotewrite:
endpoint: http://mimir-distributor.monitoring.svc.cluster.local/api/v1/push
otlphttp/loki:
endpoint: http://loki-distributor.monitoring.svc.cluster.local:3100/otlp
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, tail_sampling, batch]
exporters: [otlphttp/tempo]
metrics:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [prometheusremotewrite]
logs:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlphttp/loki]
6.3 실전 운영 팁
- memory_limiter는 첫 번째 프로세서로. 메모리가 폭주하면 다른 모든 프로세서가 무용지물.
- tail sampling은 gateway에만. agent 레벨에서 하면 트레이스가 쪼개진다.
- k8sattributes는 agent에 두는 게 정석. gateway엔 pod 정보가 안 닿을 수 있음.
- batch 크기와 timeout은 백엔드 capacity에 맞춰 조정. 너무 작으면 RPS가 폭주, 너무 크면 latency가 늘어남.
- Collector 자체 메트릭을 스크레이프하라.
:8888/metrics에otelcol_processor_dropped_spans등이 있음.
7장 · 언어별 자동 계측 — 어디까지 자동으로 되나
OpenTelemetry의 가장 큰 매력 중 하나는 자동 계측(auto-instrumentation)이다. 코드 한 줄 안 건드리고 HTTP 핸들러·DB 드라이버·gRPC 클라이언트의 스팬이 나온다.
언어별 성숙도 차이가 크다.
7.1 Java — 가장 강력
opentelemetry-javaagent.jar이 사실상 산업 표준이다. JVM의 -javaagent 메커니즘으로 바이트코드를 런타임에 다시 쓰며, 100개 이상의 라이브러리(Spring, Hibernate, Apache HttpClient, Kafka, JDBC, Servlet, Reactor, gRPC, AWS SDK …)를 자동으로 계측한다.
java -javaagent:opentelemetry-javaagent.jar \
-Dotel.service.name=order-service \
-Dotel.exporter.otlp.endpoint=http://otelcol-agent:4317 \
-Dotel.exporter.otlp.protocol=grpc \
-Dotel.resource.attributes=deployment.environment.name=prod \
-jar app.jar
이게 끝이다. 코드 변경 0줄. Spring Boot 컨트롤러, 모든 JDBC 호출, Kafka producer·consumer의 스팬이 자동으로 나온다. 트레이스 컨텍스트 전파도 자동.
Java가 best-in-class인 이유 — JVM이 런타임 바이트코드 조작이 가장 자유롭고, OTel Java 팀이 가장 크고, Datadog Java agent의 노하우가 그대로 OTel로 합쳐졌다.
7.2 Python — opentelemetry-instrument
Python은 opentelemetry-instrument 런처와 opentelemetry-distro 패키지로 자동 계측한다. monkey-patching으로 라이브러리를 감싸는 방식.
pip install opentelemetry-distro opentelemetry-exporter-otlp \
opentelemetry-instrumentation-flask \
opentelemetry-instrumentation-requests \
opentelemetry-instrumentation-psycopg2
opentelemetry-bootstrap -a install # 깔린 라이브러리 자동 감지
OTEL_SERVICE_NAME=order-service \
OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcol-agent:4317 \
OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=prod \
opentelemetry-instrument python app.py
지원 라이브러리는 Flask, Django, FastAPI, Starlette, requests, urllib3, httpx, psycopg2, asyncpg, SQLAlchemy, Redis, pymongo, celery, kafka-python, boto3 등. Java만큼은 아니지만 일상적인 라이브러리는 거의 커버된다.
7.3 Node.js — require hook 기반
Node는 @opentelemetry/auto-instrumentations-node로 require hook을 걸어 자동 계측한다.
// tracing.js ← 앱 진입점보다 먼저 로드돼야 한다
const { NodeSDK } = require('@opentelemetry/sdk-node')
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc')
const sdk = new NodeSDK({
serviceName: 'order-service',
traceExporter: new OTLPTraceExporter({
url: 'http://otelcol-agent:4317',
}),
instrumentations: [getNodeAutoInstrumentations()],
})
sdk.start()
실행:
node --require ./tracing.js app.js
ESM에서는 --import 플래그와 hook loader가 필요하다 (Node 20.6+의 register() API). CommonJS 환경이 자동 계측이 더 매끄럽다.
지원 라이브러리: Express, Koa, Fastify, Hapi, http, https, pg, mysql, redis, mongoose, ioredis, kafka.js, AWS SDK, GraphQL 등.
7.4 Go — 컴파일 타임 계측의 혁명
Go는 한참 동안 'OTel의 약점'이었다. Go는 monkey-patching이 어렵고, vtable이 없어 런타임 인터셉트가 깔끔하지 않다. 그래서 Go는 오랫동안 수동 계측만 가능했다.
2024~2025 사이에 두 길이 열렸다.
길 1: go build 시점에 코드 주입 — otel-go-instrumentation
go install github.com/open-telemetry/opentelemetry-go-instrumentation/cli@latest
# 빌드 시 자동 계측 주입
otel-instrument go build -o app ./...
빌드 도구가 표준 라이브러리와 인기 패키지(net/http, gRPC, database/sql 등)의 호출을 감싸는 래퍼를 자동 생성해 주입한다. 코드 변경 0줄.
길 2: eBPF로 외부에서 — Beyla
빌드 시 주입조차 안 한다. Beyla가 별도 프로세스로 떠서 eBPF로 커널이 보는 시스템 콜·소켓을 통해 트레이스를 만든다. 이쪽은 8장에서 자세히.
7.5 .NET, Ruby, PHP, Rust
- .NET —
OpenTelemetry.AutoInstrumentationNuGet 패키지. CoreCLR profiler API로 IL을 다시 쓴다. Java 다음으로 성숙. - Ruby —
opentelemetry-instrumentation-allgem. monkey-patch 기반. Rails, Sinatra, Rack 등. - PHP —
open-telemetry/opentelemetry-auto-laravel등. OPcache 확장이 필요한 케이스 있음. - Rust — 자동 계측 거의 없음.
tracing크레이트의 OTel 어댑터로 수동 계측이 표준.
7.6 자동 계측의 한계
자동 계측은 HTTP·DB·메시지·RPC의 경계에서만 본다. 비즈니스 로직의 의미 있는 단위 — '주문 생성', '결제 검증', '재고 차감' — 는 보이지 않는다.
production OTel의 정답은 자동 계측 + 손으로 박는 비즈니스 스팬이다. 자동 계측이 인프라 경계를 다 잡아 주고, 손으로 박는 스팬은 비즈니스 의미를 더한다.
8장 · eBPF 자동 계측 — SDK 없이 트레이스가 나오는 길
2024~2025 사이 가장 큰 변화 중 하나. eBPF로 SDK를 안 깔고도 트레이스가 만들어진다.
8.1 작동 원리
eBPF 자동 계측 도구는 별도 프로세스로 떠서, 커널의 시스템 콜·소켓 이벤트를 훅한다. HTTP 요청, gRPC 호출, DB 연결을 직접 관찰한다.
- 앱은 평범한 바이너리. SDK도 javaagent도 없다.
- eBPF 프로그램이
accept·connect·read·write등을 후크해 패킷을 디코드. - 디코드한 트랜잭션을 OTLP로 Collector에 보낸다.
8.2 도구들
- Grafana Beyla — Grafana Labs가 만든 오픈소스. Go·Java·Node·Python·Rust 모두 같은 방식으로 본다. 트레이스·메트릭 둘 다 출력. 2024년 GA.
- Coroot — Beyla보다 위에 있는 풀스택 옵저버빌리티 — eBPF로 수집하고 자기 UI를 가짐. OTel 호환.
- OpenTelemetry eBPF Collector — OTel 공식 진영. 처음엔 Splunk가 만든 것이 OTel로 기증됨. 시스템 메트릭과 네트워크 단의 트레이스를 본다.
- Pixie (Pixie Labs) — Kubernetes 전용. eBPF + 자기 쿼리 언어. OTel 출력 가능.
- Cilium Hubble — 네트워크 계층에 집중. Cilium의 옵저버빌리티 컴포넌트.
8.3 eBPF 길의 강점
- SDK 0줄. 레거시 바이너리, 손 못 대는 서드파티 서비스에 트레이스가 가능.
- 계측 누락 위험이 0. 라이브러리 버전이 OTel과 안 맞아도 보인다.
- 언어 무관. Beyla 하나로 Go·Java·Node·Python·Rust 트레이스가 같은 모양으로 나옴.
- 운영 압력이 낮음. 앱을 다시 배포하지 않아도 적용.
8.4 eBPF 길의 한계
- 분산 트레이스 컨텍스트 전파가 어려움. HTTP 헤더의
traceparent는 eBPF가 읽지만, 앱이 그것을 받아 내부 함수 호출까지 잇는 건 안 된다. 결과 — 서비스 단위 트레이스는 잘 나오지만 함수 단위 그래프는 부족. - 암호화된 트래픽. TLS 안의 HTTP/2를 보려면 uprobes로 라이브러리 함수를 후크해야 한다. 최근 도구들은 이걸 지원하지만 호환 매트릭스가 좁다.
- 커널 권한 필요. Pod이
CAP_BPF또는privileged가 필요. 보안 정책에 따라 막힐 수 있음. - 비즈니스 로직 0. '주문 생성' 같은 도메인 의미는 잡지 못한다.
8.5 eBPF + SDK 하이브리드
production 정답은 eBPF + SDK 하이브리드다.
- eBPF — 인프라 그림(서비스 간 호출, DB 쿼리, 외부 API 호출, 네트워크 latency).
- SDK — 비즈니스 의미(도메인 스팬, 커스텀 메트릭, 비즈니스 로그).
두 데이터를 같은 trace_id로 묶으려면 — 어렵다. 현실은 'eBPF 트레이스 그래프 + SDK 트레이스 그래프'가 따로 노는 경우가 많다. 2026년 5월 기준 이 부분이 가장 활발히 개선되는 중.
9장 · 리소스 검출 (Resource Detection)
OTel 신호의 모든 데이터는 Resource에 묶여 송신된다. 'service.name=order-service, deployment.environment.name=prod, host.name=node-01, k8s.pod.name=order-69b' 같은 메타데이터.
이걸 손으로 다 박을 수도 있지만, OTel SDK와 Collector에는 자동 리소스 검출기가 있다.
9.1 검출기 종류
- 환경 변수 —
OTEL_RESOURCE_ATTRIBUTES=service.name=order,deployment.environment.name=prod. - 프로세스 — pid, command, runtime version.
- 호스트 — hostname, OS, architecture.
- 컨테이너 — container.id (cgroup에서 추출).
- Kubernetes —
downwardAPI로 env vars 주입, 또는 Collector의k8sattributes프로세서가 pod IP로 보강. - 클라우드 — AWS EC2/ECS/EKS/Lambda, GCP GCE/GKE/Cloud Run, Azure VM/AKS의 IMDS를 호출해 인스턴스 메타데이터 검출.
9.2 우선순위
OTel 사양은 우선순위를 정해 둔다 — 환경 변수 < SDK 디폴트 < 명시적 코드 설정. 클라우드 검출기는 SDK 시작 시 자동으로 돈다.
9.3 production 패턴
env:
- name: OTEL_SERVICE_NAME
value: order-service
- name: OTEL_RESOURCE_ATTRIBUTES
value: "deployment.environment.name=prod,service.version=$VERSION"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: OTEL_RESOURCE_ATTRIBUTES_OTHER
value: "k8s.pod.name=$POD_NAME,k8s.node.name=$NODE_NAME"
또는 Collector의 k8sattributes 프로세서에 맡기는 게 더 깔끔. SDK 쪽은 service.name만 챙기고, 나머지는 Collector가 pod IP → API server 조회로 자동 보강.
10장 · 벤더 SDK에서 OTel SDK로 — 마이그레이션 이야기
2024~2025 사이 가장 많이 본 마이그레이션 패턴.
10.1 시나리오 — Datadog dd-trace에서 OTel로
이미 Datadog dd-trace로 트레이스를 보내는 코드베이스. OTel로 가고 싶다. 두 길.
길 1: dd-trace의 OTel API 호환 모드
dd-trace 자체가 OTel API를 받는 모드가 있다. 코드는 OTel API로 작성하지만, dd-trace가 와이어 포맷을 처리.
[코드: OTel API] -> [dd-trace agent: OTel API 받음] -> [Datadog 백엔드: dd-trace 포맷]
장점: 단계적 이동. 코드는 OTel API로 미리 갈아 두고, 백엔드는 천천히. 단점: dd-trace를 결국 떼지 않으면 의미가 적음.
길 2: OTel SDK 직출
[코드: OTel API] -> [OTel SDK] -> [OTLP] -> [OTel Collector] -> [Datadog OTLP 엔드포인트 또는 백엔드 갈아끼움]
장점: 진짜 OTel. 백엔드 갈아끼우기가 자유. 단점: 한 번에 옮겨야. dd-trace의 일부 자동 계측이 OTel에 아직 없는 경우 갭이 생긴다.
10.2 단계적 마이그레이션 순서
- OTel Collector 먼저 세움. dd-trace agent와 병행 운영.
- 새 서비스부터 OTel SDK. dd-trace는 새 서비스에 추가하지 않음.
- 트레이스 → 메트릭 → 로그 순으로 이동. 트레이스가 가장 매끄럽고, 로그는 dashboard 호환이 가장 까다롭다.
- dashboard 재작성. 시맨틱 컨벤션이 다르다 (
http.status_codevshttp.response.status_code). 한 번에 다 바꾸지 말고 새 dashboard부터. - dd-trace agent 제거. 모든 서비스가 OTel SDK로 옮겨진 뒤.
10.3 솔직한 트레이드오프
OTel SDK가 모든 면에서 벤더 agent보다 낫지는 않다.
| 항목 | OTel SDK | 벤더 SDK (예: dd-trace) |
|---|---|---|
| 자동 계측 폭 | 좋음 (Java best-in-class) | 매우 넓음 (오랜 누적) |
| 런타임 오버헤드 | 일반적으로 약간 더 큼 | 오래 튜닝되어 더 가볍기도 |
| 백엔드 자유도 | 매우 큼 | 한 벤더에 묶임 |
| 시맨틱 일관성 | 표준 시맨틱 컨벤션 | 벤더 고유 이름 |
| 진단·서포트 | 커뮤니티 | 벤더 SRE 지원 |
| AI/ML 워크플로 | 신생 (OTel GenAI 컨벤션) | 일부 벤더가 앞서 있음 |
요컨대 OTel은 잠금을 푸는 대신 약간의 런타임 비용을 가져간다. 이게 받아들일 만한가 — 답은 보통 'Yes'다. 백엔드 자유도가 SDK 미세 튜닝의 가치를 크게 능가하는 경우가 많다.
11장 · GenAI 시맨틱 컨벤션 — 다음에 잠겨야 할 영역
2025~2026 사이 OTel 진영에서 가장 활발한 작업 영역. LLM 호출의 추적을 위한 시맨틱 컨벤션이 잠금 직전까지 왔다.
핵심 속성(베타, 곧 stable 예정).
gen_ai.system—openai,anthropic,google,ollama, …gen_ai.request.model—claude-3.5-sonnet,gpt-4o, …gen_ai.usage.input_tokens/gen_ai.usage.output_tokens— 비용·rate-limit 추적.gen_ai.response.finish_reasons—stop,length,tool_calls, …gen_ai.operation.name—chat,tool_call,embedding,text_completion.- 이벤트 — 메시지 단위 입출력(선택, 비용·프라이버시 트레이드오프).
LangChain, LlamaIndex, OpenLLMetry, Arize Phoenix 등이 이 컨벤션을 따라간다. LLM 워크플로의 비용·latency·실패율을 표준화된 모양으로 보게 된다. 다음 한두 분기 안에 v1 stable로 잠길 가능성이 크다.
12장 · 흔한 실수와 안티 패턴
production에서 자주 보는 실수들.
12.1 SDK 직접 백엔드로 보내기
앱 SDK ----(OTLP)----> Datadog / Honeycomb
작은 환경에서는 동작한다. 큰 환경에서 문제 — 앱 재배포 없이 백엔드 정책을 못 바꾼다 (샘플링, 라우팅, 보강). 항상 Collector를 한 단 끼워야 한다.
12.2 head sampling으로 에러 트레이스 놓침
SDK가 1% 샘플링을 하면, 에러 트레이스의 99%도 같이 사라진다. 에러는 무조건 보존해야 하니 tail sampling(Collector에서)을 써야 한다.
12.3 trace ID·span ID를 직접 만듦
직접 16바이트 UUID를 만들고 trace_id로 박는 코드를 봤다 — 표준 트레이스 컨텍스트 전파와 안 맞는다. OTel API의 getCurrentSpan().spanContext()를 쓰라.
12.4 매 요청에 새 Tracer 생성
// 안 좋은 예
function handler(req) {
const tracer = trace.getTracer('app') // 매번 새로 받는 듯 보임
tracer.startSpan(...)
}
getTracer는 캐시되지만, 분명히 모듈 최상단에서 한 번만 받는 게 의도 표현이 깔끔하다.
12.5 batch 없이 OTLP 직출
batch 프로세서 없이 보내면 RPS만큼의 작은 요청이 나간다. batch 프로세서는 사실상 필수.
12.6 PII가 속성에 들어감
http.request.body나 user.email을 그대로 속성으로 박으면 옵저버빌리티 백엔드에 평문 PII가 쌓인다. attributes 프로세서로 마스킹하거나 아예 빼라.
12.7 ExponentialHistogram 안 쓰고 고정 bucket로 P99
기본 Histogram이 5ms / 10ms / 50ms / 100ms / 500ms 같은 고정 bucket을 쓰면 P99 계산이 부정확하다. ExponentialHistogram을 디폴트로.
12.8 Collector를 단일 인스턴스로 SPOF
agent는 DaemonSet, gateway는 HPA. 항상 다중 인스턴스. gateway가 죽으면 그 시각 모든 신호가 사라진다.
에필로그 — 표준이 깔린 자리에서 무엇을 할 것인가
OpenTelemetry는 2026년 5월 기준 표준화 전쟁에서 이긴 자리에 있다. 다만 'OTel을 쓰는가'는 더 이상 흥미로운 질문이 아니다. 흥미로운 질문은 다음이다.
- 얼마나 잘 깔았는가. agent + gateway 두 단인가, 단일 인스턴스인가. tail sampling이 있는가. memory_limiter가 첫 번째 프로세서인가.
- 시맨틱 컨벤션을 따르는가. 자동 계측만 믿지 않고 비즈니스 스팬도 시맨틱 규약으로 박는가.
- 자동 계측 + 손 계측의 균형. 인프라 경계는 자동, 비즈니스 의미는 손으로.
- eBPF를 어디에 쓰는가. 레거시·서드파티에만 쓰는가, 아니면 SDK 쪽이 전혀 안 되는 환경의 메인으로 쓰는가.
- profiles 시그널을 받을 준비. 백엔드와 SDK 버전이 받는가.
도입 체크리스트
- Collector를 agent + gateway 두 단으로 구성했는가.
- memory_limiter가 모든 파이프라인의 첫 번째 프로세서인가.
- tail sampling이 gateway에 있고 에러·느린 요청은 무조건 보존되는가.
- k8sattributes 프로세서가 agent에서 도는가.
- batch 프로세서가 모든 파이프라인에 있는가.
- 자동 계측이 깔린 언어는 javaagent / opentelemetry-instrument / require hook / 컴파일 타임 주입 중 무엇으로 도는가.
- 비즈니스 스팬에 시맨틱 컨벤션이 적용됐는가.
- PII가 속성에 들어가지 않게 attributes 프로세서가 마스킹하는가.
- Collector의 self-metrics를 스크레이프하는가 (
otelcol_processor_dropped_spans등). - ExponentialHistogram을 디폴트로 쓰는가.
- 백엔드가 GenAI 시맨틱 컨벤션을 받을 준비가 되었는가.
안티 패턴 정리
- SDK가 직접 백엔드로 — Collector를 한 단 끼우라.
- head sampling만 — 에러 트레이스를 잃는다. tail sampling 추가.
- batch 프로세서 없이 OTLP — RPS만큼의 작은 요청.
- memory_limiter를 마지막에 — 메모리가 폭주하면 의미 없다. 첫 번째로.
- PII가 그대로 속성에 — attributes 프로세서로 마스킹.
- 자동 계측만 믿고 비즈니스 스팬 0줄 — 인프라 경계만 보임.
- Collector를 단일 인스턴스 — SPOF. 다중 인스턴스 필수.
- 시맨틱 컨벤션을 무시한 커스텀 속성 — 대시보드 호환이 깨진다.
- 트레이스만 보내고 메트릭·로그는 다른 파이프라인 — OTel 시그널 세 개를 한 파이프라인으로 묶을 때 가장 큰 가치가 난다.
- profiles 시그널을 받을 백엔드가 없는데 SDK에서 켬 — 데이터가 어디로도 안 간다.
다음 글 예고
다음 글 후보: Collector 운영 깊게 — 파이프라인 부하·드롭·메트릭 카디널리티의 함정, OTel Profiles 실전 — eBPF로 Go 바이너리의 hotspot 잡기, GenAI 시맨틱 컨벤션으로 LLM 비용·latency·실패율을 단일 대시보드로 보는 법.
참고 / References
- OpenTelemetry 공식
- OpenTelemetry GitHub 조직
- OTLP 사양 (Specification)
- OTLP protobuf 스키마
- 시맨틱 컨벤션 v1
- 시맨틱 컨벤션 GitHub
- OpenTelemetry Collector
- otelcol-contrib 저장소
- OpenTelemetry Collector Builder (ocb)
- Java auto-instrumentation
- Python instrumentation
- Node.js auto-instrumentations
- Go auto-instrumentation (compile-time)
- .NET auto-instrumentation
- Grafana Beyla — eBPF auto-instrumentation
- Beyla GitHub
- Coroot
- OpenTelemetry eBPF Collector
- OpenTelemetry Profiles 사양
- Profiles 데이터 모델
- Grafana Tempo
- Jaeger
- SigNoz
- Honeycomb OTel 도입 가이드
- Datadog OTLP 수신 엔드포인트
- W3C Trace Context
- GenAI 시맨틱 컨벤션 (베타)
- OpenLLMetry
- Arize Phoenix
- CNCF OpenTelemetry 졸업 발표