OTCA — 오픈텔레메트리 인증 어소시에이트 · API 와 SDK 의 조립 — 프로바이더·프로세서·계측기·로그 모델 · 이론
라이브러리는 API 만 알고, 애플리케이션이 SDK 를 끼운다
한 줄 요약
OpenTelemetry 클라이언트는 시그널마다 API·SDK·시맨틱 컨벤션·Contrib 네 종류의 패키지로 나뉩니다. 계측 코드는 API 만 의존하고, 애플리케이션 소유자가 SDK 를 설치해 프로바이더·프로세서·익스포터를 조립합니다. 이 분리(composability)가 왜 필요한지, SDK 파이프라인이 어떻게 이어지는지, 그리고 코드를 고치지 않는 에이전트가 같은 API·SDK 를 어떻게 끼워 넣는지를 [스펙 개요](https://opentelemetry.io/docs/specs/otel/overview/)와 [클라이언트 설계 원칙](https://opentelemetry.io/docs/specs/otel/library-guidelines/)을 따라 설명합니다.
왜 이게 필요했나
계측은 문서의 표현대로 "횡단 관심사(cross-cutting concern)" 입니다. 웹 프레임워크, DB 클라이언트, 메시지 큐 라이브러리 안에 관측 코드가 섞여 들어갑니다. 그런데 그 라이브러리를 쓰는 애플리케이션이 어떤 백엔드로 텔레메트리를 보낼지, 아예 안 보낼지는 라이브러리 작성자가 알 수 없습니다. 라이브러리가 특정 구현체를 끌고 들어오면 그것을 원하지 않는 애플리케이션까지 무거워집니다.
설계 원칙 문서는 그래서 세 가지를 요구합니다. API 는 구현과 분명히 분리되어야 하고, 서드파티 라이브러리는 API 에만 의존해야 하며, 최종 애플리케이션 개발자가 SDK 를 어떻게 설정할지 — 또는 아예 안 쓸지 — 를 결정할 수 있어야 합니다. 이를 위해 API 와 SDK 는 독립된 아티팩트로 제공되어야 한다(MUST) 고 적습니다.
어떻게 동작하나
API 의 최소 구현
API 패키지는 스스로 완결됩니다. SDK 가 없어도 애플리케이션이 빌드되고 실행되어야 하므로, API 안에 최소 구현(no-op)이 들어 있습니다. 문서는 이 최소 구현의 반환값이 유효해야 한다고 강조합니다 — createSpan() 은 실패하지 않고 null 이 아닌 Span 을 돌려주어야 하며, 호출자는 지금 최소 구현이 돌고 있는지 신경 쓸 필요가 없어야 합니다. 그리고 성능 부담이 거의 없어야 합니다. 이것이 "계측된 라이브러리를 OpenTelemetry 를 쓰지 않는 애플리케이션에서도 쓸 수 있다" 는 요구를 만족시키는 방법이고, 프레임워크가 '계측판' 과 '비계측판' 을 따로 낼 필요를 없앱니다.
계측 작성자를 향한 규칙은 단호합니다. "계측 작성자는 어떤 종류의 SDK 패키지도 직접 참조해서는 안 된다(MUST NOT). API 만 참조한다."
SDK 의 안쪽 — 프로바이더에서 익스포터까지
SDK 가 설치되면 최소 구현을 대체합니다. SDK 는 다시 둘로 나뉩니다 — 프로토콜과 무관한 공통 로직(배치, 프로세스 정보 붙이기 등)과, 프로토콜에 묶인 익스포터입니다. 익스포터는 최소 기능만 가져서 벤더가 쉽게 자기 프로토콜을 붙일 수 있게 합니다. 스펙이 SDK 에 요구하는 기본 익스포터는 OTLP(로그·메트릭·트레이스), 표준 출력, 인메모리(테스트용)이며, 메트릭에는 Prometheus, 트레이스에는 Zipkin 이 더해집니다. 벤더 전용 익스포터는 클라이언트에 넣지 않습니다.
조립의 뼈대는 시그널마다 같은 모양입니다.
| 시그널 | 프로바이더 | 만드는 것 | 기록 단위 | 프로세서 → 익스포터 |
| --- | --- | --- | --- | --- |
| 트레이스 | TracerProvider | Tracer | Span | SpanProcessor → SpanExporter |
| 메트릭 | MeterProvider | Meter → Instrument | Measurement | 집계 상태 → MetricReader/Exporter |
| 로그 | LoggerProvider | Logger | LogRecord | LogRecordProcessor → LogRecordExporter |
[트레이스 SDK 스펙](https://opentelemetry.io/docs/specs/otel/trace/sdk/)에 따르면 TracerProvider 를 만들 때 SpanProcessor 들, IdGenerator, SpanLimits, Sampler 를 설정합니다. 기본 샘플러는 ParentBased(root=AlwaysOn) 입니다. TracerProvider 의 Shutdown 은 등록된 모든 프로세서의 Shutdown 을 부르고, ForceFlush 도 마찬가지로 전파됩니다.
프로세서 체인
SpanProcessor 는 스팬 생명주기의 훅입니다 — 스팬이 시작될 때 OnStart, 끝나기 직전 OnEnding, 끝난 뒤 OnEnd, 그리고 Shutdown·ForceFlush. 등록된 순서대로 호출되며, 모든 프로세서의 OnEnding 이 끝난 뒤에야 OnEnd 가 시작됩니다. 내장 프로세서는 두 가지입니다.
- Simple processor: 스팬이 끝나는 즉시 익스포터로 넘깁니다.
- Batching processor: 끝난 스팬을 큐에 모았다가 묶어서 보냅니다. 매개변수는
maxQueueSize(기본 2048, 넘치면 스팬을 버림),scheduledDelayMillis(기본 5000),exportTimeoutMillis(기본 30000),maxExportBatchSize(기본 512,maxQueueSize이하여야 함)입니다. 큐가 배치 크기에 이르거나, 지연 시간이 지나거나,ForceFlush가 불리면 내보내고, 익스포터의Export호출은 동시에 겹치지 않도록 직렬화됩니다.
Tracer ──► Span(끝) ──► [SpanProcessor 1] ──► [SpanProcessor 2: Batching] ──► SpanExporter(OTLP) │ OnStart/OnEnding/OnEnd 훅[로그 SDK](https://opentelemetry.io/docs/specs/otel/logs/sdk/)도 같은 구조입니다. LoggerProvider 에 LogRecordProcessor 를 등록하고, Simple 또는 Batching 프로세서가 LogRecordExporter(예: OTLP) 로 넘깁니다. 내장 프로세서가 "배치와 변환" 을 맡는다고 스펙이 적습니다.
에이전트 — 코드를 고치지 않고 같은 것을 끼우기
[제로코드 계측 개념](https://opentelemetry.io/docs/concepts/instrumentation/zero-code/)은 에이전트가 하는 일을 "OpenTelemetry API 와 SDK 의 능력을 애플리케이션에 더하는 것" 이라고 정의합니다. 방법은 언어마다 다릅니다 — 바이트코드 조작, 몽키 패칭, eBPF. 계측되는 것은 여러분이 쓰는 라이브러리(요청·응답, DB 호출, 메시지 큐)이지 여러분의 코드가 아니며, 자기 코드를 계측하려면 코드 기반 계측이 필요합니다. 설정은 환경 변수와 언어별 수단으로 하고, 시작하려면 서비스 이름만 있으면 됩니다.
[Java 에이전트](https://opentelemetry.io/docs/zero-code/java/agent/getting-started/)는 opentelemetry-javaagent.jar 하나입니다. Java 8 이상의 JVM 에 -javaagent:path/to/opentelemetry-javaagent.jar 를 붙이면 바이트코드를 동적으로 주입해 많은 라이브러리에서 텔레메트리를 잡습니다. 설정은 -Dotel.service.name=... 같은 시스템 속성, OTEL_SERVICE_NAME·OTEL_TRACES_EXPORTER 같은 환경 변수, JAVA_TOOL_OPTIONS, 또는 otel.javaagent.configuration-file 로 지정한 속성 파일 중 어느 것으로도 할 수 있습니다.
[Python](https://opentelemetry.io/docs/zero-code/python/)은 몽키 패칭입니다. pip install opentelemetry-distro opentelemetry-exporter-otlp 로 API·SDK 와 두 도구를 받고, opentelemetry-bootstrap -a install 이 site-packages 를 훑어 설치된 패키지에 맞는 계측 라이브러리(예: flask → opentelemetry-instrumentation-flask)를 골라 설치합니다. 실행은 opentelemetry-instrument python myapp.py 이며, --traces_exporter console,otlp 같은 인자나 OTEL_* 환경 변수로 설정합니다. 문서는 자동 계측이 동작하려면 distro 패키지가 꼭 있어야 한다고 못 박습니다.
현장에서 만나는 모습
한 팀의 사내 HTTP 클라이언트 라이브러리가 SDK 를 직접 의존하고 있었습니다. 이 라이브러리를 쓰는 배치 잡은 텔레메트리를 보낼 곳이 없는데도 OTLP 익스포터가 뜨고, 종료 때 Shutdown 을 기다리느라 늦게 끝났습니다. 라이브러리를 API 의존으로 바꾸자 배치 잡에서는 no-op 이 되었고, 웹 서비스에서는 애플리케이션이 조립한 SDK 가 그대로 쓰였습니다.
다른 팀은 스팬이 간헐적으로 사라지는 문제를 겪었습니다. 트래픽이 몰릴 때 Batching processor 의 큐(기본 2048)가 넘쳐 스팬을 버린 것이었습니다. 큐를 키우는 것과 maxExportBatchSize·scheduledDelayMillis 를 조정해 내보내는 속도를 올리는 것 사이에서 저울질해야 했습니다.
다음 글에서 이어질 것
이어지는 글에서는 메트릭 계측기 일곱 종류와 temporality, 로그 레코드의 필드와 브리지 API, 그리고 스키마 URL 을 다룹니다. 그다음 퀴즈에서는 API 와 SDK 의 분리 이유, 프로세서 체인, Batching processor 의 기본값, 에이전트의 동작 방식을 확인합니다.