Grafana 입문 6편. Alerting · Notification · SLO 깊이 — Alert Rule (Query · Condition · For · Annotation · Label), Contact Point 100+ (Slack · PagerDuty · Email · Webhook · Teams · OpsGenie · Discord · Telegram · ...), Notification Policy 의 routing tree, Silence · Inhibition · Mute Timings, Grafana Alerting vs Alertmanager 통합, SLO 기반 alert (Error Budget · multi-window · burn rate), Symptom-based vs Cause-based, Alert fatigue 회피. 운영 의 새벽 3시 자리.
이 글은 Grafana 입문에서 운영까지 시리즈 6편이에요. 1~5편이 데이터 + 시각화 였다면 6편은 알림 + 행동 트리거 를 다룹니다. 새벽 3시에 폰을 깨우는 그 영역이에요.
이번 글의 범위
Alert(알림)는 운영에서 가장 무거운 영역이에요. 잘못 박힌 alert 하나가 새벽 호출을 반복시키고, 팀을 무력하게 만들고, 진짜 사고를 놓치게 합니다. 이 글에서는 Alert 설계 원칙 과 Alert fatigue(알림 피로) 회피 를 정리해요.
| 자리 | 자산 |
|---|---|
| Rule | Alert Rule 의 구조 · Multi-dimensional |
| Routing | Contact Point · Notification Policy |
| 제어 | Silence · Inhibition · Mute Timings |
| SLO | Error Budget · multi-window burn rate |
| 의식 | Symptom-based · Alert fatigue 회피 |
Grafana Alerting — 구조
Alert Rule 의 정의
Alert Rule:
├─ Query (어떤 데이터)
├─ Condition (언제 trigger — > · < · = · ...)
├─ Evaluation Interval (얼마나 자주 평가)
├─ For (얼마나 지속 시 fire)
├─ Labels (메타데이터 — severity · team · service)
├─ Annotations (description · summary · runbook_url)
└─ Notification (어디로 — Contact Point + Policy)
Grafana-managed vs Datasource-managed
1. Grafana-managed Alert
- Grafana 자체 의 alert engine
- Datasource 무관 (Prometheus 외 Loki · Tempo · CloudWatch · ...)
- 단일 UI 의 통합 관리
- 신규 환경 권장
2. Datasource-managed Alert
- Prometheus 의 Alertmanager (전통 방식)
- Loki Ruler · Mimir Ruler
- 기존 stack 의 통합
→ 새 환경 = Grafana-managed
→ Prometheus 의 기존 alert 있음 = Datasource-managed 유지 + 점진 마이그
첫 Alert Rule 예
# Grafana UI 의 Alert Rule (YAML 표현)
name: HighErrorRate
folder: API Alerts
condition: B
data:
- refId: A
datasourceUid: prometheus-uid
model:
expr: 'sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))'
- refId: B
datasourceUid: __expr__
model:
type: threshold
conditions:
- evaluator: {type: gt, params: [0.05]} # 5% 초과
operator: {type: and}
query: {params: [A]}
# 평가
evaluation:
interval: 1m # 1분 마다 평가
for: 5m # 5분 지속 시 fire
# 메타데이터
labels:
severity: critical
team: backend
service: api
annotations:
summary: "API error rate > 5%"
description: "Current error rate: {{ $value | humanizePercentage }}"
runbook_url: "https://wiki.company.com/runbooks/high-error-rate"
Multi-dimensional Alert
Multi-dimensional Alert는 라벨별로 alert를 자동 분리해주는 기능이에요.
한 Rule = 여러 series 의 자동 alert:
sum by (service) (rate(http_requests_total[5m])) > 1000
→ service 별로 자동 분리:
- HighRate{service="api"} → 1 alert
- HighRate{service="frontend"} → 1 alert
- HighRate{service="worker"} → 1 alert
여기 시험 함정 하나 — multi-dimensional alert는 Grafana의 기본 동작이에요. Prometheus와 같은 방식으로, 한 번 정의한 rule이 자동으로 수십 개 alert를 만들어냅니다.
Contact Point — 100+ 종
Contact Point는 alert를 받을 외부 채널(Slack·PagerDuty 등) 연결을 말해요.
표준 Contact Point
- Slack
- PagerDuty
- Email
- Webhook (custom)
- Microsoft Teams
- Discord
- Telegram
- OpsGenie
- VictorOps · Splunk On-Call
- Pushover · Pushbullet
- Sensu
- AlertManager (downstream)
- Kafka REST Proxy
- LINE
- WeChat
- Cisco Webex Teams
- ... 100+ 종
Slack Contact Point
name: slack-backend
type: slack
settings:
url: https://hooks.slack.com/services/T00/B00/XXX
recipient: '#alerts-backend'
title: '{{ template "slack.title" . }}'
text: '{{ template "slack.text" . }}'
mentionGroups: '@backend-oncall'
mentionChannel: 'channel' # @channel 또는 @here
PagerDuty Contact Point
name: pagerduty-critical
type: pagerduty
settings:
integrationKey: 'YOUR_PD_INTEGRATION_KEY'
severity: 'critical'
class: 'backend'
component: '{{ .CommonLabels.service }}'
group: '{{ .CommonLabels.team }}'
Webhook (Custom)
name: custom-webhook
type: webhook
settings:
url: 'https://internal-bot.company.com/alerts'
httpMethod: 'POST'
authentication: 'bearer'
bearerToken: '${INTERNAL_BOT_TOKEN}'
message: |
{
"alert": "{{ .CommonAnnotations.summary }}",
"severity": "{{ .CommonLabels.severity }}",
"service": "{{ .CommonLabels.service }}"
}
Template — Notification 의 format
Default Slack notification:
🚨 [Alert] HighErrorRate
━━━━━━━━━━━━━━━━━━━━━
service: api
severity: critical
Summary: API error rate > 5%
Current: 7.2%
Runbook: https://wiki.company.com/...
Fired at: 2026-05-18 14:32:15 KST
Custom template:
{{ define "slack.title" }}
{{- if eq .Status "firing" -}}
🚨 [{{ .CommonLabels.severity | upper }}] {{ .GroupLabels.alertname }}
{{- else -}}
✅ [Resolved] {{ .GroupLabels.alertname }}
{{- end -}}
{{ end }}
{{ define "slack.text" }}
{{ range .Alerts }}
*Service:* `{{ .Labels.service }}`
*Summary:* {{ .Annotations.summary }}
*Description:* {{ .Annotations.description }}
{{ if .Annotations.runbook_url }}*Runbook:* {{ .Annotations.runbook_url }}{{ end }}
{{ end }}
{{ end }}
Notification Policy — Routing Tree
Notification Policy는 alert가 어떤 Contact Point로 갈지 정하는 라우팅 규칙이에요.
Policy Tree 의 구조
Root policy (default)
Receiver: default-slack
Group by: alertname, cluster, service
Group wait: 30s
Group interval: 5m
Repeat interval: 4h
│
├─ severity = critical
│ Receiver: pagerduty-oncall
│ Continue: true ← 다음 child policy 도 평가
│ │
│ └─ team = backend
│ Receiver: slack-backend-critical
│
├─ severity = warning, team = backend
│ Receiver: slack-backend
│
├─ team = frontend
│ Receiver: slack-frontend
│
└─ namespace = test
Receiver: drop (또는 silence) ← test 환경 alert 무시
Matcher 의 syntax
간단:
severity = "critical"
team = "backend"
정확 매치 X:
severity != "info"
Regex:
team =~ "backend|infra"
severity !~ "info|debug"
Group 의 의식
Group by: alertname, cluster, service
Alert 100개 발생 (같은 cluster · service 의 다른 instance):
→ group_wait 30s 대기 (추가 alert 모음)
→ 1 notification ("100 instance affected")
Alert 들 의 다른 cluster:
→ 다른 group → 별도 notification
이렇게 묶어 보내면 notification 폭주를 피할 수 있어요.
Repeat Interval
Alert 가 4시간 동안 계속 fire:
- 첫 fire = 즉시 notification
- 4시간 지나도 안 해결 = repeat notification (reminder)
- 8시간 = repeat notification (다시)
- ...
→ 잊혀지지 않게 + 새로운 alert 와 구분
Silence · Inhibition · Mute Timings
Silence — 일시 mute
Silence는 특정 alert를 일시적으로 꺼두는 기능이에요.
사용 case:
- 배포 중 (의도된 일시 변화)
- 점검 시간
- Known issue (수정 중)
설정:
- Matcher (예: cluster = "prod-eu", deploy = "true")
- 기간 (1 시간 · 4 시간 · 1 일 · 명시)
- 만료 후 자동 unmute
Inhibition — 심각한 alert 의 mask
Inhibition은 더 심각한 alert가 떴을 때 관련된 덜 심각한 alert를 가려주는 규칙이에요.
규칙: critical alert 가 활성 시 → 같은 service 의 warning alert mask
예:
- "Cluster Down" (critical) fire
→ 그 cluster 의 모든 "Instance Slow" (warning) mask
→ notification 폭주 회피
설정:
inhibit_rules:
- source_match:
severity: critical
alertname: ClusterDown
target_match:
severity: warning
equal: ['cluster']
Mute Timings — 시간대 기반
Mute Timings는 특정 시간대(주말·점심 등)에만 alert를 꺼두는 설정이에요.
규칙: 매주 토/일 의 non-critical alert 모두 mute
설정:
Mute timing: weekend
- Time: Saturday, Sunday
- Hours: 00:00 - 23:59
- Timezone: Asia/Seoul
Notification Policy:
- severity != critical → mute_time_intervals: [weekend]
용도:
- 주말 의 non-urgent alert mute
- 점심 시간 의 alert mute
- 정기 점검 시간 의 mute
- 휴일 의 mute
SLO — Service Level Objective
SLI · SLO · SLA
SLI는 실측 지표, SLO는 우리가 정한 목표, SLA는 고객과 맺은 계약이에요.
SLI (Service Level Indicator):
- 실측 metric
- 예: success_rate = (2xx + 3xx) / total
- 예: latency_p99 = histogram_quantile(0.99, ...)
SLO (Service Level Objective):
- 우리 의 목표
- 예: success_rate >= 99.9% (월간)
- 예: latency_p99 <= 500ms (95% 시간 동안)
SLA (Service Level Agreement):
- 고객 과 의 계약 (보통 SLO 보다 낮게)
- 예: 99.5% (위반 시 환불)
Error Budget 의 의미
Error Budget(오류 예산)은 SLO를 채우고 남는 허용 실패치를 가리켜요.
SLO: 99.9% success rate
Error Budget = 100% - 99.9% = 0.1%
월간:
- 30일 × 24h × 60min = 43,200 분
- 0.1% = 43.2 분
→ 한 달에 43분 의 downtime "허용"
이 시간 = "innovation 의 자원":
- 새 기능 릴리즈
- 실험
- 위험 한 변경
→ error budget 소진 = 의도된 실험 의 결과
단순 Threshold vs SLO 기반 Alert
단순 Threshold:
error_rate > 5% → alert
문제:
- 5% 가 "왜" 의 답 X (그냥 magic number)
- 잠시 spike 도 alert (flap)
- 비즈니스 의미 X
SLO 기반:
Error budget 의 burn rate:
- 1시간 의 burn rate ≥ 14.4× → "2일 후 모든 예산 소진" → critical
- 6시간 의 burn rate ≥ 6× → "5일 후 예산 소진" → warning
→ "왜 1시간 의 burn" 의 답 = "이 속도면 비즈니스 목표 위반"
→ 비즈니스 의미 명확
Multi-window Burn Rate
Burn rate(소진 속도)는 error budget이 얼마나 빨리 줄어드는지 보여주는 비율이고, Multi-window Burn Rate는 짧은 창과 긴 창을 같이 보는 방식이에요.
# Critical: 빠른 burn (긴급 대응)
- alert: HighErrorBudgetBurnFast
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[1h])) /
sum(rate(http_requests_total[1h]))
) > (14.4 * 0.001)
and
(
sum(rate(http_requests_total{status=~"5.."}[5m])) /
sum(rate(http_requests_total[5m]))
) > (14.4 * 0.001)
for: 2m
labels:
severity: critical
# Warning: 느린 burn (시간 있는 대응)
- alert: HighErrorBudgetBurnSlow
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[6h])) /
sum(rate(http_requests_total[6h]))
) > (6 * 0.001)
and
(
sum(rate(http_requests_total{status=~"5.."}[30m])) /
sum(rate(http_requests_total[30m]))
) > (6 * 0.001)
for: 15m
labels:
severity: warning
왜 Multi-window? — 짧은 window(5m)만 보면 noise가 많고, 긴 window(1h)만 보면 감지가 늦어요. 두 window를 AND 로 묶으면 noise를 줄이면서도 빨리 감지할 수 있어요.
Burn Rate 의 수치 의식
SLO 99.9%, Error budget 0.1%, 30일
Burn rate 1× = 30일 마다 모든 예산 소진 (정상)
Burn rate 2× = 15일 마다 소진
Burn rate 14.4× = 2일 마다 소진 → 매우 빠른 burn (critical)
Burn rate 6× = 5일 마다 소진 → 빠른 burn (warning)
Google SRE Book 의 권장 표:
| Severity | Long window | Short window | Burn rate | Budget |
|---|---|---|---|---|
| Critical (Page) | 1h | 5m | 14.4× | 2% / 1h |
| Critical (Page) | 6h | 30m | 6× | 5% / 6h |
| Warning (Ticket) | 24h | 2h | 1× | 10% / 1d |
| Warning (Ticket) | 72h | 6h | 0.5× | 30% / 3d |
Symptom-based vs Cause-based
Symptom-based는 사용자 영향(증상) 중심, Cause-based는 자원 지표(원인) 중심의 alert 분류예요.
Cause-based Alert (전통)
예:
- CPU > 80%
- Memory > 80%
- Disk > 80%
- Queue size > 1000
문제:
- 사용자 가 영향 받았는지 모름
- false positive (CPU 80% 인데 응답 정상)
- alert 폭주 (모든 resource 의 alert)
Symptom-based Alert (권장)
예:
- User-facing error rate > X%
- User-facing p99 latency > X ms
- SLO error budget burn rate
장점:
- 사용자 영향 의 직접 측정
- false positive 적음
- "왜" 의 답 = SLO 의 비즈니스 의미
두 가지 결합
Page 대상 (Symptom):
- User error rate / latency
- SLO burn rate
- Service down (사용자 영향)
Ticket 대상 (Cause):
- Disk usage 가 90% (곧 영향 예측)
- Certificate 가 7일 후 만료
- 자동 scale 한도 도달
→ Page = 즉시 대응 (사용자 영향)
→ Ticket = 다음 영업일 대응 (predict)
여기서 정말 중요한 시험 함정 하나 — 모든 alert를 page로 만들지 마세요. 80%는 Ticket으로 돌립니다. Page(즉시 호출)는 새벽을 깨우는 자리니까 정말 드물게 쓰는 게 좋아요.
Alert Fatigue — 가장 큰 함정
Fatigue 의 신호
- 매일 100+ alert
- "또 그 alert" 느낌
- Slack 의 alert channel 무시
- 새 alert 의 인지 느림
- 진짜 사고 의 늦은 감지
회피 패턴
1. Symptom-based alert 위주
2. Multi-window burn rate (noise 회피)
3. Grouping (한 사고 = 1 notification)
4. Inhibition (cluster down → instance alert mask)
5. Mute Timings (주말 의 non-critical mute)
6. Severity 분리 (Page vs Ticket vs Info)
7. Runbook 의식 (모든 page alert 의 runbook URL)
8. 주기적 audit (월 1회 alert review)
→ 주당 < 5 page alert 목표
Alert Review 의 매월 routine
매월 1일:
□ 지난 30일 의 alert 통계
□ false positive 분석 → 룰 조정
□ 가장 자주 발생 alert → 근본 fix 또는 mute
□ 새벽 깨운 alert → 정말 필요했나?
□ 놓친 사고 → alert 추가
함정 정리
사고 1: 모든 alert = Page
원인 — 모든 alert의 severity가 critical로 잡혀 PagerDuty가 새벽에 폭주합니다.
해결 — Symptom-based + SLO 기반만 Page로 두고, 나머지는 Ticket이나 Info로 내립니다.
사고 2: For 안 설정 → flap
원인 — for: 0 으로 두면 잠시 튄 spike에도 alert가 떴다가 바로 resolve되며 flap합니다.
해결 — for: 5m ~ 15m 정도로 잡아서, 지속 충족된 경우에만 fire하게 합니다.
사고 3: Group 잘못 → notification 폭주
원인 — group_by: [alertname] 만 두면 100개 instance의 같은 alert가 100건의 notification으로 갑니다.
해결 — group_by: [alertname, cluster, service] + group_interval: 5m 으로 묶어 줍니다.
사고 4: Repeat Interval 너무 짧음
원인 — repeat_interval: 5m 으로 두면, 1시간 동안 fire 중인 alert가 12번이나 울립니다.
해결 — repeat_interval: 4h 가 표준이에요.
사고 5: Inhibition 없음 → cluster 폭주
원인 — Cluster가 내려가면 그 안의 모든 instance·service alert가 한꺼번에 터집니다.
해결 — Inhibition rule을 박아 ClusterDown이 뜨면 자식 alert를 mask합니다.
사고 6: Silence 의 expire 안 됨
원인 — 점검 중에 silence 박아 두고 만료 시간을 안 주면 영구 mute가 되어 실 사고를 놓칩니다.
해결 — Silence는 항상 expire를 설정합니다. UI가 강제하고, 만료 알림도 받게 둡니다.
사고 7: Runbook URL 없음
원인 — oncall이 alert를 받고 "이게 뭐야?" 부터 검색하면 5분이 그냥 날아갑니다.
해결 — 모든 alert에 runbook_url annotation을 답니다. 받자마자 바로 행동하게요.
사고 8: Test 환경 alert 의 production 채널
원인 — staging의 alert가 production Slack에 도착해서 noise가 됩니다.
해결 — namespace/environment 라벨 기반으로 라우팅해서, test는 drop·staging은 별도 채널로 보냅니다.
사고 9: Symptom 없이 Cause 만
원인 — CPU·메모리·디스크 alert만 깔아두면 사용자 영향이 측정되지 않습니다.
해결 — Symptom-based(user error rate · p99 latency) alert를 추가합니다.
사고 10: Alert 의 owner 모호
원인 — Alert에 team 라벨이 없으면 누가 봐야 할지 모릅니다.
해결 — 모든 alert에 team label을 박고, team별로 라우팅합니다.
운영 권장 패턴
Pattern 1: SLO 의 4 표준 Alert
# Service: api · SLO 99.9% success rate
groups:
- name: api_slo_alerts
rules:
# 1. Fast burn — Page
- alert: ApiSloBurnFast
expr: |
(
sum(rate(http_requests_total{service="api", status=~"5.."}[1h])) /
sum(rate(http_requests_total{service="api"}[1h]))
) > (14.4 * 0.001)
and
(
sum(rate(http_requests_total{service="api", status=~"5.."}[5m])) /
sum(rate(http_requests_total{service="api"}[5m]))
) > (14.4 * 0.001)
for: 2m
labels:
severity: critical
team: backend
service: api
annotations:
summary: "API error budget burning 14.4x faster"
description: "At this rate, monthly budget exhausted in <2 days"
runbook_url: "https://wiki.company.com/runbooks/api-slo-burn"
# 2. Slow burn — Ticket
- alert: ApiSloBurnSlow
expr: |
(
sum(rate(http_requests_total{service="api", status=~"5.."}[6h])) /
sum(rate(http_requests_total{service="api"}[6h]))
) > (6 * 0.001)
and
(
sum(rate(http_requests_total{service="api", status=~"5.."}[30m])) /
sum(rate(http_requests_total{service="api"}[30m]))
) > (6 * 0.001)
for: 15m
labels:
severity: warning
team: backend
service: api
# 3. p99 latency SLO
- alert: ApiLatencySloBurn
expr: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket{service="api"}[5m])) by (le)
) > 0.5
for: 5m
labels:
severity: warning
team: backend
# 4. Service 완전 down
- alert: ApiDown
expr: up{service="api"} == 0
for: 2m
labels:
severity: critical
team: backend
annotations:
summary: "API service down"
runbook_url: "https://wiki.company.com/runbooks/api-down"
Pattern 2: Notification Policy 의 표준 tree
# Root
receiver: default-slack
group_by: [alertname, cluster, service]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
mute_time_intervals: []
routes:
# 1. test/dev env 의 alert drop
- match:
environment: test
receiver: drop
- match:
environment: dev
receiver: drop
# 2. critical → PagerDuty + Slack
- match:
severity: critical
receiver: pagerduty-oncall
continue: true
routes:
- match:
team: backend
receiver: slack-backend-critical
- match:
team: frontend
receiver: slack-frontend-critical
- match:
team: data
receiver: slack-data-critical
# 3. warning → Slack 만
- match:
severity: warning
receiver: slack-warnings
routes:
- match:
team: backend
receiver: slack-backend
- match:
team: frontend
receiver: slack-frontend
- match:
team: data
receiver: slack-data
# 4. info → 별도 채널
- match:
severity: info
receiver: slack-info
repeat_interval: 24h
mute_time_intervals: [weekend]
Pattern 3: Inhibition 의 표준
inhibit_rules:
# Cluster down → instance 의 모든 alert mask
- source_match:
alertname: ClusterDown
target_match:
cluster: ".*" # 같은 cluster 의 모든
equal: [cluster]
# Critical → 같은 service 의 warning mask
- source_match:
severity: critical
target_match:
severity: warning
equal: [service, cluster]
# Service down → 그 service 의 모든 alert mask
- source_match:
alertname: ApiDown
target_match_re:
alertname: "Api.*"
equal: [service]
Pattern 4: Mute Timings 의 표준
mute_time_intervals:
# 주말
- name: weekend
time_intervals:
- weekdays: ['saturday', 'sunday']
location: 'Asia/Seoul'
# 점심 시간
- name: lunch
time_intervals:
- times:
- start_time: '12:00'
end_time: '13:00'
location: 'Asia/Seoul'
# 정기 점검 매주 일요일 새벽
- name: maintenance
time_intervals:
- weekdays: ['sunday']
times:
- start_time: '02:00'
end_time: '04:00'
location: 'Asia/Seoul'
Pattern 5: Alert template 의 표준
{{ define "slack.title" -}}
{{- if eq .Status "firing" -}}
🚨 [{{ .CommonLabels.severity | upper }}] {{ .GroupLabels.alertname }}
on {{ .CommonLabels.service }}
{{- else -}}
✅ [RESOLVED] {{ .GroupLabels.alertname }}
{{- end -}}
{{- end }}
{{ define "slack.text" -}}
*Alerts:* {{ len .Alerts }}
*Service:* `{{ .CommonLabels.service }}`
*Cluster:* `{{ .CommonLabels.cluster }}`
*Severity:* `{{ .CommonLabels.severity }}`
{{ range .Alerts -}}
*Summary:* {{ .Annotations.summary }}
*Description:* {{ .Annotations.description }}
{{ if .Annotations.runbook_url -}}
📖 *Runbook:* {{ .Annotations.runbook_url }}
{{- end }}
🔗 *Dashboard:* {{ .Annotations.dashboard_url }}
---
{{ end -}}
{{- end }}
Pattern 6: 주간 Alert Review
# 매주 월요일 09:00 KST — 자동 alert audit
import requests
from datetime import datetime, timedelta
def weekly_alert_audit():
# Grafana API 의 alert history
response = requests.get(
"http://grafana:3000/api/alertmanager/grafana/api/v2/alerts/groups",
headers={"Authorization": f"Bearer {GRAFANA_TOKEN}"}
)
alerts = response.json()
one_week_ago = datetime.now() - timedelta(days=7)
# 통계 분석
stats = {
'total_fired': 0,
'page_alerts': 0,
'auto_resolved': 0,
'flapped': 0,
'longest_duration': 0,
'most_frequent_alert': None,
}
# ...
# Slack 보고
post_to_slack("#weekly-ops", f"""
Weekly Alert Audit ({one_week_ago.date()} ~ {datetime.now().date()})
Total fired: {stats['total_fired']}
Page alerts: {stats['page_alerts']}
Auto-resolved (<5m): {stats['auto_resolved']}
Flapped (>10x): {stats['flapped']}
Most frequent: {stats['most_frequent_alert']}
→ Most frequent alert review 권장
""")
시험 직전 한 번 더 — Alerting · SLO 압축 노트
Alert Rule 의 구조
- Query · Condition · For · Labels · Annotations · Notification
- Multi-dimensional (sum by ... → 자동 series 별 alert)
- Grafana-managed vs Datasource-managed
Contact Point (100+)
- Slack · PagerDuty · Email · Webhook · Teams · OpsGenie · Discord · Telegram · LINE · ...
- Template 으로 format 커스터마이즈
Notification Policy
- Tree 구조 (Root + Child Routes)
- Matcher (severity · team · environment)
- continue: true (다음 child 도 평가)
- Group by · Group wait · Group interval · Repeat interval
Silence · Inhibition · Mute Timings
- Silence — 임시 mute (배포 · 점검), 항상 expire
- Inhibition — 심각한 alert 가 덜 심각한 mask
- Mute Timings — 시간대 기반 (주말 · 점심)
SLO / Error Budget
- SLI (실측) · SLO (목표) · SLA (계약)
- Error Budget = 100% - SLO
- 99.9% / 월 = 43분 downtime 허용
- Burn rate × — 예산 소진 속도
Multi-window Burn Rate (SRE Book)
| Severity | Long | Short | Rate | Budget |
|---|---|---|---|---|
| Page | 1h | 5m | 14.4× | 2% / 1h |
| Page | 6h | 30m | 6× | 5% / 6h |
| Ticket | 24h | 2h | 1× | 10% / 1d |
| Ticket | 72h | 6h | 0.5× | 30% / 3d |
Symptom-based vs Cause-based
- Cause = CPU · 메모리 · 디스크 (resource)
- Symptom = User error rate · latency (영향)
- Page = Symptom 만
- Cause = Ticket (예측)
Alert Fatigue 회피
- Symptom-based 위주
- Multi-window (noise 회피)
- Grouping (한 사고 = 1 notification)
- Inhibition
- Mute Timings
- Severity 분리 (Page · Ticket · Info)
- Runbook URL 의무
- 월 1회 audit
사고
- 모든 alert = Page (새벽 폭주)
- For 안 설정 (flap)
- Group 잘못 (notification 폭주)
- Repeat Interval 너무 짧음
- Inhibition 없음 (cluster 사고 폭주)
- Silence 의 expire 없음 (영구 mute)
- Runbook URL 없음
- test 환경 alert 의 production 채널
- Symptom 없이 Cause 만
- Alert 의 owner 모호
패턴
- SLO 4 표준 Alert (Fast/Slow burn · latency · service down)
- Notification Policy 표준 tree (env · severity · team)
- Inhibition 표준 (Cluster · Critical → Warning · Service down)
- Mute Timings 표준 (weekend · lunch · maintenance)
- Alert template (title · text + runbook URL · dashboard URL)
- 주간 Alert Audit (false positive · flap · 가장 자주)
공식 문서: Grafana Alerting · Google SRE Book 의 SLO 가이드 에서 더 깊은 spec 을 확인할 수 있어요.
시리즈 다른 편 (앞뒤 글 모음)
이전 글:
- 1편 — Observability 3 pillar · LGTM stack 종합
- 2편 — Prometheus + PromQL 깊이 (Pull · Exporter · Alertmanager)
- 3편 — Loki + LogQL 깊이 (Label Index · 비용 효율)
- 4편 — Tempo + TraceQL · 분산 Trace 깊이
- 5편 — Dashboard · Panel · Variable 깊이
다음 글: