IT/모니터링

Grafana Agent에서 Alloy로 넘어가는 실전 가이드

gfrog 2026. 7. 17. 12:12

왜 지금 넘어가야 하나

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_configsprometheus.scrape "job_name" {} 블록으로 바뀌고, remote_writeprometheus.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 결과에는 안 들어간다. 기본값이 대체로 무난하긴 한데, 우리 클러스터는 시리즈가 좀 많아서 capacitymax_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 대량 재작성으로 대응했는데, 더 나은 방법이 있을 것 같아서.