ストーリー
アラート疲れ(Alert Fatigue)
アラート疲れとは
本来対応すべきアラートが大量の不要アラートに埋もれ、重要なアラートが見逃される現象です。
| 指標 | 健全な状態 | アラート疲れ状態 |
|---|---|---|
| 1日あたりアラート数 | 0〜5件 | 50件以上 |
| アクショナブル率 | 80%以上 | 20%以下 |
| アラートの無視率 | 5%以下 | 50%以上 |
| 平均対応時間 | 5分以内 | 30分以上(埋もれる) |
| オンコールエンジニアの満足度 | 高い | 非常に低い |
アラート疲れの悪循環
graph TD
A["大量のアラートが通知される"] --> B["ほとんどが対応不要(ノイズ)"]
B --> C["エンジニアがアラートを無視し始める"]
C --> D["本当に重要なアラートも見逃される"]
D --> E["重大インシデントの検知が遅れる"]
E --> F["「アラートが役に立たない」という認識が広まる"]
F --> G["アラートへの信頼が完全に失われる"]
G -->|"悪循環"| A
style A fill:#ffebee,stroke:#c62828
style G fill:#ffebee,stroke:#c62828
シグナルとノイズの分離
良いアラートの3条件
interface GoodAlert {
// 1. アクショナブル: 受信者が何かアクションを取る必要がある
actionable: true;
// 2. 緊急性: すぐに対応しなければ影響が拡大する
urgent: true;
// 3. 明確な対応先: 誰が対応すべきか明確
hasOwner: true;
}
// アラートの分類フレームワーク
type AlertClassification =
| 'page' // 即時対応が必要 → PagerDuty
| 'ticket' // 営業時間内に対応 → Jiraチケット
| 'log' // 記録のみ → ダッシュボード
| 'suppress'; // 不要 → 削除
アラート分類マトリクス
| 緊急性高い | 緊急性低い | |
|---|---|---|
| ユーザー影響あり | Page(即時対応) | Ticket(計画対応) |
| ユーザー影響なし | Ticket(営業時間対応) | Log(記録のみ) |
重要度レベル(Severity)
4段階のSeverityモデル
| Severity | 名称 | 基準 | 通知方法 | 対応時間 |
|---|---|---|---|---|
| SEV1 | Critical | サービス全体停止、データ損失リスク | PagerDuty即時呼出 | 5分以内 |
| SEV2 | Major | 主要機能の障害、一部ユーザー影響 | PagerDuty + Slack | 15分以内 |
| SEV3 | Minor | 非主要機能の障害、回避策あり | Slack通知 | 営業時間内 |
| SEV4 | Info | パフォーマンス低下、潜在的問題 | ダッシュボード | 次スプリント |
Severity判定フロー
graph TD
A["障害が発生"] --> B{"サービス全体が<br/>停止している?"}
B -->|"はい"| SEV1A["SEV1"]
B -->|"いいえ"| C{"主要機能が<br/>使えない?"}
C -->|"はい"| D{"影響ユーザー数<br/>> 10%?"}
D -->|"はい"| SEV1B["SEV1"]
D -->|"いいえ"| SEV2["SEV2"]
C -->|"いいえ"| E{"非主要機能が<br/>使えない?"}
E -->|"はい"| SEV3["SEV3"]
E -->|"いいえ"| F{"パフォーマンス<br/>低下のみ?"}
F -->|"はい"| SEV4["SEV4"]
style SEV1A fill:#ffebee,stroke:#c62828,stroke-width:2px
style SEV1B fill:#ffebee,stroke:#c62828,stroke-width:2px
style SEV2 fill:#fff3e0,stroke:#e65100,stroke-width:2px
style SEV3 fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
style SEV4 fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
Multi-Window Multi-Burn-Rate アラート
SLOベースアラートの原則
従来の「閾値超えアラート」ではなく、SLOのエラーバジェット消費速度(バーンレート)に基づくアラートです。
// バーンレートの計算
interface BurnRateAlert {
// バーンレート = 実際のエラー率 / SLOで許容されるエラー率
burnRate: number;
// 長いウィンドウ: 全体的な傾向を把握
longWindow: string;
// 短いウィンドウ: 現在も問題が継続しているか確認
shortWindow: string;
// アラートの重要度
severity: 'critical' | 'warning' | 'info';
}
// Multi-Window Multi-Burn-Rate設定
const burnRateAlerts: BurnRateAlert[] = [
{
burnRate: 14.4,
longWindow: '1h',
shortWindow: '5m',
severity: 'critical',
// 30日バジェットを約2日で消費するペース
},
{
burnRate: 6.0,
longWindow: '6h',
shortWindow: '30m',
severity: 'warning',
// 30日バジェットを約5日で消費するペース
},
{
burnRate: 3.0,
longWindow: '3d',
shortWindow: '6h',
severity: 'info',
// 30日バジェットを約10日で消費するペース
},
];
Prometheusでの実装
# SLOベースのバーンレートアラート
groups:
- name: slo_burn_rate
rules:
# エラー率の Recording Rule
- record: sli:http_error_rate:5m
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
# Critical: バーンレート 14.4x(1時間ウィンドウ)
- alert: SLOBurnRateCritical
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
annotations:
summary: "SLOバーンレートがCriticalレベル"
description: "エラーバジェットが約2日で枯渇するペースで消費中"
runbook_url: "https://wiki.example.com/runbooks/slo-burn-rate-critical"
# Warning: バーンレート 6.0x(6時間ウィンドウ)
- alert: SLOBurnRateWarning
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[6h]))
/ sum(rate(http_requests_total[6h]))
) > (6.0 * 0.001)
and
(
sum(rate(http_requests_total{status=~"5.."}[30m]))
/ sum(rate(http_requests_total[30m]))
) > (6.0 * 0.001)
for: 5m
labels:
severity: warning
annotations:
summary: "SLOバーンレートがWarningレベル"
description: "エラーバジェットが約5日で枯渇するペースで消費中"
runbook_url: "https://wiki.example.com/runbooks/slo-burn-rate-warning"
# Info: バーンレート 3.0x(3日ウィンドウ)
- alert: SLOBurnRateInfo
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[3d]))
/ sum(rate(http_requests_total[3d]))
) > (3.0 * 0.001)
and
(
sum(rate(http_requests_total{status=~"5.."}[6h]))
/ sum(rate(http_requests_total[6h]))
) > (3.0 * 0.001)
for: 30m
labels:
severity: info
annotations:
summary: "SLOバーンレートがInfoレベル"
description: "エラーバジェットが約10日で枯渇するペースで消費中"
アラートルールの設計原則
アンチパターンと改善
| アンチパターン | 問題 | 改善 |
|---|---|---|
| CPU > 80% でアラート | CPU使用率が高くてもユーザー影響がない場合がある | SLI(レイテンシ、エラー率)ベースに変更 |
| 閾値固定の静的アラート | トラフィックパターンの変化に対応できない | バーンレートベースの動的アラート |
| 全アラートをPagerDuty送信 | アラート疲れの原因 | Severity分類で通知先を分離 |
| アラートにランブックURLなし | 対応方法がわからない | 全アラートにrunbook_url付与 |
| 重複アラート | 同じ問題で複数アラート発火 | アラートのグルーピングと抑制 |
Alertmanagerでのルーティング
# alertmanager.yml
global:
resolve_timeout: 5m
slack_api_url: 'https://hooks.slack.com/services/xxx'
route:
group_by: ['alertname', 'service']
group_wait: 30s # 同じグループのアラートを30秒間集約
group_interval: 5m # 同グループの再通知間隔
repeat_interval: 4h # 同じアラートの繰り返し通知間隔
receiver: 'default-slack'
routes:
# Critical → PagerDuty即時通知
- match:
severity: critical
receiver: 'pagerduty-critical'
repeat_interval: 5m
# Warning → Slack #alerts-warning
- match:
severity: warning
receiver: 'slack-warning'
repeat_interval: 1h
# Info → Slack #alerts-info(営業時間のみ)
- match:
severity: info
receiver: 'slack-info'
repeat_interval: 12h
mute_time_intervals:
- outside-business-hours
receivers:
- name: 'pagerduty-critical'
pagerduty_configs:
- service_key: '<pagerduty-service-key>'
severity: critical
- name: 'slack-warning'
slack_configs:
- channel: '#alerts-warning'
title: '{{ .CommonLabels.alertname }}'
text: '{{ .CommonAnnotations.description }}'
- name: 'slack-info'
slack_configs:
- channel: '#alerts-info'
- name: 'default-slack'
slack_configs:
- channel: '#alerts-general'
# 抑制ルール: Criticalが発火中はWarningを抑制
inhibit_rules:
- source_match:
severity: critical
target_match:
severity: warning
equal: ['alertname', 'service']
# 営業時間外の定義
time_intervals:
- name: outside-business-hours
time_intervals:
- weekdays: ['saturday', 'sunday']
- times:
- start_time: '00:00'
end_time: '09:00'
- start_time: '18:00'
end_time: '24:00'
アラートの継続的改善プロセス
アラートは一度設計して終わりではありません。定期的にレビューし改善するプロセスが重要です。
週次アラートレビューのチェックリスト:
- 発火回数の確認: 各アラートの発火回数を集計
- アクション率の計算: 実際にアクションが取られたアラートの割合
- 偽陽性の特定: アクション不要だったアラートの原因を分析
- 不足の特定: アラートなしで検知された障害がなかったか
- 閾値の調整: データに基づいてアラート閾値を最適化
interface AlertReview {
alertName: string;
fireCount: number;
actionTaken: number;
falsePositives: number;
recommendation: 'keep' | 'tune' | 'remove' | 'merge';
}
function calculateActionableRate(review: AlertReview): number {
return review.actionTaken / review.fireCount * 100;
}
// アクション率50%未満のアラートは見直し対象
function flagForReview(reviews: AlertReview[]): AlertReview[] {
return reviews.filter(r => calculateActionableRate(r) < 50);
}
まとめ
| ポイント | 内容 |
|---|---|
| アラート疲れ | 不要アラートの大量通知により重要アラートが見逃される問題 |
| シグナル分離 | アクショナブル・緊急性・対応先の3条件で分類 |
| Severityモデル | SEV1〜SEV4の4段階で通知方法と対応時間を分離 |
| バーンレートアラート | SLOベースのMulti-Window Multi-Burn-Rateで精度向上 |
| 継続的改善 | 週次レビューでアクション率を計測し、ノイズを削減 |
チェックリスト
- アラート疲れの原因と悪循環を説明できる
- 良いアラートの3条件を理解した
- Severity分類に基づく通知ルーティングを設計できる
- Multi-Window Multi-Burn-Rateアラートの仕組みを説明できる
- Alertmanagerのルーティング・抑制ルールを設定できる
次のステップへ
次は「オンコール体制の構築」を学びます。アラートを受け取る側の体制 — ローテーション、エスカレーション、ワークライフバランスの維持について深掘りしていきましょう。
推定読了時間: 40分