- Authors
- Name
- 들어가며
- OpenTelemetry 아키텍처와 Collector의 역할
- 분산 트레이싱 도구 비교: OpenTelemetry vs Jaeger vs Zipkin
- Receiver-Processor-Exporter 파이프라인 구성
- Receiver 상세 설정
- Processor 상세 설정
- 다양한 백엔드 연동
- Kubernetes 환경 배포 전략
- 샘플링 전략
- 성능 튜닝과 고가용성 구성
- 트러블슈팅 가이드
- 실패 사례와 복구 절차
- 운영 체크리스트
- 마치며
- 참고 자료

들어가며
마이크로서비스 아키텍처가 보편화되면서 관측 가능성(Observability)은 선택이 아닌 필수가 되었다. 수십에서 수백 개의 서비스가 상호 통신하는 분산 환경에서 Traces, Metrics, Logs라는 세 가지 텔레메트리 신호를 효율적으로 수집하고 처리하는 것은 안정적인 서비스 운영의 기본 전제 조건이다.
OpenTelemetry Collector는 CNCF(Cloud Native Computing Foundation) 졸업 프로젝트인 OpenTelemetry의 핵심 컴포넌트로, 벤더에 종속되지 않는 텔레메트리 파이프라인을 제공한다. 애플리케이션이 생성한 텔레메트리 데이터를 수신(Receiver)하고, 가공(Processor)하고, 원하는 백엔드로 내보내는(Exporter) 역할을 담당한다.
2026년 현재 OpenTelemetry는 사실상 관측 가능성 계측의 업계 표준이 되었다. Jaeger v2는 내부적으로 OpenTelemetry Collector 기반으로 재설계되었고, Zipkin Exporter는 2025년부터 공식적으로 폐기 예정(Deprecated) 상태로 전환되었다. 이 흐름에서 Collector의 올바른 운영 방법을 이해하는 것은 플랫폼 엔지니어와 SRE에게 핵심 역량이다.
이 글에서는 OpenTelemetry Collector의 아키텍처, 파이프라인 설정, 다양한 백엔드 연동, Kubernetes 배포 전략, 샘플링 기법, 성능 튜닝, 트러블슈팅, 장애 복구까지 운영에 필요한 모든 내용을 실전 예제와 함께 총정리한다.
OpenTelemetry 아키텍처와 Collector의 역할
전체 아키텍처 개요
OpenTelemetry 프레임워크는 크게 세 가지 계층으로 구성된다.
- SDK/API 계층: 각 언어별 SDK가 애플리케이션 코드에서 텔레메트리 데이터를 생성한다.
- Collector 계층: 생성된 텔레메트리 데이터를 수신, 가공, 라우팅한다.
- Backend 계층: 최종 저장소(Jaeger, Tempo, Datadog 등)에서 데이터를 저장하고 시각화한다.
[애플리케이션 + OTel SDK]
|
| OTLP (gRPC/HTTP)
v
+========================+
| OpenTelemetry |
| Collector |
| |
| Receiver -> Processor |
| -> Exporter |
+========================+
| | |
v v v
[Jaeger] [Tempo] [Datadog]
Collector는 이 아키텍처에서 중앙 허브 역할을 수행한다. 애플리케이션은 OTLP(OpenTelemetry Protocol)로 데이터를 전송하기만 하면 되고, 어떤 백엔드를 사용할지는 Collector 설정에서 결정한다. 이를 통해 백엔드 교체 시 애플리케이션 코드 변경 없이 Collector 설정만 수정하면 된다.
Core vs Contrib 배포판
OpenTelemetry Collector는 두 가지 배포판을 제공한다.
| 항목 | Core | Contrib |
|---|---|---|
| 컴포넌트 범위 | 핵심 Receiver/Processor/Exporter만 포함 | 커뮤니티 기여 컴포넌트 다수 포함 |
| 바이너리 크기 | 약 50MB | 약 200MB 이상 |
| 보안 노출 면적 | 작음 | 넓음 |
| 적합한 환경 | 커스텀 빌드 기반 프로덕션 | PoC, 테스트, 초기 도입 |
프로덕션 환경에서는 OpenTelemetry Collector Builder(OCB)를 사용하여 필요한 컴포넌트만 포함한 커스텀 바이너리를 빌드하는 것이 보안과 리소스 효율 양면에서 권장된다.
분산 트레이싱 도구 비교: OpenTelemetry vs Jaeger vs Zipkin
Collector 구성을 다루기 전에, 주요 분산 트레이싱 도구의 위상 변화를 먼저 정리한다.
| 항목 | OpenTelemetry | Jaeger | Zipkin |
|---|---|---|---|
| 프로젝트 상태 | CNCF Graduated (활발히 개발 중) | CNCF Graduated (v2 전환 완료) | 독립 프로젝트 (유지보수 모드) |
| 주요 언어 | Go (Collector), 다중 언어 SDK | Go | Java |
| 역할 | 계측 프레임워크 + 수집 파이프라인 | 트레이스 저장/쿼리/시각화 백엔드 | 트레이스 저장/쿼리/시각화 백엔드 |
| 프로토콜 | OTLP (gRPC, HTTP/protobuf) | OTLP, Thrift (Legacy) | HTTP/JSON, Thrift |
| 텔레메트리 범위 | Traces + Metrics + Logs | Traces만 | Traces만 |
| Kubernetes 지원 | Operator, Helm Chart, DaemonSet/Deployment | Helm Chart, Operator | Helm Chart |
| 백엔드 연동 | 벤더 중립 (모든 백엔드 지원) | 자체 UI + Elasticsearch/Cassandra/ClickHouse | 자체 UI + Elasticsearch/Cassandra |
| 샘플링 | Head-based + Tail-based (Collector) | Remote Sampling API | 확률적 샘플링 |
| 2026년 권장 | 계측 표준으로 사용 | OTLP 기반 v2로 전환 권장 | 신규 도입 비권장 (Deprecated 예정) |
핵심 요약: OpenTelemetry는 계측과 수집의 표준, Jaeger v2는 OTLP 네이티브 백엔드, Zipkin은 레거시 마이그레이션 대상이다. 2025년부터 OpenTelemetry SDK의 Zipkin Exporter가 공식 폐기 예정으로 전환되었으므로, 신규 프로젝트에서는 OTLP 기반 아키텍처를 채택하는 것이 바람직하다.
Receiver-Processor-Exporter 파이프라인 구성
파이프라인 구조 이해
OpenTelemetry Collector의 설정 파일은 네 가지 최상위 섹션으로 구성된다.
- receivers: 데이터 수신 방법 정의
- processors: 데이터 가공/변환/필터링 규칙 정의
- exporters: 데이터 전송 대상 정의
- service: 위 컴포넌트들을 조합하여 실제 파이프라인을 활성화
중요한 점은 receivers, processors, exporters 섹션에서 컴포넌트를 정의하더라도 service.pipelines에서 참조하지 않으면 활성화되지 않는다는 것이다.
기본 파이프라인 설정 예제
다음은 OTLP Receiver로 트레이스를 수신하고, 배치 처리 후 OTLP Exporter로 전송하는 기본 설정이다.
# otel-collector-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: 512
spike_limit_mib: 128
batch:
send_batch_size: 8192
timeout: 200ms
send_batch_max_size: 0
exporters:
otlp:
endpoint: tempo.monitoring.svc.cluster.local:4317
tls:
insecure: true
debug:
verbosity: detailed
extensions:
health_check:
endpoint: 0.0.0.0:13133
zpages:
endpoint: 0.0.0.0:55679
service:
extensions: [health_check, zpages]
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp, debug]
이 설정에서 핵심적인 부분은 프로세서의 순서이다. memory_limiter를 항상 첫 번째 프로세서로 배치해야 한다. 메모리 제한을 초과하면 데이터를 일찍 거부하여 Collector의 OOM(Out of Memory) 크래시를 방지하기 위함이다. batch는 반드시 memory_limiter와 샘플링 프로세서 뒤에 배치하여, 데이터 드롭이 완료된 후에 배치를 구성해야 한다.
Receiver 상세 설정
OTLP Receiver
가장 기본적이고 권장되는 Receiver이다. gRPC(4317)와 HTTP(4318) 두 가지 프로토콜을 지원한다.
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
max_recv_msg_size_mib: 4
keepalive:
server_parameters:
max_connection_idle: 11s
max_connection_age: 30s
http:
endpoint: 0.0.0.0:4318
cors:
allowed_origins:
- 'https://*.example.com'
Jaeger Receiver
레거시 Jaeger 클라이언트와의 호환성을 위해 사용한다. Jaeger v2로의 마이그레이션 과도기에 유용하다.
receivers:
jaeger:
protocols:
grpc:
endpoint: 0.0.0.0:14250
thrift_http:
endpoint: 0.0.0.0:14268
thrift_compact:
endpoint: 0.0.0.0:6831
Prometheus Receiver
Prometheus 스크래핑 방식으로 메트릭을 수집한다. Kubernetes 서비스 디스커버리와 연동이 가능하다.
receivers:
prometheus:
config:
scrape_configs:
- job_name: 'otel-collector-internal'
scrape_interval: 15s
static_configs:
- targets: ['0.0.0.0:8888']
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: (.+)
replacement: $$1
Processor 상세 설정
Memory Limiter Processor
Collector의 메모리 사용량을 제어하여 OOM 크래시를 방지하는 필수 프로세서이다. 모든 파이프라인에서 첫 번째 프로세서로 배치해야 한다.
processors:
memory_limiter:
check_interval: 1s
limit_mib: 1024 # 하드 리밋 (1GB)
spike_limit_mib: 256 # 스파이크 허용치 (하드 리밋의 약 20%)
설정값 산정 가이드:
limit_mib: 컨테이너 메모리 제한의 약 80%로 설정한다. 컨테이너 메모리 제한이 2GB라면limit_mib: 1638정도가 적절하다.spike_limit_mib:limit_mib의 약 20%로 설정한다. 소프트 리밋은limit_mib - spike_limit_mib로 계산된다.GOMEMLIMIT환경변수: Collector 컨테이너의GOMEMLIMIT을 하드 리밋의 80%(즉, 컨테이너 메모리 제한의 약 64%)로 설정하면 Go 런타임의 GC가 더 효율적으로 동작한다.
Batch Processor
텔레메트리 데이터를 배치로 묶어 네트워크 요청 횟수를 줄이고, 직렬화 CPU 오버헤드를 낮추는 프로세서이다.
processors:
batch:
send_batch_size: 8192 # 이 개수에 도달하면 즉시 전송
timeout: 200ms # 크기에 상관없이 이 시간이 지나면 전송
send_batch_max_size: 16384 # 배치 최대 크기 상한 (0이면 무제한)
send_batch_size는 트리거 역할이지 배치 크기 상한이 아니다. 실제 배치 크기를 제한하려면send_batch_max_size를 반드시 설정해야 한다.- 메모리 압박이 있을 때는
send_batch_size와timeout을 줄여 배치를 더 빠르게 플러시한다.
Filter Processor
불필요한 텔레메트리 데이터를 조기에 드롭하여 백엔드 비용을 절감한다.
processors:
filter:
error_mode: ignore
traces:
span:
- 'attributes["http.route"] == "/healthz"'
- 'attributes["http.route"] == "/readyz"'
- 'name == "health_check"'
metrics:
metric:
- 'name == "rpc.server.duration" and resource.attributes["service.name"] == "debug-svc"'
위 설정은 헬스체크 경로의 트레이스와 특정 서비스의 불필요한 메트릭을 필터링하여 백엔드로 전송하지 않는다. 고트래픽 환경에서 텔레메트리 비용을 크게 절감할 수 있다.
Attributes Processor
스팬이나 메트릭에 속성을 추가, 수정, 삭제하는 프로세서이다.
processors:
attributes:
actions:
- key: environment
value: production
action: upsert
- key: db.statement
action: delete
- key: http.request.header.authorization
action: delete
민감정보(DB 쿼리, 인증 토큰 등)를 삭제하는 데 특히 유용하다.
다양한 백엔드 연동
Jaeger (OTLP)
Jaeger v2는 OTLP를 네이티브로 지원하므로 별도의 Jaeger Exporter가 아닌 OTLP Exporter를 사용한다.
exporters:
otlp/jaeger:
endpoint: jaeger-collector.monitoring.svc.cluster.local:4317
tls:
insecure: true
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 30s
max_elapsed_time: 300s
sending_queue:
enabled: true
num_consumers: 10
queue_size: 5000
Grafana Tempo
Tempo 역시 OTLP를 네이티브로 지원한다. gRPC 또는 HTTP 프로토콜을 선택할 수 있다.
exporters:
otlp/tempo:
endpoint: tempo-distributor.monitoring.svc.cluster.local:4317
tls:
insecure: true
sending_queue:
enabled: true
num_consumers: 10
queue_size: 10000
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 30s
Datadog
Datadog Exporter는 collector-contrib에 포함되어 있으며, API Key를 통해 인증한다.
exporters:
datadog:
api:
key: ${env:DD_API_KEY}
site: datadoghq.com
traces:
span_name_as_resource_name: true
metrics:
histograms:
mode: distributions
sending_queue:
enabled: true
num_consumers: 10
queue_size: 5000
멀티 백엔드 파이프라인 구성
하나의 Collector에서 여러 백엔드로 동시에 데이터를 전송하는 구성이다.
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, filter, attributes, batch]
exporters: [otlp/tempo, datadog]
metrics:
receivers: [otlp, prometheus]
processors: [memory_limiter, batch]
exporters: [prometheusremotewrite]
logs:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [loki]
각 Exporter는 데이터의 복사본을 받으므로, 하나의 Exporter 장애가 다른 Exporter에 영향을 주지 않는다. 단, 모든 Exporter가 동일한 데이터를 처리하므로 Receiver와 Processor 부하는 공유된다.
Kubernetes 환경 배포 전략
DaemonSet vs Deployment
Kubernetes 환경에서 Collector를 배포하는 두 가지 주요 패턴이 있다.
| 항목 | DaemonSet (Agent) | Deployment (Gateway) |
|---|---|---|
| 배포 단위 | 모든 노드에 1개씩 | 지정된 Replica 수만큼 |
| 역할 | 로컬 텔레메트리 수집, 초기 가공 | 중앙 집중 처리, 집계, 최종 전송 |
| 네트워크 부하 | 노드 내 통신 (낮음) | 크로스 노드 통신 (높음) |
| Tail Sampling | 부적합 (트레이스 분산됨) | 적합 (모든 스팬 집중) |
| 클러스터 메트릭 | 수집 시 데이터 중복 위험 | 중복 없이 수집 가능 |
| 장애 영향 범위 | 해당 노드만 | 전체 파이프라인 |
| 스케일링 | 노드 추가 시 자동 | HPA로 수동/자동 스케일링 |
프로덕션 권장 패턴은 DaemonSet(Agent) + Deployment(Gateway) 계층 구조이다. Agent가 각 노드에서 로컬 수집과 1차 가공을 수행하고, Gateway가 집계, Tail Sampling, 최종 백엔드 전송을 담당한다.
DaemonSet Agent 매니페스트
# otel-agent-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: otel-collector-agent
namespace: monitoring
spec:
selector:
matchLabels:
app: otel-collector-agent
template:
metadata:
labels:
app: otel-collector-agent
spec:
containers:
- name: otel-collector
image: otel/opentelemetry-collector-contrib:0.120.0
args: ['--config=/etc/otelcol/config.yaml']
env:
- name: GOMEMLIMIT
value: '400MiB'
- name: K8S_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
ports:
- containerPort: 4317 # OTLP gRPC
hostPort: 4317
protocol: TCP
- containerPort: 4318 # OTLP HTTP
hostPort: 4318
protocol: TCP
- containerPort: 13133 # Health Check
protocol: TCP
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /
port: 13133
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 13133
initialDelaySeconds: 5
periodSeconds: 5
volumeMounts:
- name: config
mountPath: /etc/otelcol
volumes:
- name: config
configMap:
name: otel-agent-config
Gateway Deployment 매니페스트
# otel-gateway-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: otel-collector-gateway
namespace: monitoring
spec:
replicas: 3
selector:
matchLabels:
app: otel-collector-gateway
template:
metadata:
labels:
app: otel-collector-gateway
spec:
containers:
- name: otel-collector
image: otel/opentelemetry-collector-contrib:0.120.0
args: ['--config=/etc/otelcol/config.yaml']
env:
- name: GOMEMLIMIT
value: '3200MiB'
ports:
- containerPort: 4317
protocol: TCP
- containerPort: 13133
protocol: TCP
resources:
requests:
cpu: 1000m
memory: 2Gi
limits:
cpu: 2000m
memory: 4Gi
livenessProbe:
httpGet:
path: /
port: 13133
readinessProbe:
httpGet:
path: /
port: 13133
volumeMounts:
- name: config
mountPath: /etc/otelcol
volumes:
- name: config
configMap:
name: otel-gateway-config
---
apiVersion: v1
kind: Service
metadata:
name: otel-collector-gateway
namespace: monitoring
spec:
selector:
app: otel-collector-gateway
ports:
- name: otlp-grpc
port: 4317
targetPort: 4317
- name: otlp-http
port: 4318
targetPort: 4318
type: ClusterIP
Agent 설정에서는 Gateway Service를 Exporter 엔드포인트로 지정한다.
# Agent의 exporter 설정
exporters:
otlp/gateway:
endpoint: otel-collector-gateway.monitoring.svc.cluster.local:4317
tls:
insecure: true
sending_queue:
enabled: true
queue_size: 2000
retry_on_failure:
enabled: true
Gateway 접속 시 반드시 Kubernetes Service를 통해 연결해야 한다. Pod IP 직접 참조는 Pod 재시작 시 연결이 끊어지며, 로드 밸런싱도 수행되지 않는다.
샘플링 전략
Head-based Sampling
SDK 수준에서 트레이스 시작 시점에 샘플링 결정을 내리는 방식이다.
장점: 구현이 단순하고, 리소스 절약이 즉각적이다.
단점: 트레이스 전체를 보지 않고 결정하므로, 에러가 포함된 트레이스를 놓칠 수 있다.
# SDK 설정에서 TraceIdRatioBased Sampler 사용
# 10%의 트레이스만 샘플링
processors:
probabilistic_sampler:
sampling_percentage: 10
Tail-based Sampling
트레이스의 모든(또는 대부분의) 스팬이 도착한 후에 샘플링 결정을 내리는 방식이다. Collector의 Tail Sampling Processor에서 수행한다.
장점: 에러, 고지연 등 특정 조건의 트레이스를 100% 보존할 수 있다.
단점: 트레이스의 모든 스팬이 한 곳에 모여야 하므로 Gateway Collector에서만 사용해야 하며, 메모리 소비가 크다.
processors:
tail_sampling:
decision_wait: 30s
num_traces: 100000
expected_new_traces_per_sec: 1000
policies:
# 정책 1: 에러가 포함된 트레이스는 100% 샘플링
- name: errors-policy
type: status_code
status_code:
status_codes:
- ERROR
# 정책 2: 지연이 500ms를 초과하는 트레이스는 100% 샘플링
- name: latency-policy
type: latency
latency:
threshold_ms: 500
# 정책 3: 나머지 트레이스는 5%만 샘플링
- name: probabilistic-policy
type: probabilistic
probabilistic:
sampling_percentage: 5
Tail Sampling 운영 주의사항:
- DaemonSet Agent에서는 절대 사용하지 않는다. 트레이스의 스팬들이 여러 노드에 분산되어 도착하므로, 각 Agent가 트레이스의 일부분만 보게 되어 올바른 샘플링 결정을 내릴 수 없다.
decision_wait는 트레이스의 모든 스팬이 도착할 시간적 여유를 의미한다. 너무 짧으면 불완전한 트레이스 기반으로 결정하고, 너무 길면 메모리 소비가 급증한다.num_traces는 동시에 메모리에 유지하는 트레이스 수의 상한이다. 이를 초과하면 가장 오래된 트레이스가 강제로 결정된다.
권장 샘플링 전략 조합
| 환경 | 권장 전략 | 설명 |
|---|---|---|
| 트래픽이 적은 서비스 | 전수 수집 (100%) | 모든 트레이스를 보존하여 디버깅 용이성 확보 |
| 일반 프로덕션 | Head 10% + Tail 에러/지연 | 비용 절감과 중요 트레이스 보존의 균형 |
| 고트래픽 서비스 | Head 1% + Tail 에러/지연 | 대량 트래픽에서 비용 최적화 |
| 규제/감사 대상 | 전수 수집 + 장기 저장 | 컴플라이언스 요건 충족 |
성능 튜닝과 고가용성 구성
핵심 성능 파라미터
# 성능 최적화 설정 예시
processors:
memory_limiter:
check_interval: 1s
limit_mib: 1638 # 컨테이너 메모리 2GB의 ~80%
spike_limit_mib: 328 # limit_mib의 ~20%
batch:
send_batch_size: 10000
timeout: 500ms
send_batch_max_size: 20000
exporters:
otlp/backend:
endpoint: backend:4317
sending_queue:
enabled: true
num_consumers: 20 # 병렬 전송 워커 수
queue_size: 10000 # 큐 크기 (배치 단위)
retry_on_failure:
enabled: true
initial_interval: 5s
max_interval: 30s
max_elapsed_time: 300s
timeout: 30s
GOMEMLIMIT 설정
Go 런타임의 GC 효율을 높이기 위해 GOMEMLIMIT 환경변수를 설정한다.
# 컨테이너 메모리 제한이 2GB일 때
# GOMEMLIMIT = limit_mib * 0.8 = 1638 * 0.8 ~ 1310MiB
export GOMEMLIMIT=1310MiB
| 컨테이너 메모리 | limit_mib | spike_limit_mib | GOMEMLIMIT |
|---|---|---|---|
| 512Mi | 410 | 82 | 328MiB |
| 1Gi | 820 | 164 | 656MiB |
| 2Gi | 1638 | 328 | 1310MiB |
| 4Gi | 3276 | 655 | 2621MiB |
고가용성(HA) 구성
Gateway Collector의 고가용성을 위한 핵심 전략은 다음과 같다.
- 최소 3개 Replica: Deployment의 replicas를 최소 3으로 설정한다.
- Pod Anti-Affinity: 동일 노드에 Gateway Pod가 집중되지 않도록 설정한다.
- PDB(PodDisruptionBudget): 동시에 종료되는 Pod 수를 제한한다.
- HPA: CPU/메모리 기반 자동 스케일링을 구성한다.
# PodDisruptionBudget 설정
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: otel-gateway-pdb
namespace: monitoring
spec:
minAvailable: 2
selector:
matchLabels:
app: otel-collector-gateway
트러블슈팅 가이드
일반적인 문제와 해결 방법
1. 데이터가 백엔드에 도달하지 않는 경우
# 1단계: Collector 로그 확인
kubectl logs -n monitoring deploy/otel-collector-gateway --tail=100
# 2단계: zPages에서 파이프라인 상태 확인
kubectl port-forward -n monitoring svc/otel-collector-gateway 55679:55679
# 브라우저에서 http://localhost:55679/debug/tracez 접속
# 3단계: debug exporter를 임시 추가하여 데이터 흐름 확인
# exporters에 debug: { verbosity: detailed } 추가 후
# pipeline에 debug exporter 추가
2. Collector 메모리 사용량 급증
memory_limiter가 설정되어 있는지 확인한다.GOMEMLIMIT이 적절히 설정되어 있는지 확인한다.- Tail Sampling의
num_traces값이 너무 크지 않은지 확인한다. - Exporter의
sending_queue.queue_size가 과도하지 않은지 확인한다.
3. Exporter 큐 포화
# Collector 내부 메트릭으로 큐 상태 확인
curl http://localhost:8888/metrics | grep otelcol_exporter_queue
# otelcol_exporter_queue_size: 현재 큐 크기
# otelcol_exporter_queue_capacity: 큐 최대 용량
# queue_size가 capacity에 근접하면 큐 포화 상태
원인: 백엔드가 데이터를 충분히 빠르게 처리하지 못하거나, Collector에 과도한 트래픽이 유입됨.
해결: num_consumers를 늘리거나, 백엔드의 처리 용량을 확인하거나, 필터 프로세서로 불필요한 데이터를 조기 드롭한다.
4. 프로세서 순서 오류
잘못된 프로세서 순서는 미묘한 문제를 야기한다.
| 실수 | 증상 | 올바른 순서 |
|---|---|---|
| batch를 memory_limiter 앞에 배치 | 메모리 급증 후 OOM | memory_limiter를 항상 첫 번째로 |
| attributes를 batch 뒤에 배치 | 속성 변경이 적용되지 않음 | attributes는 batch 앞에 |
| tail_sampling을 DaemonSet에 배치 | 불완전한 샘플링 결정 | Gateway에서만 tail_sampling 사용 |
| resourcedetection을 마지막에 배치 | 리소스 속성 누락 | resourcedetection을 앞쪽에 배치 |
핵심 모니터링 메트릭
Collector 자체의 운영 상태를 모니터링하기 위해 다음 내부 메트릭을 추적해야 한다.
| 메트릭 | 설명 | 경보 기준 |
|---|---|---|
otelcol_receiver_accepted_spans | 수신 성공 스팬 수 | 급격한 감소 시 경보 |
otelcol_receiver_refused_spans | 수신 거부 스팬 수 | 0이 아닌 값이 지속되면 경보 |
otelcol_exporter_sent_spans | 전송 성공 스팬 수 | accepted 대비 크게 차이나면 경보 |
otelcol_exporter_send_failed_spans | 전송 실패 스팬 수 | 0이 아닌 값이 지속되면 경보 |
otelcol_exporter_queue_size | 현재 큐 크기 | capacity의 80% 초과 시 경보 |
otelcol_processor_dropped_spans | 프로세서에서 드롭된 스팬 | 예상치 초과 시 경보 |
실패 사례와 복구 절차
사례 1: Gateway 전체 장애 (전체 Replica 다운)
증상: 모든 텔레메트리 데이터가 유실됨. Agent의 Exporter 큐가 포화되어 데이터 드롭 시작.
원인: 잘못된 설정 변경 배포, OOM 연쇄 발생, Node 장애 등.
복구 절차:
- 즉시 이전 정상 ConfigMap으로 롤백한다.
- Gateway Deployment의 replicas가 충분한지 확인한다.
- Pod Anti-Affinity가 설정되어 있는지 검증한다.
- Agent 측의 sending_queue와 retry_on_failure 설정을 확인한다. 큐에 쌓인 데이터는 Gateway 복구 후 자동으로 재전송된다.
- 재발 방지를 위해 PDB를 설정하고, ConfigMap 변경 시 Canary 배포를 적용한다.
# ConfigMap 롤백
kubectl rollout undo configmap/otel-gateway-config -n monitoring
# 또는 이전 버전의 ConfigMap을 직접 적용
kubectl apply -f otel-gateway-config-backup.yaml
# Gateway Deployment 재시작
kubectl rollout restart deployment/otel-collector-gateway -n monitoring
# 복구 상태 확인
kubectl rollout status deployment/otel-collector-gateway -n monitoring
사례 2: 백엔드 장애로 인한 데이터 유실
증상: Exporter 큐 포화, otelcol_exporter_send_failed_spans 급증.
원인: 백엔드(Tempo, Jaeger 등)가 다운되거나 응답이 느려짐.
복구 절차:
- 백엔드의 상태를 먼저 확인하고 복구한다.
- Collector는 retry_on_failure 설정에 의해 자동으로 재시도한다.
- 큐 포화로 드롭된 데이터는 복구 불가능하므로, 장기적으로 큐 크기를 늘리거나 Kafka 등의 버퍼 계층을 도입한다.
사례 3: memory_limiter 미설정으로 인한 OOM
증상: Collector Pod가 OOMKilled 상태로 반복 재시작됨.
원인: memory_limiter 프로세서 미설정 또는 부적절한 값 설정.
복구 절차:
- memory_limiter를 모든 파이프라인의 첫 번째 프로세서로 추가한다.
- limit_mib를 컨테이너 메모리 제한의 80%로 설정한다.
- GOMEMLIMIT 환경변수를 설정한다.
- 컨테이너 리소스 제한이 워크로드에 비해 너무 작지 않은지 검토한다.
운영 체크리스트
초기 배포 체크리스트
- memory_limiter 프로세서가 모든 파이프라인의 첫 번째 프로세서로 설정되어 있는가
- GOMEMLIMIT 환경변수가 설정되어 있는가
- health_check Extension이 활성화되어 있는가
- livenessProbe와 readinessProbe가 설정되어 있는가
- Exporter에 sending_queue와 retry_on_failure가 활성화되어 있는가
- 컨테이너 리소스 requests/limits가 적절히 설정되어 있는가
- PodDisruptionBudget이 설정되어 있는가
프로세서 순서 체크리스트
- memory_limiter가 첫 번째 프로세서인가
- resourcedetection이 memory_limiter 다음에 위치하는가 (사용 시)
- filter/attributes 등 데이터 가공 프로세서가 batch 앞에 위치하는가
- tail_sampling이 Gateway에서만 사용되고 있는가 (사용 시)
- batch가 마지막 프로세서인가
모니터링 체크리스트
- Collector 내부 메트릭(8888 포트)을 Prometheus로 수집하고 있는가
- Exporter 큐 포화 경보가 설정되어 있는가
- 수신 거부(refused) 메트릭 경보가 설정되어 있는가
- 전송 실패(send_failed) 메트릭 경보가 설정되어 있는가
- Collector Pod의 메모리/CPU 사용량 경보가 설정되어 있는가
보안 체크리스트
- 불필요한 포트가 외부에 노출되지 않았는가
- TLS가 필요한 통신 경로에 적용되어 있는가
- API Key 등 민감정보가 환경변수 또는 Secret으로 관리되고 있는가
- attributes 프로세서로 민감 속성(DB 쿼리, 인증 토큰)을 삭제하고 있는가
마치며
OpenTelemetry Collector는 현대적인 관측 가능성 파이프라인의 핵심 인프라이다. 벤더 중립적인 설계 덕분에 Jaeger, Grafana Tempo, Datadog 등 어떤 백엔드든 유연하게 연동할 수 있으며, 백엔드 교체 시에도 애플리케이션 코드를 수정할 필요가 없다.
안정적인 운영을 위해 가장 중요한 세 가지를 요약하면 다음과 같다.
- memory_limiter를 반드시 첫 번째 프로세서로 설정하고 GOMEMLIMIT을 적절히 구성한다.
- DaemonSet(Agent) + Deployment(Gateway) 계층 구조로 배포하여 로컬 수집과 중앙 집중 처리를 분리한다.
- Exporter의 sending_queue와 retry_on_failure를 활성화하여 일시적인 백엔드 장애에 대한 복원력을 확보한다.
Collector는 지속적으로 발전하고 있으며, OpAMP(Open Agent Management Protocol)를 통한 원격 설정 관리 등 새로운 기능이 계속 추가되고 있다. 공식 문서와 릴리스 노트를 주기적으로 확인하며 최신 변경사항을 반영하는 것이 바람직하다.
참고 자료
- OpenTelemetry Collector 공식 문서
- OpenTelemetry Collector Configuration
- OpenTelemetry Collector Architecture
- OpenTelemetry Sampling 개념
- OpenTelemetry Collector Scaling Guide
- OpenTelemetry Collector Troubleshooting
- OpenTelemetry Kubernetes Helm Chart
- Tail Sampling Processor (GitHub)
- Memory Limiter Processor (GitHub)
- Batch Processor (GitHub)