Grafana Agent에서 Alloy로 넘어가는 실전 가이드
왜 지금 넘어가야 하나
Grafana Agent(Static/Flow/Operator)가 2025년 11월 1일에 공식적으로 EOL을 맞았다. 우리 팀도 그 전에 다 넘겨야 했는데, 막상 문서를 보면 "convert 명령어 쓰세요" 정도로 뭉뚱그려져 있어서 실무에서 걸리는 지점이 꽤 있었다. 이 글은 프로덕션 클러스터 3개(스테이징 1, 프로덕션 2 — 노드 합쳐서 80대 정도)에서 넘긴 기록을 정리한 것이다.
Alloy는 이제 단순히 Agent 후속작이 아니라 Grafana Labs가 OpenTelemetry Collector를 자기네 방식으로 배포하는 형태에 가깝다. 이게 왜 중요하냐면, 기존에 metrics {}, logs {} 블록으로 쪼개 놓던 것이 otelcol.* 파이프라인과 나란히 놓이면서 config 사고 방식이 좀 달라진다.
솔직히 이걸 왜 굳이 다시 정리하냐면, "EOL 지났는데 그냥 놔둬도 되지 않냐"는 질문을 팀 내부에서 두 번쯤 받았기 때문이다. 답은 "안 된다"에 가깝다.
- CVE 패치가 더 안 나온다. 최근 3개월 사이에도 Alloy 쪽에는 릴리스가 계속 붙는데 Agent에는 안 붙는다.
- Fleet Management 같은 신규 기능은 Alloy에서만 동작한다. 원격에서 config 롤아웃하는 기능이 꽤 편해서, 여러 리전에 흩어진 콜렉터를 관리하는 팀이라면 이거 하나 때문에라도 넘어갈 만하다.
- Loki 4.x, Pyroscope 최신 스키마 같은 백엔드 쪽 신기능이 Agent 클라이언트에서 못 따라간다.
마이그레이션 사전 준비
넘기기 전에 딱 세 가지만 확인하면 사고를 크게 줄일 수 있다.
먼저 현재 Agent가 어떤 배포판인지 정확히 알아야 한다. Static인지, Flow인지, Operator인지에 따라 경로가 다르다. grafana-agent --version 결과와 config 상단이 metrics: YAML인지 prometheus.scrape "..." {} 블록인지로 판별 가능하다. Operator는 CRD(GrafanaAgent, MetricsInstance 등)로 알 수 있다.
그 다음 remote_write 엔드포인트를 정리해 둔다. Alloy로 옮기면서 라벨이 하나만 바뀌어도 대시보드가 다 깨지는데, 이걸 리허설 없이 프로덕션에 붙였다가 알럿이 5분 동안 다 침묵한 경험이 있다. 다시는 하고 싶지 않다.
마지막으로 스크레이프 타겟 수와 시리즈 카디널리티를 기록해 둔다. 옮기고 나면 첫 15분은 이 두 숫자를 계속 봐야 한다.
convert 명령어부터 돌려보자
Grafana Alloy에는 convert 서브커맨드가 있는데, 기존 Prometheus/Static Agent config를 River(이제는 Alloy config syntax) 형식으로 자동 변환해 준다. 실무에서는 이걸 그대로 쓰면 안 되고, 초안으로만 봐야 한다.
# Static Agent config → Alloy config
alloy convert --source-format=static \
--output=/tmp/alloy.river \
/etc/grafana-agent/agent.yaml
# 순수 Prometheus config에서 시작하는 경우
alloy convert --source-format=prometheus \
--output=/tmp/alloy.river \
/etc/prometheus/prometheus.yml
변환 결과는 놀랍게도 꽤 깔끔하다. scrape_configs가 prometheus.scrape "job_name" {} 블록으로 바뀌고, remote_write는 prometheus.remote_write "default" {}로 분리된다. 이 두 블록이 forward_to로 연결되는 구조가 Alloy 사고 방식의 핵심이다.
실제 config 예시
우리 팀에서 실제로 쓰는 형태를 조금 정리해서 옮기면 이런 식이다.
prometheus.remote_write "default" {
endpoint {
url = "https://prom.internal/api/v1/write"
basic_auth {
username = env("PROM_USER")
password = env("PROM_PASS")
}
queue_config {
max_samples_per_send = 2000
capacity = 20000
max_shards = 50
}
}
external_labels = {
cluster = "prod-apne2",
env = "prod",
}
}
discovery.kubernetes "pods" {
role = "pod"
}
prometheus.scrape "kubernetes_pods" {
targets = discovery.kubernetes.pods.targets
forward_to = [prometheus.remote_write.default.receiver]
scrape_interval = "30s"
scrape_timeout = "15s"
}
queue_config는 convert 결과에는 안 들어간다. 기본값이 대체로 무난하긴 한데, 우리 클러스터는 시리즈가 좀 많아서 capacity와 max_shards를 직접 튜닝해야 했다. 이 값 안 만지면 첫 배포 후 15분쯤 뒤에 prometheus_remote_storage_samples_dropped_total이 튀는 걸 볼 수 있다.
Loki 로그도 같이 옮기기
Static Agent에서 logs: 블록으로 관리하던 Promtail 파이프라인은 loki.source.*와 loki.write로 분해된다.
loki.write "default" {
endpoint {
url = "https://loki.internal/loki/api/v1/push"
}
external_labels = {
cluster = "prod-apne2",
}
}
loki.source.kubernetes "pods" {
targets = discovery.kubernetes.pods.targets
forward_to = [loki.process.default.receiver]
}
loki.process "default" {
forward_to = [loki.write.default.receiver]
stage.docker {}
stage.labels {
values = {
namespace = "namespace",
pod = "pod",
}
}
}
여기서 함정 하나. Promtail 시절에는 파일에서 직접 읽었는데(static_configs + file path), loki.source.kubernetes는 Kubernetes API를 통해 컨테이너 로그를 가져온다. 이게 편하긴 한데, RBAC이 없으면 조용히 실패한다. pods/log verb에 대한 ClusterRole을 잊지 말고 붙여야 한다. 처음 옮기고 로그가 안 올라와서 30분 동안 원인을 못 찾았다.
배포 전략
블루/그린 방식으로 넘겼다. 즉 새 Alloy 파드를 별도 라벨로 띄워서 remote_write는 같은 엔드포인트로 붙이되, 기존 Agent도 그대로 두는 것이다.
이렇게 하면 잠깐 동안 시리즈가 두 배로 뜨는데, 이건 대시보드에서 job 라벨이나 alloy 특정 external label로 필터 걸어서 검증할 수 있다. 3~4시간 정도 병렬로 돌려 보면 확실히 판단이 서고, 그 다음 기존 Agent를 내리면 된다.
카디널리티 폭탄이 무섭다면 external_labels에 deployment="alloy-canary" 같은 걸 붙여 두면 나중에 지우기도 쉽다.
실제로 겪은 사소한 문제들
prometheus.relabel 컴포넌트가 스크레이프 앞에 붙는지 뒤에 붙는지가 헷갈리는 경우가 많다. 정답은 "타겟을 걸러낼 거면 앞, 라벨을 조작할 거면 뒤"인데, 이 구분을 흐리게 쓰면 컴포넌트 그래프가 순환해서 alloy가 fmt 검증에서 튕겨낸다.
또 하나, otelcol.* 컴포넌트들과 prometheus.* 컴포넌트를 섞으면 데이터 타입이 다르다는 이유로 forward_to에 못 붙는다. Prometheus 파이프라인에서 나온 데이터를 OTLP로 내보내고 싶으면 중간에 otelcol.receiver.prometheus를 두고 변환해야 한다. 이건 문서에 있긴 한데 잘 안 보인다.
그래서, 넘긴 후 뭐가 달라졌나
메모리 사용량이 약간 올랐다(파드당 평균 220MB → 260MB 정도). 이건 OTLP 파이프라인이 같이 로드되기 때문이라고 짐작하고 있다. 반대로 스크레이프 지연 P99는 살짝 내려갔는데, 왜 그런지는 아직 정확히 못 짚었다. 좀 더 관측한 뒤에 별도로 정리해 보려고 한다.
Fleet Management는 아직 도입 안 했다. 팀 내부적으로 GitOps 흐름(Argo CD로 alloy config CM 배포)이 이미 잘 돌고 있어서, 굳이 지금 겹치는 걸 넣을 필요는 없어 보인다. 이건 팀 규모나 리전 수에 따라 판단이 다를 텐데, 5개 이상 리전 관리하는 팀이면 볼 만하다고 들었다.
혹시 넘기다가 remote_write 라벨 문제로 대시보드 깨진 경험 있는 분 있으면 어떻게 해결했는지 좀 알려주세요. 우리는 recording rule 대량 재작성으로 대응했는데, 더 나은 방법이 있을 것 같아서.