Grafana 입문 6편 — Alerting · Notification · SLO 깊이

2026-05-18Grafana 입문에서 운영까지

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편 — Alerting · Notification · SLO 깊이

이 글은 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 5% / 6h
Warning (Ticket) 24h 2h 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 5% / 6h
Ticket 24h 2h 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 을 확인할 수 있어요.

시리즈 다른 편 (앞뒤 글 모음)

이전 글:

다음 글:

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!