Prometheus Alert Rule 작성 방법 — expr, for, labels, annotations부터 테스트까지
Prometheus 알림 규칙을 제대로 작성하는 법을 정리합니다. 규칙 파일 구조와 alert/expr/for/labels/annotations 5요소, pending→firing 상태, 템플릿(annotation) 작성, 좋은 expr 패턴, InstanceDown·에러율·지연·디스크 예측 같은 실전 예제, recording rule, 그리고 promtool 검증·유닛 테스트와 운영 팁까지 다룹니다.
메트릭을 수집하는 목적의 절반은 "문제가 생겼을 때 알림을 받기" 위해서입니다. Prometheus 에서 그 알림의 조건 을 정의하는 것이 Alert Rule(알림 규칙) 입니다. 규칙을 잘 쓰면 진짜 장애만 깔끔하게 울리고, 못 쓰면 새벽마다 무의미한 알림에 시달립니다.
이 글에서 다루는 내용:
- 알림 규칙의 평가 흐름과 파일 구조
- 규칙 한 개의 5요소:
alert/expr/for/labels/annotations pending → firing상태와 flapping 방지- annotation 템플릿, 좋은
expr작성법 - InstanceDown·에러율·지연·디스크 예측 등 실전 예제
- recording rule,
promtool검증·유닛 테스트, 운영 팁
1. Alert Rule이란
Prometheus 는 설정한 주기(evaluation_interval)마다 각 규칙의 expr 을 평가합니다. 결과가 나오면(=조건 충족) 알림 후보가 되고, 지정한 for 시간만큼 조건이 유지되면 firing 상태가 되어 Alertmanager 로 전송됩니다. 알림의 그룹핑·중복제거·라우팅·발송(Slack·이메일 등)은 Alertmanager 의 몫입니다.
[메트릭] → Prometheus가 expr 평가(주기적)
→ 조건 충족 + for 경과 → firing
→ Alertmanager로 전송 → 그룹핑/라우팅 → Slack·Email·PagerDuty참고로 규칙에는 두 종류가 있습니다. Alerting rule(알림 조건)과 Recording rule(자주 쓰는 식을 미리 계산해 새 시계열로 저장). 이 글의 주제는 전자이며, 후자는 9장에서 함께 다룹니다.
2. 규칙 파일 구조
규칙은 별도 YAML 파일에 groups 로 묶어 작성하고, prometheus.yml 의 rule_files 로 불러옵니다.
# prometheus.yml
global:
evaluation_interval: 15s # 규칙을 얼마나 자주 평가할지
rule_files:
- "rules/*.yml"
alerting:
alertmanagers:
- static_configs:
- targets: ["localhost:9093"]# rules/app-alerts.yml
groups:
- name: app-availability # 그룹 이름(같은 그룹은 순차 평가)
interval: 30s # (선택) 이 그룹만의 평가 주기
rules:
- alert: InstanceDown
expr: up == 0
for: 5m
labels:
severity: critical
annotations:
summary: "인스턴스 다운: {{ $labels.instance }}"
description: "{{ $labels.job }} / {{ $labels.instance }} 가 5분 이상 응답하지 않습니다."같은 그룹 내 규칙은 위에서 아래로 순차 평가되므로, recording rule 의 결과를 같은 그룹의 다른 규칙이 참조할 수 있습니다.
3. Alert Rule 한 개의 구조 (5요소)
| 필드 | 역할 | 필수 |
|---|---|---|
alert | 알림 이름(=alertname 라벨) | Y |
expr | 알림 조건이 되는 PromQL. 결과가 하나라도 나오면 그 시계열마다 알림 후보 | Y |
for | 조건이 이 시간 동안 연속 유지돼야 firing | N |
labels | 알림에 덧붙는 라벨(라우팅·심각도). expr 결과 라벨과 합쳐짐 | N |
annotations | 사람이 읽는 설명(템플릿 가능). 라우팅에는 안 쓰임 | N |
핵심은 expr 이 반환한 시계열 하나하나가 개별 알림 인스턴스라는 점입니다. up == 0 이 인스턴스 3개에서 참이면, 알림 3건이 각각 instance 라벨을 달고 만들어집니다.
4. for와 알림 상태(state)
알림은 세 가지 상태를 오갑니다.
- inactive —
expr결과 없음(조건 미충족) - pending — 조건은 충족됐지만
for시간이 아직 안 지남 - firing —
for동안 조건이 연속 유지됨 → Alertmanager 로 발송
inactive ──(조건 충족)──▶ pending ──(for 경과)──▶ firing
▲ │ │
└────(조건 해제)───────────┴──────────────────────┘for 가 없으면 한 번만 조건을 만족해도 즉시 firing 되어, 순간 스파이크에도 알림이 울렸다 꺼졌다(flapping) 합니다. 대부분의 규칙에 for: 5m~for: 15m 정도를 두는 것이 노이즈를 크게 줄입니다. pending 중에 조건이 한 번이라도 풀리면 타이머는 0으로 리셋됩니다.
활성 알림은 ALERTS{alertname=..., alertstate="pending|firing", ...} 메트릭으로도 조회할 수 있어, 알림 자체를 모니터링할 수 있습니다.
5. labels — 라우팅과 심각도
labels 는 알림에 의미를 부여하고, Alertmanager 가 이 라벨로 라우팅·그룹핑·억제(inhibition) 를 결정합니다. 가장 흔한 것이 severity 입니다.
labels:
severity: critical # critical / warning / info 등 일관된 체계로
team: platform # 어느 팀으로 보낼지expr 결과에 이미 붙어 있던 라벨(instance, job 등)은 그대로 알림에 유지되고, 여기에 위 라벨이 더해집니다. 그래서 Alertmanager 라우트에서 severity="critical" 은 PagerDuty 로, warning 은 Slack 으로 보내는 식의 분기가 가능합니다.
팁:
severity값은 조직 전체에서 동일한 어휘로 통일하세요. 누구는crit, 누구는critical을 쓰면 라우팅 규칙이 새기 시작합니다.
6. annotations와 템플릿
annotations 는 알림 메시지 본문입니다. Go 템플릿으로 라벨·값을 끼워 넣어 무엇이, 얼마나 문제인지 구체적으로 적습니다.
annotations:
summary: "{{ $labels.job }} 5xx 에러율 높음"
description: "에러율 {{ $value | humanizePercentage }} (임계 5%)가 10분 이상 지속."
runbook_url: "https://runbooks.example.com/HighErrorRate"
dashboard: "https://grafana.example.com/d/abc/app"자주 쓰는 템플릿 요소:
{{ $labels.<name> }}— 해당 알림 시계열의 라벨 값 ({{ $labels.instance }}){{ $value }}—expr의 현재 값{{ $value | humanize }}— 1234567 →1.235M식으로 사람이 읽기 좋게{{ $value | humanizePercentage }}— 0.0523 →5.23%{{ $value | humanizeDuration }}— 초 단위를2h 5m식으로{{ printf "%.2f" $value }}— 소수 자리 고정
좋은 annotation 은 현재 값·임계값·영향 범위·런북 링크를 담아, 알림만 보고도 바로 행동할 수 있게 합니다.
7. 좋은 expr 작성법
규칙의 품질은 expr 에서 갈립니다.
- counter 는
rate()로. 누적값을 그대로 비교하지 말고 초당 증가율로. - 의미 있는 라벨만 남겨 집계.
sum by (job, instance) (...)처럼, 알림을 구분할 라벨은 유지하고 나머지는 합칩니다. - 비율은 0 나눗셈에 주의. 분모가 0이면 결과가 사라져(no data) 알림이 안 뜨는데, 보통은 이게 안전한 동작입니다.
- 누락 감지는
absent(). 메트릭이 아예 사라지는 상황(타깃 소멸)은 임계 비교로는 못 잡습니다. - 원인이 아니라 증상에 알림. "CPU 90%" 단독보다 "에러율 상승·지연 증가"처럼 사용자 영향에 거는 편이 노이즈가 적습니다.
# 에러율(5xx 비율) — 분모 0이면 자연히 no data
sum by (job) (rate(http_requests_total{code=~"5.."}[5m]))
/ sum by (job) (rate(http_requests_total[5m])) > 0.05
# 특정 job 의 메트릭이 통째로 사라졌는지
absent(up{job="payment-api"})8. 자주 쓰는 패턴 예제
groups:
- name: app-symptoms
rules:
# 인스턴스 다운
- alert: InstanceDown
expr: up == 0
for: 5m
labels: { severity: critical }
annotations:
summary: "인스턴스 다운: {{ $labels.instance }}"
# 5xx 에러율 5% 초과
- alert: HighErrorRate
expr: |
sum by (job) (rate(http_requests_total{code=~"5.."}[5m]))
/ sum by (job) (rate(http_requests_total[5m])) > 0.05
for: 10m
labels: { severity: warning }
annotations:
summary: "{{ $labels.job }} 에러율 높음"
description: "에러율 {{ $value | humanizePercentage }}, 10분 지속."
# p95 지연 1초 초과 (histogram)
- alert: HighRequestLatency
expr: |
histogram_quantile(0.95,
sum by (job, le) (rate(http_request_duration_seconds_bucket[5m]))
) > 1
for: 10m
labels: { severity: warning }
annotations:
summary: "{{ $labels.job }} p95 지연 높음"
description: "p95 = {{ $value | humanizeDuration }} (임계 1s)."
# 디스크가 4시간 내 가득 찰 추세 (예측)
- alert: DiskWillFillSoon
expr: |
predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}[1h], 4*3600) < 0
for: 30m
labels: { severity: warning }
annotations:
summary: "{{ $labels.instance }} 디스크 4시간 내 소진 예상"
# 메모리 포화 90% 초과
- alert: HighMemoryUsage
expr: |
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) > 0.9
for: 15m
labels: { severity: warning }
annotations:
summary: "{{ $labels.instance }} 메모리 사용률 90% 초과"predict_linear(...[1h], 4*3600) 은 최근 1시간 추세로 4시간 뒤 값을 외삽합니다. < 0 이면 "지금 추세면 4시간 내 0(=가득 참)" 이라는 뜻이라, 임계치에 닿기 전에 미리 알림을 줍니다.
9. Recording Rule 함께 쓰기
복잡하거나 자주 쓰는 식은 recording rule 로 미리 계산해 새 시계열로 저장하면, 알림 규칙과 대시보드가 단순해지고 평가 비용도 줄어듭니다.
groups:
- name: app-recordings
rules:
- record: job:http_requests:rate5m
expr: sum by (job) (rate(http_requests_total[5m]))
- record: job:http_errors:ratio5m
expr: |
sum by (job) (rate(http_requests_total{code=~"5.."}[5m]))
/ sum by (job) (rate(http_requests_total[5m]))이름은 관례적으로 level:metric:operations 형식(job:http_errors:ratio5m)을 씁니다. 그러면 알림은 이렇게 짧아집니다.
- alert: HighErrorRate
expr: job:http_errors:ratio5m > 0.05
for: 10m
labels: { severity: warning }10. 규칙 검증·테스트 (promtool)
규칙을 프로덕션에 넣기 전에 promtool 로 문법과 동작을 검증합니다.
# 문법·구조 검증
promtool check rules rules/*.yml더 나아가 유닛 테스트로 "이런 입력이면 이 알림이 떠야 한다"를 못 박을 수 있습니다.
# tests/instance-down.test.yml
rule_files:
- ../rules/app-alerts.yml
evaluation_interval: 1m
tests:
- interval: 1m
input_series:
- series: 'up{job="api", instance="a:8080"}'
values: "1 1 0 0 0 0 0 0" # 3분차부터 다운
alert_rule_test:
- eval_time: 7m
alertname: InstanceDown
exp_alerts:
- exp_labels:
severity: critical
job: api
instance: a:8080
exp_annotations:
summary: "인스턴스 다운: a:8080"promtool test rules tests/instance-down.test.ymlCI 에 promtool check rules 와 promtool test rules 를 걸어두면, 잘못된 규칙이 머지되는 것을 사전에 막을 수 있습니다.
11. 운영 팁 / 안티패턴
- 거의 모든 규칙에
for를 두세요. 순간 스파이크 알림이 사라집니다. - 증상에 알림. 사용자 영향(에러율·지연·가용성)에 걸고, 원인 지표(CPU·메모리)는 보조로.
- annotation 을 행동 가능하게. 현재 값·임계값·런북 링크 필수.
severity체계를 통일. 라우팅이 여기에 의존합니다.- 알림 피로(alert fatigue) 경계. 끄지 않는 알림은 결국 모두가 무시합니다. "이 알림이 뜨면 사람이 무엇을 할지" 가 없으면 알림이 아니라 대시보드 항목입니다.
- 규칙을 바꾼 뒤에는 Prometheus 를 리로드합니다.
# --web.enable-lifecycle 로 떠 있을 때
curl -X POST http://localhost:9090/-/reloadAlertmanager 연결은 2장의 alerting: 블록이면 충분하고, 실제 발송 채널·라우팅·중복제거는 Alertmanager 의 alertmanager.yml 에서 따로 구성합니다(별도 주제).
마무리
Alert Rule 은 결국 "의미 있는 expr + 적절한 for + 행동 가능한 annotation" 의 조합입니다. expr 로 증상을 정확히 포착하고, for 로 노이즈를 걸러내고, labels 로 올바른 사람에게 보내고, annotations 로 바로 조치할 수 있게 만드는 것 — 이 네 가지가 좋은 알림의 전부입니다. 마지막으로 promtool 유닛 테스트까지 붙이면, 규칙이 "기대대로 운다"는 것을 코드처럼 보장할 수 있습니다.
다음 단계로는 여기서 만든 알림을 Alertmanager 라우트(심각도별 채널·근무시간 분기·inhibition)와 연결하고, ALERTS 메트릭으로 "알림 자체"를 대시보드화해 알림 위생을 관리해 보길 권합니다.