OpenTelemetry는 관측성의 어디를 표준화하는가
OpenTelemetry의 API, SDK, Collector, OTLP, Resource, Sampling, Agent/Gateway 배포 패턴을 중심으로 관측성 파이프라인을 정리합니다.
모니터링 도구를 붙이다 보면 어느 순간 데이터가 도구에 묶인다.
trace는 APM 제품의 agent가 만들고, metric은 다른 exporter가 만들고, log는 또 다른 수집기가 가져간다. 이름도 다르고, attribute도 다르고, 전송 프로토콜도 다르다. 나중에 backend를 바꾸려 하면 애플리케이션 곳곳에 묶인 설정과 계측 코드가 발목을 잡는다.
OpenTelemetry는 이 지점을 표준화하려는 프로젝트다. 관측성 backend 자체가 아니라, telemetry data를 만들고, 전파하고, 수집하고, 내보내는 공통 언어에 가깝다.
OpenTelemetry = instrumentation + data model + protocol + collector
OpenTelemetry가 맡는 구간
OpenTelemetry는 backend가 아니다
공식 문서는 OpenTelemetry를 observability framework and toolkit이라고 설명한다. telemetry data의 generation, export, collection을 돕지만, observability backend 자체는 아니다. 저장과 시각화는 의도적으로 다른 도구에게 맡긴다.
이 구분이 중요하다.
- Pinpoint는 Agent, Collector, 저장소, UI를 함께 가진 APM이다.
- Loki는 로그 저장과 조회를 맡는 backend다.
- OpenTelemetry는 이들 앞단에서 telemetry를 표준 형식으로 만들고 흘려보내는 계층이다.
그래서 OpenTelemetry를 도입한다고 해서 대시보드가 자동으로 생기지는 않는다. 대신 trace, metric, log를 여러 backend로 보낼 수 있는 공통 파이프라인이 생긴다.
Signal: traces, metrics, logs, baggage
OpenTelemetry를 처음 볼 때는 세 가지 signal만 기억하기 쉽다.
traces
metrics
logs
하지만 개념적으로는 baggage도 중요하다. baggage는 서비스 경계를 넘어 함께 전파되는 key-value context다. 모든 데이터가 backend로 저장되는 관측성 signal이라고 보기는 조심스럽지만, trace/log/metric에 붙을 문맥을 이동시키는 역할을 한다.
| Signal | 무엇을 말하는가 | 주로 묻는 질문 |
|---|---|---|
| Trace | 하나의 요청이 여러 서비스와 내부 작업을 지나간 경로다. | 이 요청은 어디에서 느려졌는가? |
| Metric | 시간에 따라 집계되는 숫자다. latency, error rate, CPU, queue length 등이 여기에 가깝다. | 시스템 상태가 언제부터 나빠졌는가? |
| Log | 특정 시점에 애플리케이션이 남긴 사건 기록이다. | 그 순간 실제로 어떤 메시지와 예외가 남았는가? |
| Baggage | 서비스 경계를 넘어 전파되는 key-value context다. | 이 요청에 붙은 공통 문맥을 다음 서비스에서도 볼 수 있는가? |
Trace 안에서는 span이 기본 단위다. 하나의 HTTP 요청, DB query, message publish, 내부 작업 하나가 span이 될 수 있다. span들은 trace id를 공유하고, parent-child 관계로 이어진다.
Java와 Spring에서는 어떻게 붙는가
Java 애플리케이션에서는 크게 두 가지 접근이 있다.
하나는 zero-code instrumentation이다. OpenTelemetry Java Agent를 JVM 옵션으로 붙이면, 지원되는 라이브러리와 프레임워크에 대해 자동 계측이 들어간다.
java \
-javaagent:/path/to/opentelemetry-javaagent.jar \
-Dotel.service.name=order-api \
-jar app.jar
환경 변수로도 설정할 수 있다.
export JAVA_TOOL_OPTIONS="-javaagent:/path/to/opentelemetry-javaagent.jar"
export OTEL_SERVICE_NAME="order-api"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://otel-collector:4318"
java -jar app.jar
다른 하나는 code-based instrumentation이다. 애플리케이션 코드에서 OpenTelemetry API를 직접 사용해 span, metric, attribute를 기록한다. 자동 계측이 놓치는 비즈니스 구간을 표시하거나, 더 의미 있는 attribute를 붙이고 싶을 때 쓴다.
Spring Boot에서는 Java Agent가 가장 직접적인 출발점인 경우가 많다. 다만 native image, agent 충돌, Spring 설정 파일 기반 관리가 중요하다면 Spring Boot starter 방식도 검토할 수 있다. 핵심은 둘 중 무엇을 쓰든 service.name과 resource attribute를 명시적으로 정하는 것이다.
Resource는 telemetry의 주소다
OpenTelemetry에서 resource는 telemetry를 만든 주체를 설명한다. 예를 들어 service.name, service.namespace, deployment.environment.name, k8s.namespace.name, k8s.cluster.name 같은 값이 여기에 들어간다.
공식 문서는 service.name을 명시적으로 설정할 것을 권장한다. 설정하지 않으면 SDK가 unknown_service 같은 기본값을 넣을 수 있다. backend에서 수많은 unknown_service를 보면, 이미 관측성의 첫 단추가 풀린 것이다.
Resource attribute가 붙는 자리
service.name, service.namespace, service.version은 backend에서 서비스를 구분하는 가장 기본적인 값이다.
좋은 resource 설계는 backend를 바꿔도 오래 남는다. 반대로 여기서 이름이 흔들리면 trace, metric, log를 서로 연결하는 일이 계속 어려워진다.
Collector는 작은 telemetry 라우터다
OpenTelemetry Collector는 telemetry를 받아서, 처리하고, 내보내는 독립 프로세스다.
Collector 설정은 보통 네 덩어리로 읽는다.
receivers: 데이터를 받는다. 예: OTLP, Prometheus, Kafka, Fluent Forward.processors: 데이터를 가공한다. 예: batch, memory_limiter, attributes, tail_sampling.exporters: 데이터를 보낸다. 예: OTLP, Prometheus Remote Write, Loki OTLP HTTP, debug.service.pipelines: 어떤 receiver, processor, exporter를 실제로 연결할지 정한다.
중요한 점은 component를 정의하는 것만으로는 활성화되지 않는다는 것이다. 공식 문서도 receiver는 service 섹션의 pipeline에 추가되어야 활성화된다고 설명한다.
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
exporters:
otlp:
endpoint: tempo:4317
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp]
Agent pattern과 Gateway pattern
Collector 배포는 크게 두 가지로 시작할 수 있다.
Collector 배포 패턴
Agent pattern은 애플리케이션과 가까워 장애 격리와 로컬 전처리에 유리하다. Gateway pattern은 정책과 exporter 설정을 중앙에서 관리하기 쉽다. 실제 운영에서는 Agent-to-Gateway처럼 둘을 섞는 경우도 많다.
tail sampling을 gateway에서 하고 싶다면 한 가지를 더 봐야 한다. tail sampling은 trace 전체 또는 대부분의 span을 본 뒤 sampling 여부를 정한다. 따라서 같은 trace의 span들이 같은 Collector로 모여야 한다. OpenTelemetry 공식 gateway 문서도 이런 경우 trace ID 또는 service-name aware load balancing을 언급한다.
Sampling은 비용과 정보의 균형이다
모든 trace를 저장하는 것은 가장 단순하지만 가장 비싸다. 그래서 sampling을 설계한다.
Head sampling은 trace 초반에 결정을 내린다. 단순하고 효율적이지만, 나중에 오류가 났는지, 전체 latency가 어땠는지 보고 결정할 수 없다.
Tail sampling은 trace가 끝난 뒤 전체 span을 보고 결정한다. 오류 trace를 항상 남기거나, latency가 긴 trace를 더 많이 남기는 식의 정책을 만들 수 있다. 대신 stateful하고 운영 비용이 크다.
| 방식 | 장점 | 주의할 점 |
|---|---|---|
| Head sampling | 단순하고 빠르며 애플리케이션 또는 Collector pipeline 초반에서 결정할 수 있다. | 전체 trace 내용을 보고 판단할 수 없어 오류 trace를 놓칠 수 있다. |
| Tail sampling | 오류, latency, attribute 조건을 보고 더 의미 있는 trace를 남길 수 있다. | span을 모아야 하므로 stateful하고, 같은 trace가 같은 sampling 지점으로 모이도록 설계해야 한다. |
Pinpoint, Loki와 함께 보면
세 도구를 같이 놓으면 역할이 선명해진다.
| 도구 | 주된 역할 | 질문 |
|---|---|---|
| Pinpoint | Java Agent 중심의 완성형 APM | 이 요청은 어떤 서비스와 메서드에서 느려졌는가? |
| Loki | 라벨 기반 로그 저장과 LogQL 조회 | 그 시점에 어떤 로그와 예외가 남았는가? |
| OpenTelemetry | telemetry 생성, 전파, 수집, export의 표준 계층 | 도구에 묶이지 않는 방식으로 trace, metric, log를 어떻게 흘려보낼 것인가? |
OpenTelemetry는 Pinpoint나 Loki를 단순히 대체하지 않는다. 오히려 시스템이 커질수록 이들 사이의 언어를 맞추는 층이 된다. trace id를 로그에 함께 남기고, Loki에서 해당 trace id를 찾고, trace backend에서 전체 요청을 따라가는 식의 연결이 가능해진다.
운영 체크리스트
OpenTelemetry를 도입할 때는 아래 항목부터 고정한다.
service.name을 반드시 명시한다.unknown_service가 생기면 나중에 정리가 어렵다.- resource attribute 이름을 조직 표준으로 정한다.
- 자동 계측과 수동 계측의 경계를 정한다. 자동 계측은 넓게, 수동 계측은 비즈니스 의미가 있는 구간에만 둔다.
- Collector pipeline에서 component 정의와 pipeline 활성화를 구분한다.
- OTLP gRPC 4317과 HTTP 4318을 혼동하지 않는다.
- sampling 정책을 비용 절감만으로 정하지 않는다. 장애 분석에 필요한 trace를 남겨야 한다.
- PII나 token 같은 민감정보가 attribute, baggage, log body로 흘러가지 않게 필터링한다.
- backend별 제한을 확인한다. 같은 OTel 데이터라도 Loki, Prometheus, trace backend가 받아들이는 label/attribute/cardinality 모델은 다르다.
OpenTelemetry는 별을 직접 보여주는 망원경이라기보다, 별빛을 같은 언어로 모으는 관측 장치에 가깝다. 어디에 저장하고 어떻게 볼지는 다른 도구가 맡는다. 대신 한 번 정돈된 telemetry pipeline은 backend가 바뀌어도 오래 남는다.