Prometheus Recording Rules と Alerting Rules 上級編:クエリの最適化と本番環境における精密アラートの構築

Monitoring tutorial - IT technology blog
Monitoring tutorial - IT technology blog

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%です。アプリケーションログをすぐに確認してください。

何度も失敗してデプロイした末に学んだこと

最初から正しくできたわけではありません。以下は数ヶ月の本番環境を経て安定稼働している手順です:

  1. グループごとにファイルを分けるrecording_node.ymlalerting_node.ymlalerting_http.yml……——1つのファイルにエラーが発生した場合はそのファイルだけロールバックすればよく、残りに影響しません。
  2. プルリクエストをマージする前にCI/CDでpromtool check rulesを実行する——構文エラーを早期に検出し、デプロイ時になって初めて気づくことを防ぎます。
  3. forはwarningで最低5分、criticalで2〜3分に設定する——短いスパイクをフィルタリングするのに十分でありながら、本当のインシデント時に過度に遅延しない設定です。
  4. 2回以上現れるクエリにはRecording Rulesを使う——ダッシュボードでもアラートルールでも同様です。DRYの原則はPromQLにも完全に適用されます。
  5. アノテーションには必ずrunbook_urlを含める——処理手順のドキュメントへの直接リンク。チームに新しく参加したオンコールエンジニアも何をすべきかわかり、深夜2時に他のメンバーへ連絡する必要がありません。

以前は、インシデントが発生するたびに各サーバーにSSHしてチェックする必要がありました。今ではアラートが必要な情報をすべて含めてTelegramに直接送信されてきます——ラップトップを開く必要すらないことが多い。しかし、対応速度以上に価値があるのは信頼できるアラートです。チームはもう「これはフラッピングじゃないか」と疑う必要がありません——アラートが鳴れば、対処すべき本物の問題があるのです。

Share: