IT/모니터링

Promtail EOL 앞두고 Grafana Alloy로 갈아탄 이야기

gfrog 2026. 9. 2. 00:16

지난 봄에 Promtail 공식 EOL 공지(2026년 3월 2일) 이후로 계속 미뤄오다가, 이번 분기 초에 결국 Grafana Alloy 마이그레이션을 밀어붙였다. 노드 80대 규모 EKS 두 클러스터에서 Promtail DaemonSet + 별도 Grafana Agent Static이 각각 metrics/logs를 뿌리고 있었는데, Agent도 이미 2025년 11월에 EOL을 맞은 상태였다. 사실 진작 했어야 하는 일인데, 잘 돌아가는 걸 굳이 건드리기 싫어서 미루다가 SRE 슬랙에 "Promtail 이제 CVE 안 고쳐준대요"라는 글이 올라오고 나서야 결심이 섰다.

솔직히 처음엔 단순 재브랜딩쯤으로 생각했다. 컨피그 문법이 River로 바뀌긴 했지만 alloy convert 명령 하나로 promtail YAML을 뽑아준다는 문서를 믿었기 때문이다. 결과부터 말하면 그 자동 변환이 오히려 발목을 잡았다.

alloy convert가 만들어준 것과 실제 필요한 것의 간극

첫 시도는 이러다.

alloy convert --source-format=promtail --output=alloy-config.alloy promtail.yaml

깔끔하게 나온 것 같아서 스테이징 클러스터에 그대로 배포했다. 그런데 로그 라인 라벨이 예전과 미묘하게 달랐다. Promtail에서는 filename 라벨로 파일 경로가 붙었는데, 변환된 Alloy 컨피그는 __path__만 남기고 최종 라벨은 다르게 매핑하고 있었다. Loki 대시보드가 죄다 깨졌다. {filename=~"...*"} 쿼리들이 매치하는 스트림 자체가 없어져버린 것이다.

원인을 파보니 alloy convert는 Promtail의 pipeline_stages 중 몇 가지를 그대로 옮기지 않고 "Alloy에서 권장하는" 방식으로 리라이트한다. discovery.kubernetes 컴포넌트가 붙이는 기본 라벨과 이전 Promtail의 relabel 규칙이 겹치는 경우 특히 그렇다. 문서 어딘가에 있긴 한데 변환 결과 파일만 봐서는 알 수가 없다.

결국 변환된 결과는 초안으로만 쓰고, 라벨 매핑을 하나씩 검증하면서 다시 썼다. 이 과정에서 우리 팀 내부 논의 끝에 결정한 규칙 하나 — 변환 툴 결과를 그대로 커밋하지 말자. diff 리뷰가 사실상 불가능하다.

River가 HCL이 아니라는 걸 몸으로 배웠다

River 문법이 HCL을 닮은 것 같아서 그렇게 다뤘다가 몇 시간 날렸다. 예를 들어 이런 걸 쓰고 싶었다.

loki.source.kubernetes "pods" {
  targets    = discovery.kubernetes.pods.targets
  forward_to = [loki.process.filter.receiver]

  // 우리 팀 로그 포맷: JSON에서 level=ERROR만 별도 채널로
  extra = { channel = level == "ERROR" ? "critical" : "normal" }
}

이렇게 쓰면 안 된다. River는 참조를 지원하지만 컴포넌트 블록 안에서 임의 표현식으로 다른 컴포넌트 상태를 실시간 참조하는 방식이 아니다. 컴포넌트 사이 데이터 흐름은 명시적으로 forward_toreceiver 인자로만 이어야 하고, 필터링/변형은 별도의 loki.process 스테이지에서 처리해야 한다. 처음엔 이게 답답했는데 며칠 써보니 오히려 컴포넌트 그래프가 눈에 잘 들어와서 디버깅이 편해졌다.

또 하나 실수: -config.file 인자와 실제 배포시 사용하는 run 서브커맨드의 인자 순서 문제. 헬름 차트 values를 커스터마이즈하다가 args 배열에 파일 경로를 두 번 넣어서 컨테이너가 재시작 루프에 빠진 일이 있었다. CrashLoopBackOff에서 kubectl logs --previous 찍어보니 "unknown config file" 에러가 반복되고 있었다. 이건 순전히 눈으로 놓친 실수인데, 헬름 values의 extraArgs가 차트 기본 args 앞이 아니라 뒤에 붙는다는 걸 몰랐다.

Loki 쪽 카디널리티가 조용히 올라가고 있었다

배포하고 이틀쯤 지나서 Loki ingester CPU가 평소보다 40% 정도 높은 걸 발견했다. 처음엔 그냥 트래픽 스파이크라고 생각했는데, active stream 개수가 계단식으로 계속 올라가고 있었다. 원인은 Alloy가 붙이는 service_name 라벨 — OpenTelemetry semantic conventions를 자동 적용하다 보니, 우리가 정리해뒀던 라벨 화이트리스트를 빠져나가는 새 라벨이 스트림 폭발을 만들고 있었다.

otelcol.processor.attributesdelete 액션을 추가해서 진정시켰다.

otelcol.processor.attributes "trim" {
  action {
    key    = "service_name"
    action = "delete"
  }

  output {
    logs = [otelcol.exporter.loki.default.input]
  }
}

이런 자동 라벨 부여는 Alloy가 좀 더 적극적이다. 편해 보이지만 마이그레이션 직후엔 오히려 이걸 다 꺼두고 필요한 것만 켜는 편이 안전하다는 게 이번 교훈이다.

롤아웃은 클러스터별로, DaemonSet은 노드별로

가장 조심한 부분이 롤아웃 전략이었다. Promtail을 그냥 지우고 Alloy로 갈아엎으면 롤아웃 중 로그 유실이 생길 수 있다. 우리는 이렇게 했다.

첫째, 스테이징 클러스터에서 Alloy DaemonSet을 별도 이름(alloy-logs)으로 배포해서 Promtail과 병렬로 며칠 돌렸다. 같은 로그가 Loki에 이중으로 들어가는데, 라벨 셀렉터로 구분해서 대시보드 검증에 썼다. 라벨/스트림/쿼리 결과가 일치하는 걸 확인한 뒤에 Promtail을 제거했다.

둘째, 프로덕션은 노드 라벨 기반으로 카나리 배포했다. nodeSelectoralloy-canary=true 붙인 노드 2대에서 먼저 돌리고, 24시간 관찰 후 나머지에 확장. DaemonSet의 rolling update 자체는 노드마다 순차 진행이라 대체로 안전하지만, 문제가 났을 때 롤백 범위를 좁히려면 이게 훨씬 낫다.

마무리 — 아직 다 못 끝냈다

metrics 쪽은 이번에 같이 안 옮겼다. Agent Static의 metrics.wal 설정이 우리 리모트라이트 세팅과 얽혀 있어서, 이건 다음 스프린트에 별도로 뜯을 예정이다. Alloy에서는 prometheus.remote_write가 담당하는데, WAL 경로 마이그레이션 중 데이터 유실 시나리오를 좀 더 봐야 한다.

혹시 지금 Promtail 쓰고 계신 분들, 3월 EOL 지나서 CVE 하나 터졌을 때 급하게 하지 마시고 시간 두고 하는 걸 권한다. 특히 alloy convert 결과를 그대로 신뢰하지 말고 라벨 하나하나 검증하시길. 우리 팀은 결국 이걸 2주 정도 잡고 진행했는데, 원래 예상은 이틀이었다.

다른 팀에서 Alloy로 옮기신 분들 중 metrics WAL 마이그레이션 경험 있으신 분 계시면 댓글이나 DM으로 좀 알려주시면 감사하겠습니다.


태그: Grafana Alloy, Promtail, Loki, observability, migration, 관측성, DevOps