Grafana Alloy vs OpenTelemetry Collector, 우리 팀은 뭘 골랐나
한 달 반쯤 팀 내부에서 계속 논쟁이 있었다. Grafana Agent가 EOL 방향으로 가면서 Alloy로 갈아탈지, 아니면 이참에 그냥 순정 OpenTelemetry Collector(otelcol)로 통일할지. 결론부터 말하면 우리는 양쪽 다 쓴다. 딱 정하는 게 실무에서는 오히려 이상한 답이었다. 왜 그런 결론이 났는지 정리해둔다.
왜 이 논쟁이 지금 튀어나왔나
Grafana Agent가 2025년 말에 사실상 유지보수 모드로 들어갔고, 팀이 쓰던 Agent Flow 설정을 어디로 옮기느냐가 실질적인 문제였다. 자연스러운 후보는 두 개.
하나는 Grafana Alloy. Agent Flow의 후속이니 마이그레이션 문서도 잘 정리돼 있고, alloy convert 명령으로 기존 config를 그대로 밀어넣을 수도 있다. 다른 하나는 vendor-neutral 진영의 OpenTelemetry Collector. contrib 배포판을 쓰면 receiver/processor/exporter 조합이 문자 그대로 수백 개다.
문제는 우리 팀 상황이 애매하게 걸쳐 있다는 거였다. 메트릭/로그는 Grafana Cloud로 밀고 있는데, 트레이스는 자체 호스팅 Tempo가 아니라 Datadog으로 이중 전송한다. 그리고 EKS 위에서 사이드카/데몬셋 형태로 노드마다 하나씩 돌린다.
각각 뭐가 좋고 뭐가 껄끄러웠나
Alloy를 먼저 만져봤다. 확실히 Grafana 스택에 붙일 때는 편하다. loki.write, prometheus.remote_write, pyroscope.write 같은 컴포넌트가 native로 있어서 인증, retry, backoff 튜닝을 굳이 옵션 파일 뒤져가며 손댈 일이 별로 없다. UI(디버그 페이지)도 그럭저럭 쓸만하다. 파이프라인이 그래프로 나온다.
근데 몇 가지가 걸렸다.
첫째, 설정 언어가 River(지금은 그냥 Alloy syntax라고 부르는)다. HCL 비슷한데 미묘하게 다르고, 우리 팀은 이미 Terraform HCL, otelcol YAML, Prometheus YAML을 다루고 있어서 언어 하나가 더 늘어난다. 신입 온보딩 문서에 "Alloy는 이렇게 쓰는 언어예요" 페이지가 하나 더 붙는 건 은근히 부담이다.
둘째, OpenTelemetry 호환이라고 하지만 완전한 upstream 미러링은 아니다. 예를 들어 특정 processor 옵션이 upstream에는 있는데 Alloy 쪽에는 아직 반영이 안 됐다든가, otelcol.processor.transform 같은 컴포넌트가 있어도 OTTL(OpenTelemetry Transformation Language) 최신 함수 지원이 한 두 버전씩 뒤에 있는 경우가 있었다. 크리티컬한 건 아닌데, upstream 문서 보고 그대로 따라 했을 때 "어? 이 함수 없다는데?" 하는 순간이 몇 번 나왔다.
셋째, contrib 진영에 있는 exotic한 receiver, 예를 들어 특정 SaaS의 webhook receiver 같은 건 Alloy에는 아예 포팅이 안 돼 있다. Alloy는 upstream의 subset을 선별해서 담고 있어서 "일단 뭐든 다 있음"이 아니다.
otelcol contrib 쪽은 반대다. 컴포넌트는 문자 그대로 다 있는데, Grafana Cloud 특유의 것들은 손이 조금 더 간다. Loki push는 그냥 lokiexporter 쓰면 되긴 하는데, tenant 헤더나 org id 같은 걸 헤더로 직접 넣어줘야 하고, 프로파일링(Pyroscope)은 아예 upstream에 exporter가 없어서 Alloy 아니면 Pyroscope agent를 따로 돌려야 한다.
그리고 YAML이 좀 길어진다. 다들 알겠지만.
그래서 실제로 어떻게 나눴나
결국 이렇게 결정했다.
노드 데몬셋 = Alloy. 노드에서 로그를 긁고, kubelet 메트릭을 스크레이프하고, 프로파일링까지 챙기는 무거운 역할은 Alloy가 맡는다. loki.source.kubernetes, pyroscope.scrape, prometheus.exporter.unix 같은 조합이 그냥 잘 붙는다. 여기서 vendor-neutral 유지하려고 굳이 힘 뺄 이유가 없었다.
앱 사이드카 / 게이트웨이 = otelcol contrib. 앱에서 나오는 OTLP를 받고, Datadog으로 트레이스 미러링하고, tail-sampling 걸고, 필요하면 attribute 뜯어고치고 하는 pipeline은 순정 otelcol에서 돌린다. 여기서 중요했던 게 OTTL 버전이 최신이어야 한다는 점과 Datadog exporter가 upstream에서 활발하게 관리된다는 점이었다. 그리고 앱팀이 OTel SDK로 계측을 표준화하고 있으니까, 게이트웨이도 같은 진영으로 맞추는 게 논리적으로 맞았다.
즉, "인프라 계측(로그/노드 메트릭/프로파일링)"은 Alloy, "애플리케이션 계측(트레이스/커스텀 메트릭 파이프라인)"은 otelcol. 이 축으로 갈랐다.
껄끄러웠던 부분
이렇게 하면 자연스럽게 두 개를 유지해야 한다는 문제가 생긴다. Helm 차트도 두 개, 업그레이드 절차도 두 개, 알람도 두 개. 팀 내 리뷰에서도 "그냥 하나로 통일하는 게 낫지 않냐"는 의견이 있었다.
근데 반대로, 하나로 억지로 통일하려고 하면 어느 쪽이든 실무 상 손해가 나는 부분이 생겼다. Alloy 하나로 밀면 tail-sampling processor 최신 기능이나 Datadog exporter의 세밀한 옵션에서 뒤처지고, otelcol 하나로 밀면 Pyroscope 프로파일링을 별도 agent로 또 돌리거나 우회해야 한다. 둘 다 감수할 만큼의 이득이 없었다.
그리고 사실 리소스 오버헤드도 별로 안 크다. 노드 데몬셋에는 Alloy 하나가 돌 뿐이고, otelcol 게이트웨이는 클러스터당 몇 개 replica만 있으면 된다. 프로세스 두 개를 노드마다 겹치는 구조가 아니다.
마이그레이션할 때 실제로 도움된 것
alloy convert --source-format=otelcol 명령이 은근히 유용했다. 우리는 반대 방향(Agent Flow → Alloy)이었지만, 팀 다른 서비스가 otelcol에서 Alloy로 부분 이동할 때는 이걸로 초안을 뽑아놓고 손봤다. 초안을 그대로 프로덕션에 넣지는 말고, converted 파일을 diff로 리뷰하는 게 낫다. exporter 이름이 바뀌면서 옵션 semantics가 미묘하게 다른 경우가 있다.
otelcol 쪽에서는 otel-tui 같은 로컬 디버깅 도구가 도움이 됐다. staging 환경에서 collector 파이프라인 바꾸기 전에 로컬에서 receive 잘 되는지 미리 확인.
결론 비슷한 것
"Alloy가 더 낫다" 또는 "otelcol이 더 낫다" 같은 이분법은 우리 케이스에는 안 맞았다. 각자 잘하는 지점이 다르고, 그 지점을 서로 침범해서 잘하지도 못한다. 정치적으로 vendor-neutral을 강하게 밀어야 하는 조직이라면 otelcol 하나로 밀 수도 있고, Grafana 스택 안에서만 놀 거면 Alloy 하나로도 충분하다. 그 사이 어딘가에 있으면 그냥 둘 다 쓰는 게 자연스럽다.
물론 이건 우리 팀의 지금 시점 판단이고, 몇 달 뒤에 Alloy가 upstream OTel과 격차를 좁히거나, 반대로 upstream에 Pyroscope exporter가 들어오면 다시 리뷰할 예정이다. 관측성 도구는 원래 6개월에 한 번씩 재평가해야 한다고 팀에서 반농담으로 얘기한다.
혹시 비슷한 결정 겪고 있는 분 있으면 어떻게 갈랐는지 궁금하다. Alloy 하나로 통일한 팀이 실제로 어떤 부분에서 아쉬웠는지 후기가 제일 궁금한 부분.