Grafana Alloy vs OpenTelemetry Collector, 결국 뭘로 갈까
관측성 파이프라인 한 번이라도 운영해본 사람이면 다 비슷한 고민을 한다. "에이전트는 뭘로 깔지?" 예전에는 Prometheus + Promtail + Tempo agent 같은 식으로 컴포넌트별로 따로 깔거나, 통합하려면 OpenTelemetry Collector 한 장 깔거나, 그것도 아니면 Grafana Agent 깔거나. 그런데 Grafana Agent가 Alloy로 리브랜딩된 이후, 그리고 그 사이에 OTel Collector contrib도 계속 두꺼워지면서 선택지가 다시 흐릿해졌다.
우리 팀에서도 작년 말에 한 번 정리하고 갔는데, 최근 Alloy v1.16 시리즈가 나오고 OTel Collector 쪽도 컴포넌트가 또 바뀌면서 다시 비교할 필요가 생겼다. 이번 글은 그 정리 노트다. 어느 쪽이 절대적으로 낫다는 결론은 없다. 다만 어떤 상황에서 뭘 고르는 게 맞는지에 대한 우리 팀 기준은 정리해 두려고 한다.
두 도구가 사실은 비슷한 출발점이다
오해부터 풀고 가자. Alloy는 OTel Collector의 fork가 아니다. 별도 코드베이스인데, 내부적으로 OTel Collector 컴포넌트(receivers, processors, exporters)를 wrapping해서 가져다 쓴다. 그러니까 otelcol.receiver.otlp, otelcol.processor.batch 같은 블록을 Alloy에서 쓰면 사실상 같은 코드가 도는 셈이다.
그래서 "Alloy는 느리다", "OTel은 무겁다" 같은 거친 주장은 대부분 사실이 아니다. 같은 OTel 컴포넌트를 쓰는 부분에서는 성능 차이가 거의 없다. 차이는 그 컴포넌트들을 어떻게 묶고, 어떻게 라우팅하고, 어떤 부가 기능을 곁들이느냐에서 나온다.
설정 언어가 사용감을 크게 가른다
가장 먼저 부딪히는 차이는 config 작성 경험이다.
OTel Collector는 YAML이다. 익숙하지만 길어지면 정말 길어진다. 파이프라인이 5개 넘어가기 시작하면 pipelines 섹션이 스크롤 한 번에 안 들어온다. 그리고 YAML 특성상 "이 processor가 어디서 쓰이는지" 한눈에 보기 어렵다. processor 정의는 위에 있고, 그걸 참조하는 pipeline은 한참 아래 있고.
processors:
batch:
timeout: 1s
memory_limiter:
check_interval: 1s
limit_mib: 512
exporters:
otlp/tempo:
endpoint: tempo:4317
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [otlp/tempo]
Alloy는 River라는 HCL 계열 언어를 쓴다. 컴포넌트 단위로 블록을 만들고, 컴포넌트끼리는 참조로 연결한다. 같은 파이프라인을 Alloy로 쓰면 이런 식이다.
otelcol.receiver.otlp "default" {
grpc {}
output {
traces = [otelcol.processor.memory_limiter.default.input]
}
}
otelcol.processor.memory_limiter "default" {
check_interval = "1s"
limit = "512MiB"
output {
traces = [otelcol.processor.batch.default.input]
}
}
otelcol.processor.batch "default" {
output {
traces = [otelcol.exporter.otlp.tempo.input]
}
}
otelcol.exporter.otlp "tempo" {
client {
endpoint = "tempo:4317"
}
}
처음 보면 더 길어 보인다. 그런데 컴포넌트가 많아질수록 평가가 바뀐다. 각 블록이 자기 입력과 출력을 명시하기 때문에 "이 데이터는 어디서 와서 어디로 가나"가 코드 안에 그대로 박힌다. 파이프라인 다이어그램이 머릿속에 굳이 안 그려져도 따라가진다. YAML 설정 1000줄 짜리를 디버깅해본 사람이면 이 부분의 가치를 안다.
대신 단점도 분명하다. River는 흔한 언어가 아니다. 신입이 들어오면 한 번은 가르쳐야 한다. YAML은 "그냥 YAML"로 통과되는데, River는 "이게 뭐예요?"가 먼저 나온다.
UI와 디버깅 경험은 Alloy 쪽이 매끈하다
Alloy는 기본으로 :12345에 웹 UI를 띄운다. 컴포넌트 그래프, 각 컴포넌트의 헬스, 들어오는/나가는 신호 수, 마지막 에러 메시지가 한 화면에 다 나온다. 처음 깔고 트러블슈팅할 때 이게 생각보다 차이가 크다.
OTel Collector도 zpages extension을 켜면 비슷한 정보를 볼 수는 있다. 다만 깊이가 다르다. zpages는 텍스트 위주에 페이지 단위로 갈라져 있고, Alloy UI는 그래프로 한눈에 보인다. 새벽에 알람 받고 들어왔을 때 차이가 난다.
컴포넌트 커버리지는 OTel Collector contrib가 여전히 우위다
OTel Collector contrib repo는 진짜 두껍다. 거의 모든 메이저 옵저버빌리티 벤더가 자기네 receiver/exporter를 contrib에 PR로 올린다. Datadog, New Relic, Splunk, Honeycomb, Lightstep, AWS X-Ray, Google Cloud Trace 등등. 특이한 SaaS로 데이터를 보내야 한다거나, 마이너한 메시지 큐에서 메트릭을 빼야 한다거나 하면 contrib가 거의 항상 답을 갖고 있다.
Alloy는 OTel 컴포넌트를 wrapping하긴 하는데, 모든 contrib 컴포넌트가 다 들어와 있진 않다. 자주 쓰는 건 다 있다. 다만 "어 이거 OTel contrib에는 있는데 Alloy에 없네" 하는 케이스가 가끔 있다. 마이그레이션 검토할 때 사전 체크가 필요한 부분이다.
반대로 Alloy는 Prometheus 진영 컴포넌트가 OTel보다 풍부하다. prometheus.exporter.* 시리즈로 mysqld_exporter, node_exporter, blackbox_exporter, redis_exporter 같은 걸 별도 프로세스 안 띄우고 Alloy 안에서 돌릴 수 있다. 노드당 사이드카 컨테이너 수를 줄이려는 환경에서는 이게 꽤 매력적이다.
Grafana 스택 쓰면 Alloy, 안 쓰면 OTel이 무난하다
이게 결국 가장 단순한 결정 기준이다.
Loki + Mimir + Tempo + Pyroscope를 이미 굴리는 팀이면 Alloy가 자연스럽다. loki.write, prometheus.remote_write, otelcol.exporter.otlp 같은 게 다 한 config에 들어가고, profiling까지 한 에이전트에서 본다. 여러 agent 띄울 일이 없다.
반대로 백엔드가 Grafana 스택이 아니거나, 멀티 벤더(예: Datadog + 내부 Prometheus) 환경이거나, 언젠가 백엔드를 바꿀 가능성이 있는 조직이면 OTel Collector가 안전하다. vendor neutral이라는 말은 마케팅 문구가 아니라 실제로 exporter만 바꾸면 같은 파이프라인이 도는 구조라서 그렇다.
우리 팀의 경우, 메트릭은 Mimir로 보내고 로그는 Loki, 트레이스는 Tempo로 쏘는 전형적인 Grafana 스택이라 Alloy를 고르는 게 자연스러웠다. 다만 멀티 클러스터 중 일부는 Datadog APM 백엔드를 같이 쓰는 곳이 있어서, 그쪽은 OTel Collector를 따로 운영한다. 한 가지로 통일하지 못한 게 약간 찜찜하긴 한데, 무리하게 통일했다면 더 골치 아팠을 거라고 본다.
실무에서 부딪힌 자잘한 차이들
문서에 잘 안 나오는데 운영하면서 느낀 것들 몇 가지.
Alloy는 config 변경 시 hot reload가 좀 더 매끄럽다. SIGHUP 보내면 변경된 컴포넌트만 재구성하고 나머지는 그대로 둔다. OTel Collector도 reload는 되는데, 동작 범위가 더 거칠다는 인상.
OTel Collector는 distro 개념이 잘 잡혀 있다. opentelemetry-collector-builder로 필요한 컴포넌트만 골라서 커스텀 바이너리를 만들 수 있다. 보안팀이 "사용 안 하는 receiver는 빼라"고 하는 환경에선 이 기능이 진짜 유용하다. Alloy는 단일 바이너리에 컴포넌트가 다 들어있는 형태라 이런 트림은 어렵다.
리소스 사용량은 비슷하다고들 하는데, 실측해보면 같은 워크로드에서 Alloy가 약간 더 먹는 경우가 있었다. 컴포넌트 그래프 평가 오버헤드인 듯하다. 1~2% 수준이라 운영에 영향을 줄 정도는 아니다.
그래서 어떻게 정리하나
이번에 팀 내부 정리한 가이드는 대충 이렇게 나왔다.
새 클러스터에 새로 깔거나, 백엔드가 Grafana 스택 중심이면 Alloy. River 학습 비용이 있지만 한 번 익히면 운영성이 좋다.
기존에 OTel Collector를 잘 굴리고 있고 큰 불편이 없으면 굳이 Alloy로 옮기지 않는다. 마이그레이션 비용 대비 얻는 게 명확하지 않은 환경이 대부분이다.
멀티 벤더 백엔드거나 SaaS 옵저버빌리티 벤더에 락인되어 있으면 OTel Collector. vendor neutral 설계가 실제 도움이 된다.
이상한 receiver(특정 SaaS, 마이너 메시지 큐 등)가 필요하면 OTel contrib에 있는지 먼저 보고, 그게 Alloy로 wrapping되어 있는지 체크. 없으면 OTel Collector가 답이다.
쓰다 보면 두 도구를 같이 쓰는 케이스도 흔해질 것 같다. 노드 레벨 수집은 Alloy로, 게이트웨이/aggregation 레이어는 OTel Collector로 가는 식. 사실 우리도 그런 구조로 가고 있다.
혹시 다른 팀에서는 어떻게 결정하고 있는지 궁금하다. 특히 Alloy로 풀 마이그레이션한 팀의 6개월 후 회고가 있으면 보고 싶다.