Prometheus Grafana 알람 Slack 연동 설정 — 웹훅·임계값 튜닝·온콜 라우팅

목차

Prometheus는 시계열 메트릭을 수집하고 조건에 따라 알람을 발생시키는 오픈소스 모니터링 도구다. Alertmanager가 그 알람을 받아서 Slack이나 PagerDuty 같은 채널로 전송하고, Grafana가 메트릭을 시각화한다. 이 세 가지를 조합하면 서버 상태 감시부터 장애 알림까지 Prometheus Grafana 알람 Slack 연동 설정 한 세트가 완성된다.

스테이징 서버에 배포 직후 CPU가 92%를 찍은 적이 있다. Grafana 대시보드는 있었는데 알람이 없어서 아무도 몰랐다. 직접 대시보드를 열어보기 전까지 30분 넘게 서버가 거의 뻗어 있었다. 그 이후로 모니터링만 해놓고 알람을 안 건다는 게 얼마나 의미 없는 일인지 체감했고, 그날부터 전체 알람 파이프라인을 구성하기 시작했다.

Prometheus 알람 시스템의 구조

Prometheus → Alertmanager → Receiver 파이프라인

Prometheus의 알람 흐름은 생각보다 단순하다. Prometheus가 alerting rule을 주기적으로 평가해서 조건이 충족되면 ALERTS 메트릭을 firing 상태로 전환한다. 이 firing된 알람은 설정된 Alertmanager 엔드포인트로 HTTP POST된다. Alertmanager는 받은 알람을 그룹핑하고, 라우팅 규칙에 따라 적절한 receiver로 전송한다.

핵심은 Prometheus 자체가 알람을 "전송"하지 않는다는 거다. Prometheus는 알람을 "발생"시키기만 하고, 실제 Slack이나 이메일 전송은 전부 Alertmanager 몫이다. 이 분리 구조를 모르면 "rules가 firing인데 왜 Slack에 안 오지?"라는 상황에 빠진다. Prometheus 웹 UI의 Alerts 탭에서는 빨간색으로 firing이 뜨는데 정작 알림은 안 오는, 꽤 혼란스러운 상태가 된다.

prometheus.yml에서 Alertmanager 주소를 지정하는 부분이 전체 파이프라인의 시작점이다:

# prometheus.yml
alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - 'alertmanager:9093'  # Docker Compose 환경에서는 서비스명 사용

rule_files:
  - 'rules/*.yml'  # alerting rule 파일 경로

이 설정이 빠져 있으면 Prometheus는 알람을 평가만 하고 어디에도 보내지 않는다. 설정 후 http://prometheus:9090/api/v1/alertmanagers 엔드포인트를 호출해서 Alertmanager가 활성 타겟으로 잡혀 있는지 확인하는 습관을 들이는 게 좋다. 여기서 빈 배열이 오면 연결 자체가 안 된 거다.

alerting rules 파일 작성

rules 파일은 PromQL 기반이다. groups 아래에 rules 배열을 넣고, 각 rule에 alert 이름, expr 조건식, for 지속 시간, labels, annotations를 지정한다:

# rules/node_alerts.yml
groups:
  - name: node_alerts
    rules:
      - alert: HighCpuUsage
        expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "CPU 사용률 {{ $value | printf \"%.1f\" }}% ({{ $labels.instance }})"
          description: "5분 이상 CPU 80% 초과 상태 지속"

      - alert: HighMemoryUsage
        expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 85
        for: 3m
        labels:
          severity: critical
        annotations:
          summary: "메모리 사용률 {{ $value | printf \"%.1f\" }}%"

for 필드의 역할이 크다. 이걸 안 넣거나 너무 짧게 잡으면 순간적인 스파이크에도 알람이 울린다. 처음에 for: 1m으로 잡았더니 매 배포마다 컨테이너 재시작 과정에서 CPU 스파이크가 발생해 알람이 쏟아졌다. 5분으로 올리니까 배포 시 일시적 스파이크는 걸러지고, 실제 문제만 잡히기 시작했다.

labels.severity는 단순 태그가 아니다. 나중에 Alertmanager의 route에서 이 값을 기준으로 분기할 수 있다. warning은 Slack 채널로, critical은 Slack + PagerDuty로 보내는 식의 라우팅이 가능해진다. rule 작성 단계에서 severity를 일관되게 붙여두는 게 나중에 라우팅 설정을 단순하게 만든다.

공식 문서의 alerting rules 섹션(prometheus.io/docs/alerting/latest/overview/)에 PromQL 표현식 예제가 더 있다.

관련 추천 상품 보기 — 개발자를 위한 추천 장비와 도구를 확인해보세요. 쿠팡에서 보기 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

Alertmanager Slack 웹훅 연동

Slack Incoming Webhook 생성

Slack 웹훅은 api.slack.com/apps에서 Slack App을 만들고, 거기에 Incoming Webhook을 추가하는 방식이 2026년 4월 기준 권장 방식이다. 예전의 Legacy Incoming Webhooks도 아직 동작하긴 하지만 신규 생성이 제한되는 추세다.

절차 자체는 간단하다. "Create New App" → "From scratch" → App 이름 지정(예: prometheus-alerts) → "Incoming Webhooks" 활성화 → "Add New Webhook to Workspace" → 채널 선택. 생성된 URL은 https://hooks.slack.com/services/T.../B.../xxx 형태다.

한 가지 주의할 점은, Slack App 방식의 webhook은 해당 App을 알람 받을 채널에 초대(invite)해야 한다는 거다. 안 그러면 Alertmanager에서 channel_not_found 에러가 나는데, 이 에러가 로그에 좀 느리게 찍혀서 처음에는 원인을 못 찾을 수 있다.

alertmanager.yml 설정 파일

여기서 한 가지 시행착오가 있었다. 처음에 webhook URL을 url 필드에 넣었는데 아무 반응이 없었다. Alertmanager 로그를 뒤져보니 level=error component=dispatcher msg="Notify for alerts failed" 에러가 찍혀 있었다. slack_configs에서 올바른 필드명은 api_url이다. url이 아니라 api_url. 이것 때문에 1시간 가까이 헤맸다.

전체 설정 파일은 이렇게 구성했다:

# alertmanager.yml
global:
  resolve_timeout: 5m
  slack_api_url: 'https://hooks.slack.com/services/T.../B.../기본웹훅URL'

route:
  receiver: 'slack-warning'
  group_by: ['alertname', 'severity']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - match:
        severity: critical
      receiver: 'slack-critical'
      repeat_interval: 1h

receivers:
  - name: 'slack-warning'
    slack_configs:
      - channel: '#alerts-warning'
        send_resolved: true
        title: '⚠️ {{ .GroupLabels.alertname }}'
        text: >-
          {{ range .Alerts }}
          *{{ .Annotations.summary }}*
          {{ .Annotations.description }}
          {{ end }}

  - name: 'slack-critical'
    slack_configs:
      - channel: '#alerts-critical'
        send_resolved: true
        title: '🔴 {{ .GroupLabels.alertname }}'
        text: >-
          {{ range .Alerts }}
          *{{ .Annotations.summary }}*
          인스턴스: {{ .Labels.instance }}
          {{ end }}

global.slack_api_url에 기본 webhook URL을 넣으면, 각 receiver의 slack_configs에서 api_url을 생략할 수 있다. 채널마다 다른 webhook을 쓰고 싶으면 receiver 레벨에서 api_url을 별도 지정하면 된다.

send_resolved: true는 반드시 넣는 게 좋다. 이걸 빼면 알람 발생 알림만 오고 복구 알림은 안 온다. Slack 채널에 빨간 알람만 쌓이는 상태가 되면, 현재 문제가 진행 중인 건지 이미 복구된 건지 구분할 수 없다. 복구 알림이 오면 초록색 메시지가 와서 한눈에 구분된다.

Alertmanager 설정 문법 상세는 공식 문서(prometheus.io/docs/alerting/latest/configuration/)에서 확인할 수 있다. 특히 <slack_config> 섹션에 template 함수 목록이 정리돼 있다.

연동 테스트와 자주 만나는 에러

설정을 다 했으면 amtool로 테스트 알람을 보내볼 수 있다:

# 테스트 알람 생성
amtool alert add test_alert severity=warning instance=localhost:9090 \
  --alertmanager.url=http://localhost:9093 \
  --annotation.summary="테스트 알람"

# 현재 firing 중인 알람 확인
amtool alert --alertmanager.url=http://localhost:9093

테스트 알람을 보냈는데 Slack에 안 오면 대부분 아래 세 가지 중 하나다:

증상 원인 해결
로그에 connection refused webhook URL 오타 또는 만료 Slack App에서 webhook 재생성
로그에 에러 없는데 Slack 수신 없음 채널명 오타 또는 Bot 미초대 # 포함 채널명 확인, App을 채널에 초대
알람이 30초~1분 뒤에 뒤늦게 도착 group_wait 대기 중 테스트 시 group_wait: 5s로 낮춰서 확인

설정 파일 문법 검증은 Alertmanager를 재시작하기 전에 amtool check-config alertmanager.yml 명령으로 먼저 돌리는 게 안전하다. YAML 들여쓰기 실수로 Alertmanager가 아예 안 뜨는 경우가 있다.

임계값 튜닝 — 알람 피로 줄이기

알람 시스템에서 진짜 어려운 부분은 연동이 아니라 튜닝이다. YAML 몇 줄이면 알람은 오기 시작하는데, 임계값을 잘못 잡으면 하루에 알람이 수십 건씩 쏟아지거나, 반대로 진짜 장애를 놓친다. 둘 다 경험해봤는데 전자가 더 위험하다. 알람이 너무 많으면 아무도 안 보게 된다.

group_wait, group_interval, repeat_interval

이 세 파라미터가 알람 빈도의 핵심이다. 하나씩 역할이 다르다.

group_wait은 새로운 알람 그룹이 만들어졌을 때, 같은 그룹에 속할 수 있는 다른 알람을 기다리는 시간이다. 서버 3대에서 동시에 CPU 알람이 뜨면, group_wait 동안 모아서 Slack 메시지 하나로 묶어 보낸다. 30초~1분이 적당하다.

group_interval은 이미 알람을 보낸 그룹에 새로운 알람이 추가됐을 때 업데이트 알림을 보내는 간격이다. 기본값 5분이 대부분의 상황에서 무난하다.

repeat_interval은 같은 알람이 계속 firing 상태일 때 반복 알림 주기다. 이게 제일 민감하다. 처음에 repeat_interval을 30분으로 잡았더니 디스크 사용률 알람이 하루에 48번 왔다. 디스크는 용량을 비우지 않는 한 갑자기 내려가지 않으니까, 하루 종일 같은 알람이 반복되는 거다. warning은 4시간, critical은 1시간으로 분리하고, 디스크처럼 변화가 느린 메트릭은 repeat_interval을 12시간까지 올리니까 노이즈가 확 줄었다.

메트릭별 임계값 기준

서비스마다 적절한 임계값은 다르지만, 처음 구성할 때 출발점이 필요하다. 운영하면서 조정하더라도 너무 낮은 값에서 시작하면 알람 피로에 지쳐서 그냥 Slack 채널을 뮤트해버리는 사태가 벌어진다.

메트릭 warning critical for 비고
CPU 사용률 80% 95% 5m 배포 시 일시적 스파이크 감안
메모리 사용률 85% 95% 3m OOM killer 전에 잡아야 함
디스크 사용률 80% 90% 10m 변화가 느리므로 for를 길게
5xx 에러율 1% 5% 2m 절대 건수 아닌 비율 기준
응답 시간 p99 2s 5s 3m 서비스 SLA에 맞게 조정

이 표에서 가장 헤맨 건 5xx 에러율이었다. 처음에 절대 건수(increase(http_responses_total{status=~"5.."}[5m]) > 5)로 알람을 걸었더니, 트래픽이 거의 없는 새벽 시간대에 요청 10개 중 1개만 500이 나와도 알람이 울렸다. 비율 기반으로 바꾸면서 최소 요청 수 조건을 함께 걸어야 한다:

# 5xx 비율이 1% 초과 AND 분당 요청이 10건 이상일 때만
- alert: High5xxRate
  expr: |
    (
      rate(http_responses_total{status=~"5.."}[5m])
      / rate(http_responses_total[5m])
    ) > 0.01
    and
    rate(http_responses_total[5m]) > 0.17  # 분당 약 10건
  for: 2m
  labels:
    severity: warning

2주 정도 운영하면서 "조치가 필요 없었는데 울린 알람"을 기록해두면 임계값 조정의 근거가 된다. 체감상 초기 2주 동안 임계값을 3~4번은 수정하게 된다.

inhibit_rules로 중복 알람 억제

서버 한 대가 완전히 다운되면 CPU, 메모리, 디스크, 응답시간 알람이 전부 동시에 발생한다. Slack 채널에 5~6개 알람이 한꺼번에 쏟아지면 정작 "이 서버가 죽었다"라는 핵심 정보가 묻힌다.

inhibit_rules를 쓰면 상위 severity 알람이 firing 중일 때 같은 인스턴스의 하위 severity 알람을 억제할 수 있다:

# alertmanager.yml에 추가
inhibit_rules:
  - source_match:
      severity: 'critical'
    target_match:
      severity: 'warning'
    equal: ['instance']

이 설정으로 instance=server-1에서 critical 알람이 firing 중이면, 같은 서버의 warning 알람은 Slack으로 전송되지 않는다. 알람 폭풍 상황에서 체감 효과가 크다.

더 정교하게 하려면 NodeDown 같은 상위 알람을 하나 만들고, 그 알람이 firing일 때 해당 인스턴스의 모든 하위 알람을 억제하는 구조도 가능하다. 다만 inhibit_rules를 너무 복잡하게 만들면 "왜 이 알람이 안 왔지?" 하는 디버깅이 어려워지니까, 처음에는 critical → warning 억제 한 줄만 넣고 시작하는 게 낫다.

필요한 장비가 있다면 쿠팡에서 찾아보기 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

온콜 라우팅과 Grafana 연계

route 트리로 팀별·심각도별 분기

Alertmanager의 route는 트리 구조로 동작한다. 최상위 route가 기본 receiver를 지정하고, 하위 routes에서 label 매칭 조건에 따라 다른 receiver로 분기한다. alerting rule에 labels.team: backend 같은 라벨을 붙여두면 팀별 Slack 채널로 분리가 된다:

route:
  receiver: 'slack-default'
  routes:
    - match:
        team: 'backend'
      receiver: 'slack-backend'
      routes:
        - match:
            severity: 'critical'
          receiver: 'pagerduty-backend'  # critical은 PagerDuty로 에스컬레이션

    - match:
        team: 'infra'
      receiver: 'slack-infra'

이렇게 하면 인프라 알람이 백엔드 채널에 섞이는 걸 방지할 수 있고, 각 팀이 자기 담당 알람만 받게 된다. 라벨 설계를 rule 작성 단계에서 일관되게 해놔야 라우팅이 깔끔해진다. team, severity, env(production/staging) 정도면 대부분의 라우팅 시나리오를 커버한다.

시간 기반 라우팅(업무 시간에는 Slack, 야간에는 PagerDuty)은 Alertmanager의 time_intervalsactive_time_intervals 기능으로 가능하긴 하다(v0.24 이후 추가). 이 부분은 아직 직접 구성해보지 않아서 자세히 쓰기 어렵다. 야간 온콜이 필요한 규모라면 PagerDuty나 Grafana OnCall 같은 전용 도구에 위임하는 게 현실적이라는 평가가 많다.

Grafana alerting과 Prometheus alerting

Grafana 8부터 도입된 Unified Alerting이 2026년 4월 기준으로는 꽤 성숙한 상태다. Grafana에서도 자체적으로 알람을 만들고 Slack으로 보낼 수 있다. 그러면 Prometheus alerting과 뭐가 다른가.

Prometheus alerting은 인프라 레벨 메트릭에 적합하다. Prometheus 서버가 24시간 돌면서 rule을 지속적으로 평가하기 때문에 안정적이다. 반면 Grafana alerting은 여러 데이터소스를 조합할 수 있다는 게 강점이다. Prometheus + MySQL + Elasticsearch 데이터를 하나의 알람 조건에 엮을 수 있다. 비즈니스 메트릭이나 복합 조건 알람은 Grafana 쪽이 유리하다.

실무에서는 둘을 병행하는 경우가 많다. CPU, 메모리, 디스크 같은 인프라 알람은 Prometheus + Alertmanager로, 서비스 레벨 지표(에러율, 전환율 등)는 Grafana에서 관리하는 구조다. 한 곳으로 통일하고 싶으면 Grafana Unified Alerting으로 전부 옮기는 것도 방법이지만, 이미 Prometheus alerting rules가 수십 개 쌓인 상태라면 굳이 마이그레이션할 이유는 없다.

Docker Compose로 전체 스택을 올리는 최소 구성:

# docker-compose.yml
services:
  prometheus:
    image: prom/prometheus:v2.53.0
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - ./rules:/etc/prometheus/rules
    ports:
      - '9090:9090'

  alertmanager:
    image: prom/alertmanager:v0.27.0
    volumes:
      - ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
    ports:
      - '9093:9093'

  grafana:
    image: grafana/grafana:11.4.0
    ports:
      - '3000:3000'
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=changeme

이미지 태그 버전은 작성 시점 기준이다. 배포 전에 Docker Hub에서 최신 stable 태그를 확인하는 게 좋다. docker compose up -d로 올리고, localhost:9090/alerts에서 rule 상태, localhost:9093에서 Alertmanager UI, localhost:3000에서 Grafana를 볼 수 있다.

Prometheus Grafana 알람 Slack 연동 설정의 기본 구성은 여기까지다. 당장 적용하려면 이렇게 하면 된다: 먼저 alertmanager.yml에 Slack webhook 하나만 연결해서 테스트 알람이 오는지 확인하고, CPU·메모리 rule 2개만 만들어서 1주일 돌려본 뒤, 노이즈를 보면서 임계값과 for 값을 조정한다. 한 번에 모든 메트릭에 알람을 거는 것보다 이렇게 점진적으로 늘리는 게 알람 피로를 피하는 방법이다. 다음에는 Alertmanager의 time_intervals를 활용한 시간대별 라우팅과 Grafana OnCall 플러그인 연동을 실험해볼 생각이다.

관련 글

Chiko
Chiko

Platform Engineer. Python, AI, Infra에 관심이 많습니다.