Grafanaダッシュボードが亀のように遅く、深夜3時にアラートが鳴り続けるとき
PrometheusをシステムへDoに導入した当初、セットアップさえ終わればそれでいいと思っていました。ダッシュボードが動き、Grafanaが接続でき、メトリクスも取得できている——問題ないはず、と。ところが数週間後、チームからクレームが来始めました。ダッシュボードのロードに10〜15秒かかる、アラートが数分おきに鳴って自然に止まる(フラッピング)、そして最も困ったのが——深夜3時に「CPU高負荷」のアラートが届いてSSHでサーバーにログインしてみると……CPUは正常だった、という状況です。
問題はPrometheusにあったのではありません。使い方にあったのです——具体的には、完全に見落としていた2つの機能:Recording Rulesと、正しく書かれたAlerting Rulesです。
なぜダッシュボードが遅く、アラートが信頼できないのか?
Prometheusは生のメトリクスを時系列データとして保存します。GrafanaがパネルをレンダリングするたびにPromQLクエリがPrometheusに送信され、そのクエリは選択した時間範囲内の数百万のデータポイントをスキャンする可能性があります。
たとえば「5分平均CPU使用率」パネルのクエリ:
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
一見シンプルに見えますが、サーバーが50台あってダッシュボードにパネルが20個ある場合、Grafanaのリフレッシュのたびに20個のクエリが同時実行され、それぞれが表示中の時間範囲全体の時系列をスキャンします。ダッシュボードのラグは必然です。
アラートのフラッピングはまた別の話です。アラート条件をバッファなしで生メトリクスに対して直接評価した場合、2秒間のCPUスパイクでアラートが発火します。CPUが正常に戻る→アラート解除→スパイク再発→アラート再発。深夜に無害な短いスパイクで叩き起こされることになります。
Recording Rules:事前計算して、後からクエリする
Recording Rulesのアイデアはとてもシンプルです。Grafanaがリフレッシュのたびに最初から計算し直す代わりに、Prometheusに定期的(デフォルト1分)に結果を計算させて新しいメトリクスとして保存しておくのです。Grafanaはそのメトリクスを読み込むだけ——単純な数値のクエリと同じ速さです。
Recording Rulesのファイル構造
ファイル/etc/prometheus/rules/recording_rules.ymlを作成します:
groups:
- name: node_cpu_recording
interval: 1m # 1分ごとに再計算
rules:
# インスタンスごとの5分平均CPU使用率
- record: job:node_cpu_usage:avg5m
expr: |
100 - (
avg by (instance, job) (
rate(node_cpu_seconds_total{mode="idle"}[5m])
) * 100
)
# インスタンスごとのメモリ使用率(%)
- record: job:node_memory_usage:ratio
expr: |
1 - (
node_memory_MemAvailable_bytes
/ node_memory_MemTotal_bytes
)
# ディスクの読み書きスループット合計
- record: job:node_disk_throughput:rate5m
expr: |
sum by (instance, job) (
rate(node_disk_read_bytes_total[5m])
+ rate(node_disk_written_bytes_total[5m])
)
メトリクス名について:Prometheusの公式規則はlevel:metric:operationsです。例えばjob:node_cpu_usage:avg5mは「jobレベルで集計、メトリクスはcpu usage、オペレーションはavg 5分」と読みます。必須ではありませんが、推奨されます——特にチームが大きくなって素早く調べる必要が出てきたときに役立ちます。
prometheus.ymlにrulesファイルを登録する
# /etc/prometheus/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 1m # ルール評価の頻度
rule_files:
- "/etc/prometheus/rules/*.yml"
リロード前にルールの有効性を確認する
# promtoolでバリデーション — Prometheusの再起動不要
promtool check rules /etc/prometheus/rules/recording_rules.yml
# PrometheusをリロードしてルールUpdate(ダウンタイムなし)
curl -X POST http://localhost:9090/-/reload
Recording Rulesを適用した後、ダッシュボードのロード時間が15秒から2秒以下に短縮されました。サーバーをアップグレードする必要も、Prometheusをチューニングする必要もありません——既存の機能を正しく使うだけでよかったのです。
Alerting Rules:適切なタイミングで、適切な人へアラートを送る
Alerting RulesはRecording Rulesのように結果を保存するのではなく、条件を評価して条件が満たされたときにAlertmanagerへ送信します。フラッピングを排除する鍵はforパラメータにあります。シンプルに聞こえますが、これが最もよく見落とされているものだと感じています。
標準的なAlerting Rulesの構造
# /etc/prometheus/rules/alerting_rules.yml
groups:
- name: node_alerts
rules:
# CPUが継続的に10分間高負荷の場合のみアラート — フラッピング防止
- alert: HighCPUUsage
expr: job:node_cpu_usage:avg5m > 85
for: 10m
labels:
severity: warning
team: infrastructure
annotations:
summary: "{{ $labels.instance }} のCPU使用率が高い"
description: |
インスタンス {{ $labels.instance }} のCPU使用率が {{ $value | printf "%.1f" }}% で
10分間継続して85%を超えています。高負荷のプロセスを確認してください。
runbook_url: "https://wiki.internal/runbooks/high-cpu"
# メモリ不足 — 直接影響するためcritical
- alert: HighMemoryUsage
expr: job:node_memory_usage:ratio > 0.90
for: 5m
labels:
severity: critical
team: infrastructure
annotations:
summary: "{{ $labels.instance }} のメモリが残りわずか"
description: |
{{ $labels.instance }} のメモリ使用率が {{ $value | printf "%.0f%%" }} です。
システムがスワップを開始するか、OOMキルが発生する可能性があります。
# ディスク残量低下 — predict関数で事前警告
- alert: DiskWillFillSoon
expr: |
predict_linear(
node_filesystem_avail_bytes{mountpoint="/"}[6h], 24 * 3600
) < 0
for: 30m
labels:
severity: warning
annotations:
summary: "{{ $labels.instance }} のディスク / が24時間以内に満杯になります"
description: |
過去6時間の増加速度に基づくと、{{ $labels.instance }} の
ディスク {{ $labels.mountpoint }} は24時間以内に満杯になります。
重要なパラメータの解説
for: 10m— 条件が継続して10分間満たされた場合のみアラートが発火します。2秒間のCPUスパイクでは条件不足 → アラートなし。フラッピングを排除する最もシンプルで効果的な方法です。labels.severity— Alertmanagerはこのラベルでルーティングします:warningは営業時間内にSlackへ、criticalは深夜3時にPagerDutyへ。最初から正しく分類することでオンコール担当者のバーンアウトを防げます。annotations.description—{{ $labels.instance }}と{{ $value }}を使ったテンプレートにより、具体的な数値をメッセージに直接埋め込めます。オンコールエンジニアはアラートを読むだけで問題を把握でき、SSHで追加確認する必要がありません。predict_linear()— ディスクが満杯になってからアラートを送る(対応が遅すぎる)のではなく、この関数は直近6時間の増加速度に基づいて予測します。24時間前に警告——ログを整理したりボリュームを拡張したりするのに十分な時間です。
Recording RulesとAlerting Rulesの組み合わせ
よく使うパターン:Recording Rulesで複雑なメトリクスを計算し、Alerting Rulesでそのメトリクスを使ってアラートを設定します。高速でかつ一貫性があり——ダッシュボードとアラートが同じ数値を使うため、ダッシュボードにCPU 40%と表示されているのにアラートがCPU 90%と叫ぶ、という事態は起きません。
groups:
- name: http_slo_recording
rules:
# 5分間のHTTP 5xxエラー率
- record: job:http_error_rate:ratio5m
expr: |
sum by (job, instance) (
rate(http_requests_total{status=~"5.."}[5m])
)
/
sum by (job, instance) (
rate(http_requests_total[5m])
)
- name: http_slo_alerts
rules:
# エラー率がSLO 1%を超えた場合にアラート
- alert: HTTPErrorRateTooHigh
expr: job:http_error_rate:ratio5m > 0.01
for: 5m
labels:
severity: critical
annotations:
summary: "HTTPエラー率が高い: {{ $labels.job }}"
description: |
サービス {{ $labels.job }} で {{ $value | printf "%.2f%%" }} の5xxエラーが発生しています。
SLOの上限は1%です。アプリケーションログをすぐに確認してください。
何度も失敗してデプロイした末に学んだこと
最初から正しくできたわけではありません。以下は数ヶ月の本番環境を経て安定稼働している手順です:
- グループごとにファイルを分ける:
recording_node.yml、alerting_node.yml、alerting_http.yml……——1つのファイルにエラーが発生した場合はそのファイルだけロールバックすればよく、残りに影響しません。 - プルリクエストをマージする前にCI/CDで
promtool check rulesを実行する——構文エラーを早期に検出し、デプロイ時になって初めて気づくことを防ぎます。 forはwarningで最低5分、criticalで2〜3分に設定する——短いスパイクをフィルタリングするのに十分でありながら、本当のインシデント時に過度に遅延しない設定です。- 2回以上現れるクエリにはRecording Rulesを使う——ダッシュボードでもアラートルールでも同様です。DRYの原則はPromQLにも完全に適用されます。
- アノテーションには必ず
runbook_urlを含める——処理手順のドキュメントへの直接リンク。チームに新しく参加したオンコールエンジニアも何をすべきかわかり、深夜2時に他のメンバーへ連絡する必要がありません。
以前は、インシデントが発生するたびに各サーバーにSSHしてチェックする必要がありました。今ではアラートが必要な情報をすべて含めてTelegramに直接送信されてきます——ラップトップを開く必要すらないことが多い。しかし、対応速度以上に価値があるのは信頼できるアラートです。チームはもう「これはフラッピングじゃないか」と疑う必要がありません——アラートが鳴れば、対処すべき本物の問題があるのです。
