Prometheus Alertmanager を電話で鳴らす:本当に緊急なものだけ

Alertmanager には音声レシーバーがありません。critical のときだけ Prometheus のアラートを電話に変える方法:webhook 設定、条件、そして Watchdog の罠。

更新日

目次

Alertmanager には音声レシーバーがありません。Prometheus のアラート発火時に電話を受けるには、購読タイプを着信に設定した Echobell チャンネルへ向けた webhook_configs レシーバーを追加します。本記事では、そのための正確な YAML、解決済みアラートに電話をかけさせないための条件、重大度によるルーティング、そして放置すると 4 時間おきに永遠に鳴り続ける Watchdog アラートについて解説します。

Prometheus はこの 10 年に構築されたインフラの事実上標準のメトリクス基盤であり、Alertmanager は難しい部分——アラートの重複排除、グルーピング、メンテナンス中のサイレンス、上流障害時の下流ノイズの抑制——を本当によくこなします。

唯一やってくれないのが、人を起こすことです。

なぜ Alertmanager 単体では電話を鳴らせないのか

Alertmanager にはメール、Slack、PagerDuty、OpsGenie、Discord、Telegram、Pushover、Webex、MS Teams など十数種のレシーバーがあります。しかしそのすべてが届けるのはメッセージであり、メッセージは消音スイッチ、おやすみモード、iOS の集中モードの影響を受けます。深夜 3 時においては、アラートは届いたが何も起きなかった、ということです。

voice_configs は存在しません。多くの人が行き着く選択肢はこうです。

  • PagerDuty / OpsGenie / Splunk On-Call —— 確かに電話をかけてくれますが、いずれも本格的なインシデント管理プラットフォームで、シートあたりの価格もそれ相応です。ローテーションとエスカレーションツリーが必要なら正解ですが、電話を鳴らしたいだけなら重すぎます。(OpsGenie 自体がサービス終了へ向かっていることも、いま多くのチームがこの層を見直している理由です。)
  • Sachet のような SMS ブリッジ —— サービスをもう 1 つ運用し、ゲートウェイに 1 通ごとに課金され、それでも SMS はメッセージのままです。iOS では、送信者が許可リストに入っていない限り SMS が集中モードを突破することはありません。
  • Twilio ベースの自作 —— 小さな webhook レシーバーを書き、番号を買い、通話ごとに支払い、そして「電話を鳴らす」ことだけが仕事の本番インフラを 1 つ抱えることになります。

汎用の webhook レシーバーが抜け道です。ドキュメント化された JSON を任意の URL に POST してくれる——必要なのはそれだけです。

必要なもの

  • 稼働中の Prometheus + Alertmanager と、alertmanager.yml を編集できる権限
  • Echobell のインストール(App Store / Google Play
  • 10 分

本記事は Alertmanager 0.31 を前提に書いています。webhook のペイロードは長年 version: "4" のままなので、0.2x 系でも挙動は同じです。

Alertmanager からは hook.echobell.one への外向き HTTPS が必要ですが、インターネットから到達可能である必要はありません。クラスタ内、VPC 内、自宅ラボの Alertmanager でも問題なく動きます。

ステップ 1 —— 電話をかけてくるチャンネルを作る

Echobell で Prometheus Critical のような名前のチャンネルを作成します。購読の通知タイプを着信に設定してください。ここが肝心です。着信タイプのアラートは着信画面として表示され、iOS の集中モードやおやすみモードを突き抜けて鳴ります。通常のプッシュ通知ではこうはいきません。

テンプレートは Alertmanager のペイロードを直接読むように設定します。

Title: 🔴 {{commonLabels.alertname}} on {{commonLabels.instance}}
Body: {{commonAnnotations.summary}}
{{commonAnnotations.description}}

さらに詳細設定でリンクテンプレートを設定すると、通知記録から該当グラフへ直接飛べます。

{{alerts[0].generatorURL}}

そしてチャンネルの Webhook URL をコピーします。

https://hook.echobell.one/t/<channel-token>

この URL は秘密情報として扱ってください。これを持っている人は誰でもあなたの電話を鳴らせます。

ステップ 2 —— webhook レシーバーを追加する

alertmanager.yml にて:

route:
  group_by: ["alertname", "cluster", "service"]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  receiver: echobell-critical

receivers:
  - name: echobell-critical
    webhook_configs:
      - url: "https://hook.echobell.one/t/<channel-token>"
        send_resolved: false

curl -X POST http://localhost:9093/-/reloadSIGHUP でリロードします。

send_resolved: false に注目してください。webhook レシーバーのデフォルトは true で、Alertmanager の他のほとんどのレシーバーとは逆です。つまりこの行を省くと、サービスが壊れたときに電話が鳴り、勝手に直ったときにもう一度鳴ります。この 2 回目の電話こそが、人に 1 回目を無視することを教えてしまいます。ステップ 4 で、着信音なしに復旧通知を受け取る方法を説明します。

ステップ 3 —— 実際に届くものを理解する

Alertmanager はアラートをグループ化し、グループごとに 1 つのペイロードを POST します。

{
  "version": "4",
  "groupKey": "{}:{alertname=\"HighErrorRate\"}",
  "truncatedAlerts": 0,
  "status": "firing",
  "receiver": "echobell-critical",
  "groupLabels": { "alertname": "HighErrorRate" },
  "commonLabels": { "alertname": "HighErrorRate", "severity": "critical" },
  "commonAnnotations": { "summary": "Error rate above 5% for 10m" },
  "externalURL": "http://alertmanager.internal:9093",
  "alerts": [
    {
      "status": "firing",
      "labels": { "alertname": "HighErrorRate", "instance": "api-7d9f:8080" },
      "annotations": { "summary": "Error rate above 5% for 10m" },
      "startsAt": "2026-09-04T02:41:07.351Z",
      "endsAt": "0001-01-01T00:00:00Z",
      "generatorURL": "http://prometheus:9090/graph?g0.expr=...",
      "fingerprint": "a1b2c3d4e5f60718"
    }
  ]
}

Echobell は JSON ボディをそのまま読むので、上記のすべてのフィールドがテンプレートと条件で使えます。ネストしたアクセスはどちらの記法でも動きます——{{commonLabels.severity}} でも {{alerts[0].labels["instance"]}} でも構いません。

このペイロードの 2 つの性質が、以降のすべてを決めます。

トップレベルの status は、グループ内のいずれかのアラートが発火していれば firing になります。 グループ内のすべてのアラートが解決して初めて resolved になります。だからこそ、フィルタ対象としてきれいに使えます。

commonLabels にはグループ内の全アラートに共通するラベルだけが入ります。 これが最もよくある落とし穴です。group_by が広く、1 回の webhook が 3 つの異なるインスタンスの HighErrorRate を運んでいる場合、commonLabels.instance は存在せず、{{commonLabels.instance}} は空文字列としてレンダリングされます。対処法は後述の専用セクションで扱います。

ステップ 4 —— 復旧は静かなプッシュで送る

復旧したことは知りたい——ただ、それで電話を受けたくないだけです。Prometheus Recovered という 2 つ目の Echobell チャンネルを作り、通知タイプを通常にして、次のテンプレートを設定します。

Title: ✅ {{commonLabels.alertname}} resolved
Body: {{commonAnnotations.summary}}

そして詳細設定に次の条件を入れます。

status == "resolved"

条件は、何かが配信される前に評価される式です。式が偽なら、Echobell はリクエストを受け付けたうえで何も送りません。

あとは同じレシーバーから両方のチャンネルへ向けます。1 つのレシーバーは複数の webhook_configs を持てます。

receivers:
  - name: echobell-critical
    webhook_configs:
      # 電話を鳴らす。発火時のみ。
      - url: "https://hook.echobell.one/t/<calling-channel-token>"
        send_resolved: false
      # 静かなプッシュ。チャンネル条件が firing 側を落とす。
      - url: "https://hook.echobell.one/t/<recovery-channel-token>"
        send_resolved: true

復旧チャンネルは firing と resolved の両方のペイロードを受け取り、firing のほうを捨てます。結果として、障害では電話が鳴り、復旧は朝に読むプッシュとして届きます。

ステップ 5 —— 全部ではなく、重大度でルーティングする

すべてのアラートを着信チャンネルに送るキャッチオールルートは、無視される電話を量産する装置です。重大度による分岐は、ルーティングツリー本来の居場所である Alertmanager 側で行いましょう。

route:
  group_by: ["alertname", "cluster", "service"]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  receiver: echobell-warning

  routes:
    # Watchdog は決して人間に届かせない。ステップ 6 参照。
    - matchers:
        - alertname = "Watchdog"
      receiver: "null"

    - matchers:
        - severity = "critical"
      receiver: echobell-critical
      group_wait: 10s
      repeat_interval: 1h

receivers:
  - name: "null"

  - name: echobell-critical
    webhook_configs:
      - url: "https://hook.echobell.one/t/<calling-channel-token>"
        send_resolved: false

  - name: echobell-warning
    webhook_configs:
      - url: "https://hook.echobell.one/t/<normal-channel-token>"
        send_resolved: true

ルートは上から順に評価され、最初にマッチしたものが勝ちます——continue のデフォルトは false です。したがって順序が重要で、Watchdog のルートは、それを飲み込みうるルールより上に置く必要があります。

チャンネルを 1 つにまとめて Echobell 側でフィルタしたい場合、等価な条件はこうです。

status == "firing" && commonLabels.severity == "critical"

通常は Alertmanager 側のほうが優れています。そちらなら severitygroup_waitrepeat_interval も同時に制御できるからです。Echobell 側が向いているのは、今日中に設定変更をマージできないときです。

ステップ 6 —— Watchdog の罠

kube-prometheus-stack を使っているなら、式が vector(1)Watchdog というアラートがあります。これは常に発火するように設計されています——Prometheus 自体が止まったことを外部システムが検知できるようにするために存在します。デフォルト設定では null レシーバーへルーティングされます。

これを除外せずにキャッチオールルートを着信チャンネルへ向けると、Watchdog は repeat_interval ごとに、永遠に、しかも即座にあなたへ電話をかけ続けます。人々が「電話アラートは使い物にならない」と結論づける原因の第 1 位がこれです。

ステップ 5 の null ルートは残しておいてください。そのうえで、任意ですが有用なことをしましょう——Watchdog を本物のデッドマンズスイッチに変えるのです。

    - matchers:
        - alertname = "Watchdog"
      receiver: deadmansswitch
      group_wait: 0s
      group_interval: 1m
      repeat_interval: 50s

receivers:
  - name: deadmansswitch
    webhook_configs:
      - url: "https://hc-ping.com/<your-check-uuid>"
        send_resolved: false

Echobell 自身はデッドマンズスイッチにはなれません。リクエストが届いたときに通知するのであって、届かなくなったときではないからです。そこで Watchdog の ping は、沈黙の検知を目的としたサービス(Healthchecks.io、Cronitor、Dead Man's Snitch)へ送り、そのサービスの「チェックがダウンした」webhook を Echobell の着信チャンネルへ向けます。こうすれば、電話が鳴るということは「監視自体が死んだ」という意味になります。これこそ最も起こされたいアラートであり、そして誰も設定していないアラートです。

狼少年にしないためのチューニング

Alertmanager 側の 3 つの設定がほとんどの仕事をし、Prometheus 側にもう 1 つあります。

設定場所役割
for:アラートルール発火するまでに条件が継続しなければならない時間。2 秒の瞬断に対する第一の防御線。
group_waitルート最初の通知までに他のアラートを待つ時間。デフォルト 30s。critical では 10s まで下げる。
group_intervalルート既存グループ内の新規アラートについて通知するまでの最小間隔。デフォルト 5m。
repeat_intervalルート未解決アラートを再通知する頻度。デフォルト 4h——つまり夜間障害は 3:00 に電話をかけ、7:00 にもう一度かけてきます。

考える価値があるのは repeat_interval です。4 時間は何かを壊れたまま放置するには長すぎ、20 分はチャンネルを無効化させるための装置です。critical に 1 時間、あたりが妥当な出発点でしょう。

出られなかった電話を次の repeat_interval を待たずに即座に再試行させたい場合は、Echobell アプリ設定の失敗した通話を再試行をオンにしてください。

業務時間外だけ鳴らす

日中はおそらくすでにダッシュボードを見ています。Echobell のシステム時刻変数(すべて UTC)を使えば、Alertmanager に 2 本目のルートを足さずに、チャンネルを時間帯ごとに違う振る舞いにできます。

status == "firing" && (hour >= 17 || hour < 9)

これで UTC 09:00–17:00 の外でのみ電話がかかります。日中のプッシュ用には、通常タイプの 2 つ目のチャンネルを逆の時間帯に向けます。

status == "firing" && hour >= 9 && hour < 17

dayOfWeek >= 1 && dayOfWeek <= 5 を足せば、週末も業務時間外として扱えます。これらは常に UTC で計算される点に注意し、自分のタイムゾーン分をずらしてください。より詳しくは Echobell の UTC 条件で時間帯を指定した通知を実現するを参照してください。

commonLabels が空になる問題への対処

グループが複数インスタンスのアラートを含むと commonLabels.instance は消え、通知タイトルは 🔴 HighErrorRate on になってしまいます。

推奨度順に 3 つの対処法があります。

  1. そのラベルを group_by に入れる。 group_byinstance を含めれば、グループ内のすべてのアラートがそれを共有し、commonLabels.instance は常に存在します。代償は通知数の増加です——アラート名ごと 1 通ではなく、インスタンスごと 1 通になります。
  2. 最初のアラートを読む。 {{alerts[0].labels.instance}} は必ず値を持ちます。ただしそれは多数のうちの 1 つでしかないので、件数と組み合わせましょう:{{alerts[0].labels.instance}}(全 {{alerts.length}} 件)
  3. 空でも読めるようにラベルを設計する。 Echobell にはデフォルト値演算子がありません——{{a || "unknown"}} はフォールバックではなくリテラルの true をレンダリングします——ので、Instance: {{commonLabels.instance}} を独立した行に書き、空値が壊れた文章ではなく明らかに空だと分かるようにします。

ペイロードを小さく保つ

100 個の Pod をまたぐグループは大きな JSON ボディを生み、Echobell は 1 MiB を超えるトリガーボディを HTTP 413 で拒否します。Alertmanager 側で上限を設けましょう。

      - url: "https://hook.echobell.one/t/<channel-token>"
        send_resolved: false
        max_alerts: 20

これで Alertmanager は最大 20 件までを送り、切り捨てた件数を truncatedAlerts に入れます。本文に出すこともできます。

Body: {{commonAnnotations.summary}}
Alerts: {{alerts.length}}(+{{truncatedAlerts}} 件を切り捨て)

チームとアラートを共有する

Echobell のチャンネルは購読リンクで共有でき、購読者はそれぞれ自分の通知タイプを選べます。したがって同じルートで、オンコール担当の電話を鳴らしつつ、他の全員には通常プッシュとして届けられます——シート課金もなく、Alertmanager 側に追加のルーティングルールも要りません。

これは、多くのチームがそもそも Prometheus を自己ホストしている理由とも合致します。メトリクスとアラートルールは自分たちのインフラに留まり、Echobell は通知内容と履歴をサーバーではなく端末側に保持します。

この構成で得られないもの

境界を正直に示しておくと、後で悪い移行をせずに済みます。Echobell は配信レイヤーであり、インシデント管理プラットフォームではありません。次のものはありません。

  • オンコールのローテーション表やフォロー・ザ・サンの引き継ぎ
  • 最初の担当者が出なければ 2 人目を呼び出すエスカレーションツリー
  • インシデントのタイムライン、確認応答の追跡、ポストモーテム用ツール

チームにこれらが必要なら、必要なのは PagerDuty や Grafana Cloud IRM などです。ここで扱ったのは、Alertmanager が開けたままにしている具体的な穴——発火したアラートを、実際に鳴る電話に変えること——だけです。個人運用者、小規模チーム、自宅ラボにとっては、たいていそれが要件のすべてです。

トラブルシューティング

そもそも何も届かない。 まず Alertmanager 自身のログ(level=error component=dispatcher)を確認し、次にルートが本当にそのレシーバーに解決されるか確かめます。amtool config routes test severity=critical alertname=HighErrorRate を使えば、実際にアラートが発火するのを待たずに、どのレシーバーに落ちるかが分かります。

Echobell が HTTP 404 を返す。 チャンネルトークンが間違っているか、チャンネルが削除されています。未知のトークンは 404 であって、静かな成功ではありません。

Echobell が 200 と "notificationTriggered": false を返す。 条件が偽と評価されました。レスポンスボディには "conditionsMet": false も含まれており、「条件が間違っている」のか「webhook が届いていない」のかを最速で見分けられます。Alertmanager が実際に送った内容に対して status == "firing" を確認してください——alerts[0].status ではなくトップレベルの status です。

HTTP 413。 ペイロードが 1 MiB を超えました。上記のとおり max_alerts を設定してください。

HTTP 405。 チャンネルで POST Only が有効なのに、何かが GET を送っています。Alertmanager は POST するので、これはたいていブラウザで URL を試したという意味です。

タイトルに空白の穴がある。 そのグループの commonLabels にそのラベルが含まれていませんでした。上記のセクションを参照してください。

鳴らないが通知は届く。 購読の通知タイプが着信ではなく、通常または時間指定になっています。通知タイプは購読者ごとに選択するので、鳴っていない端末側で確認してください。

本番を壊さずにテストする。 expr: vector(1)、独自の alertnameseverity: critical を持つルールを追加し、一度発火させてから削除します。あるいは手動で 1 件投げます。

curl -X POST http://localhost:9093/api/v2/alerts -H 'Content-Type: application/json' -d '[
  {"labels":{"alertname":"EchobellTest","severity":"critical"},
   "annotations":{"summary":"Testing the phone call path"}}
]'

よくある質問

Prometheus Alertmanager は単体で電話をかけられますか?

かけられません。Alertmanager にはメール、Slack、PagerDuty、OpsGenie など多くのレシーバーがありますが、音声レシーバーも SMS レシーバーもありません。電話をかけるには、汎用の webhook レシーバーを Echobell のような発信可能なサービスへルーティングするか、インシデント管理プラットフォームに課金する必要があります。

電話アラートはおやすみモードを突破しますか?

します。Echobell の着信通知タイプは着信として表示されるため、iOS の集中モードとおやすみモードを突き抜けて鳴ります。詳細と関連設定は重要アラートを iOS Focus Mode 越しに届ける方法をご覧ください。

ファイアウォールの内側や Kubernetes 内の Alertmanager でも動きますか?

動きます。この webhook は Alertmanager からの外向き HTTPS リクエストなので、hook.echobell.one に到達できれば十分です。Alertmanager にパブリックアドレスや Ingress は不要です。

アラートが解決したときに電話が来ないようにするには?

着信チャンネルを指す webhook 設定に send_resolved: false を設定します。webhook レシーバーのデフォルトは true で、Alertmanager の他のほとんどのレシーバーとは逆なので、これはオプトインではなくオプトアウトです。それでも静かに復旧通知を受け取りたい場合は、条件を status == "resolved" にした 2 つ目のチャンネルを追加してください。

なぜ同じアラートで 4 時間おきに電話が鳴るのですか?

それは repeat_interval で、デフォルトは 4h です。Alertmanager はまだ発火中のアラートをこの間隔で再通知します。ルートごとに設定してください——critical に 1h はよくある選択です。キャッチオールルートを追加した直後から鳴り始めたのなら、犯人は常時発火する Watchdog アラートである可能性が高いので、ステップ 6 を参照してください。

同じアラートで複数人に電話をかけられますか?

かけられます。チャンネルを同僚と共有すれば、購読者はそれぞれ自分の通知タイプを選びます。着信チャンネルを購読している全員が呼び出され、シートあたりの課金もありません。

重大度のフィルタは Alertmanager と Echobell 条件のどちらでやるべきですか?

Alertmanager を優先してください。そちらでルーティングすれば group_waitrepeat_interval も重大度ごとに設定でき、ルーティングツリーが他の設定と一緒にバージョン管理に残ります。Echobell の条件は、Alertmanager の設定を変更できないときや、Alertmanager に概念のないフィルタ(時間帯など)に使いましょう。

まとめ

構成は、1 つのレシーバー、1 行の send_resolved: false、そして severity: critical 以外のすべてを着信チャンネルから遠ざけるルーティングツリーだけです。既存のアラートルール、グルーピング、サイレンス、抑制設定はそのままに、「Prometheus は気づいた」と「人間が気づいた」の間の隙間を埋めてくれます。

iPhone 版 Echobell をダウンロード、または Google Play で入手して、本番で頼る前に上記の EchobellTest アラートを一度発火させてみてください。


関連記事