Google Cloudの監視設計にて、Cloud Scheduler と Cloud Run Job で定期バッチを組むとき、 「このバッチが止まったことに気づけるようにしたい」と考えたことはないでしょうか。
自分は先日、 半月に 1 回だけ動くバッチの監視を設計していて、 ここで詰まりました。 CPU 使用率やエラー率のように「値が大きすぎる」を見るのは簡単なのに、 「そもそも動いていない」を検知しようとした途端に手が止まった のです。
調べた結果たどり着いたのが PromQL でしたが、 切り替えるだけでは解決せず、 本当の壁は別のところにあった と分かるまでに回り道をしました。 その過程を整理します。
PromQL とは
時系列データに対する検索・集計の言語 です。 SQL がテーブルに対する問い合わせ言語なのと同じ関係で、 PromQL は監視データに対する問い合わせ言語だと考えると分かりやすいと思います。
もとは Prometheus という OSS の監視ツールが持っている言語で、 監視の世界での標準になりました。 Cloud Monitoring でも PromQL でアラート条件を書けるようになっています。
Cloud Monitoring のアラート条件は 3 種類ある
Terraform で書く場合、 アラート条件の形式はおおよそ次の 3 つです。
| 書き方 | Terraform のブロック名 | 公式の日本語表記 | どんな条件か |
|---|---|---|---|
| 閾値 | condition_threshold |
指標しきい値 | 「この値が N を超えたら」。 アラートの基本的な書き方 |
| 欠損 | condition_absent |
指標なし | 「データが来なくなったら」。 死活監視で使う |
| クエリ | condition_prometheus_query_language |
PromQL ベースのアラート | 式を文字列で書く。 ここで PromQL が出てくる |
上 2 つは項目を埋めるだけなので比較的簡単に書ける反面、 表現できる範囲があらかじめ決まっています。 3 つ目は自分で式を書くぶん、 書くのが難しい代わりに表現力が高い。 このトレードオフが3つのアラート条件の形式間で存在します。
参考: アラートポリシーの動作 | Cloud Monitoring
「動いていない」は閾値では検知できない
監視の条件を設定するとき、 大半は「値が大きすぎる / 小さすぎる」で表現できます。 エラー率が 5% を超えた、 メモリ使用率が 80% を超えた、 という形です。
ところが Cloud Run Job が正常に動作しているかを監視したい場合、 この指標では監視できません。 Cloud Run Job は一定間隔で実行される定期バッチ処理のサービス だからです。 「Job が動いていない」という状態は、 データが 1 つも無い状態 になります。
閾値の条件は「届いたデータ点の値」を評価する仕組みなので、 データ点が無ければ評価そのものが起きず、 発火しようがありません。
エラー率のグラフが 0% で真っ平らなとき、 それは「正常でエラーがない」のかもしれないし、 「そもそもリクエストが 1 件も来ていない」 のかもしれません。 閾値では、 この 2 つを区別できません。
こういう非対称性のために、 Cloud Monitoring には欠損専用の条件形式として condition_absent が用意されています。
そもそもメトリクスとは何か
後半の「メトリクスの種類によって制限が違う」という話の土台になります。 メトリクスとは「(タイムスタンプ, 値) の並び」 です。 このデータの並びに対して ラベル が付与されています。
run.googleapis.com/job/completed_task_attempt_count
{ job_name="my-batch-job", result="succeeded" } → 10/01 03:00 に 1、 10/16 03:00 に 1
重要なのは、 ラベルの組み合わせ 1 つにつき時系列(time series)が 1 本できる ことです。 上の例で result="failed" のデータが届いたら、 それは同じメトリクスの中の 別の 1 本 になります。
そして アラートは時系列ごとに評価されます。 ラベルの付け方でメトリクスの性質が変わる、 重要な概念です。 合計 4,500 件のリクエストでも 3 本の系列に散っていれば 1 本あたり 1,500 件で、 閾値 4,000 の条件は発火しません。
ログとメトリクスの違い
| ログ | メトリクス | |
|---|---|---|
| 単位 | 1 件のイベント(1 行の JSON) | 数値の時系列 |
| 用途 | 後から読んで原因を追う手がかり | 継続的に量を可視化し、 異常を検知する |
| 量 | 多い、 重い | 少ない、 軽い |
アラートは基本的にログではなく メトリクスに設定します。 ログに設定すると扱うデータ量が膨大になりますし、 不要なアラートが発報されて管理が大変になるためです。
ログベースメトリクス
標準のメトリクスだけでは監視が難しいときに使うのが ログベースメトリクス です。 一言でいうと 「アプリが出したログを数えて、 メトリクスにする」機能 です。
アプリのログ ログベースメトリクス
{"event":"job_completed"} → job-completed = 1
Cloud Logging のフィルタ(jsonPayload.event="job_completed")を指定してメトリクスを定義すると、 一致するログが出るたびにカウントが上がります。
ラベルの組み合わせごとにストリームを持ち、 毎分 メモリ上のカウンタの値で時系列を更新している、 という構造です。
過去のログには遡らない
ログベースメトリクスには、 知らないと事故になる性質があります。
それは**メトリクスを作成した後に届いたログからしか値が作られない。**ということです。
過去のログに遡って集計されることはないので、 メトリクスを作った瞬間が起点になります。
つまり 障害が起きてから「このログを数えるメトリクスを作ろう」としても、 障害中のデータは取れません。 監視は先回りして作る必要があります。
condition_absent(metric absence)とは
「時系列にデータがない状態」を検知するための条件形式 です。
日本語ドキュメントでは、 この機能が 「指標なしのアラート ポリシー」、 条件そのものが 「指標の不在条件」 と訳されています。 condition_absent は Terraform のブロック名なので日本語名はありません。
指定するのは trigger absence time(公式訳は「トリガーとなる不在時間」) で、 「どれだけの時間データが無ければ通知するか」を決めます。
この condition_absent には 3 つの制約があります。
制約 1: 最低 1 回のデータが必要になる
一度もデータが出たことのないメトリクスでは条件が成立しません。 新規構築時に「Job を 1 回も起動していない」という状態だと、 そもそも監視をスタートさせることができない ということです。
制約 2: 時系列ごとに評価される
前述のとおりアラートは時系列ごとに評価されます。 既定では「いずれかの時系列で欠損があれば発火」という動作なので、 ラベルで系列が分かれていると片方だけ欠損しても発火します。 成功・失敗のラベルなら、 失敗の系列だけデータが無い状態(= ずっと成功している、 つまり正常)でも鳴ってしまいます。
制約 3: 上限は 23.5 時間
23.5 時間を超えるデータ欠損の監視は、 事実上 condition_absent ではできません。
参考: 指標なしのアラート ポリシーを作成する | Cloud Monitoring
半月に 1 回のバッチには使えない
冒頭の状況に戻ります。 監視したかったのは 半月に 1 回動く定期バッチ で、 実行間隔は最長 16 日あります。 上限は 23.5 時間なので、 16 日の途絶はどう頑張っても表現できません。
PromQL なら制約がない、 と思ったら
そこで見つけたのが PromQL でした。 式を自分で書ける形式なので 23.5 時間の制約はなく、 absent_over_time(some_metric[17d]) と書けば「直近 17 日間にデータが 1 つも無ければ 1 を返す」を表現できます。 これで解決だと思ったのですが、 実際には PromQL に別の制約がありました。
You can only alert on the most recent 25 hours of data for custom metrics (the prefix
custom.googleapis.com) and user-defined Log-Based metrics (the prefixlogging.googleapis.com/user/).
カスタムメトリクスおよびユーザー定義のログベースメトリクスについては、 直近 25 時間分のデータに対してのみアラートを設定できます。
自分が監視源にしていたのは、 アプリが出す「バッチ完了」ログを数えるログベースメトリクスでした。 つまり条件形式を PromQL に変えても、 23.5 時間が 25 時間になっただけで、 16 日の途絶は依然として表現できません。
制限は条件形式ではなくメトリクスの種類にあった
改めて整理すると、 データの観測期間は次のようになります。
| 組み合わせ | 評価できる範囲 | 16 日の途絶を検知できるか |
|---|---|---|
condition_absent + 任意のメトリクス |
23.5 時間 | できない |
| PromQL + ログベースメトリクス | 25 時間 | できない |
| PromQL + カスタムメトリクス | 25 時間 | できない |
| PromQL + Google Cloud 標準メトリクス | 2 年 | できる |
先ほどの引用をもう一度読むと、 25 時間の制限が課されているのは custom.googleapis.com と logging.googleapis.com/user/ の 2 つの接頭辞に限られている ことが分かります。 run.googleapis.com のような Google Cloud 標準メトリクス(GCP のサービスが自動で出すもの)は対象外で、 同じドキュメントに「you can use PromQL to alert on up to 2 years of metric data」と書かれています。
「PromQL にすれば長い期間を見られる」のではなく、 「標準メトリクスなら長い期間を見られる」が正しい理解でした。 壁は条件形式ではなく、 監視源として何を選んでいるかにありました。
保持期間と「アラートで評価できる範囲」は別にある
調べる過程で「ログベースメトリクスのデータ保持期間は 6 週間」という仕様もありました。 6 週間 = 42 日なので、 16 日の途絶を見るには十分な長さに見えます。 それなのになぜ 25 時間しか参照できないのか。 保持期間と、 アラートで評価できる範囲は別の概念だからです。
| メトリクスの種類 | データ保持期間 | アラートで評価できる範囲 |
|---|---|---|
| Google Cloud 標準 | 6 週間(一部のサービスは 24 か月) | 最大 2 年 |
| ログベース | 6 週間 | 25 時間 |
| カスタム | 24 か月 | 25 時間 |
最初の直感では、 ログベースメトリクスなら 6 週間分のデータが保存されているので観測期間はクリアできそう、 と考えていました。 実際には アラート条件として設定できる範囲が 25 時間しかない。 Metrics Explorer では 1 か月前のグラフも普通に描けるので、 「グラフに出るのだから条件にも書けるはず」という直感が外れます。 データが残っていることと、 そのデータでアラートを組めることは別問題です。
解決: 標準メトリクスに監視源を移す
やることは単純で、 監視源をアプリのログから Cloud Run Job の標準メトリクスに移します。
absent_over_time(
run_googleapis_com:job_completed_task_attempt_count{
monitored_resource="cloud_run_job",
job_name="my-batch-job",
result="succeeded"
}[17d]
)
| 部分 | 意味 |
|---|---|
run_googleapis_com:job_completed_task_attempt_count |
メトリクス名 |
{ ... } |
ラベルセレクタ。 SQL の WHERE にあたる絞り込み |
[17d] |
レンジセレクタ。 「直近 17 日ぶん」という期間指定 |
17 日は「実行間隔の最大 16 日 + 余裕 1 日」から決めています。 result="succeeded" で絞るのがポイントで、 このメトリクスは成功でも失敗でも出るため、 絞らないと 失敗し続けている状態でもデータは届くので発火しません。
メトリクス名の変換規則
Cloud Monitoring での正式名と PromQL での名前は形が違うので、 機械的に変換します。
ドメインの . を _ に、 ドメインとメトリクス名の区切りの / を : に、 残りの / を _ にします。
run.googleapis.com/job/completed_task_attempt_count ← Cloud Monitoring の名前
run_googleapis_com:job_completed_task_attempt_count ← PromQL での名前
長い期間を見ると評価の頻度が落ちる
PromQL で長い範囲を指定すると、 評価の頻度に下限がかかります。
| クエリの範囲 | 最小(既定)の評価間隔 |
|---|---|
| 25 時間まで | 30 秒 |
| 25 時間 〜 8 日 | 5 分 |
| 8 日 〜 16 日 | 1 時間 |
| 16 日 〜 32 日 | 2 時間 |
| 32 日 〜 93 日 | 6 時間 |
| 93 日 〜 2 年 | 12 時間 |
今回は [17d] なので 2 時間ごと の評価になります。 途絶の検知が最大 2 時間遅れる計算ですが、 半月に 1 回動くバッチの監視としては十分な粒度だと思います。
移してから気づいたこと
アプリのログに依存しなくなった
ログベースメトリクスは「アプリがそのログを出し続けている」ことが暗黙の前提です。 リファクタでイベント名を変えると アラートが無言で死にます。 エラーも警告も出ず、 値が来なくなるだけです。 標準メトリクスにはこの依存がありません。
見ている対象も正確になりました。 ログベース版は「アプリが完了ログを出せたか」しか保証しませんが、 標準メトリクス版は「実行が成功したか」そのものを見ています。
「データが一度も無い」ときの挙動が真逆
同じ欠損検知なのに、 まだ 1 度もデータが存在しない状態での挙動が正反対 でした。
| 条件形式 | データが一度も無いとき |
|---|---|
condition_absent |
発火しない。 「最低 1 回はデータが必要」という仕様のため、 評価が始まらない |
PromQL の absent_over_time
|
発火する。 「その期間にデータが無い」がそのまま真になる |
condition_absent ならバッチを 1 回も動かしていない状態でアラートを作っても静かなだけですが、 absent_over_time に変えると 初回実行より前に作った瞬間から鳴り続けます。 そのため構築手順に「バッチの初回実行を済ませてから監視リソースを作る」という順序を明記しました。 条件形式を変えるときは 初期状態の挙動も確認する ことをおすすめします。
まとめ
-
「データが来ない」は閾値の条件では原理的に検知できない。 そのための
condition_absentは、 不在時間の上限が 23.5 時間 - PromQL 条件に変えても、 ログベースメトリクスとカスタムメトリクスには 25 時間の壁が残る。 制限は「条件の書き方」ではなく 「メトリクスの種類」 側にある。 標準メトリクスなら 最大 2 年
- データの保持期間と、 アラートで評価できる範囲は別物。 ログベースメトリクスは 6 週間保存されるが、 アラートからは 25 時間しか見えない
-
absent_over_timeは データが一度も無い状態でも発火する。condition_absentとは挙動が逆なので、 リソースを作る順序に注意する
今回いちばんの学びは、 制約に当たったとき「それがどのレイヤーに課されているのか」を特定する ことでした。 制約は条件形式ではなくメトリクスの種類の側にあったので、 いくら書き方を工夫しても壁は動きませんでした。
最後に
Cloud Monitoring は「とりあえずログベースメトリクスを作ってアラートを掛ける」使い方でも十分に動いてしまうので、 制約に当たるまで種類の違いを意識しないことが多いのではないかと思います。
もしいま定期バッチの監視を組んでいる方がいたら、 その監視源が標準メトリクスなのかログベースメトリクスなのか を一度確認してみてください。 実行間隔が日次より長いなら、 同じ壁に当たるはずです。 なお本記事の PromQL クエリを使う場合は、 必ず Metrics Explorer でラベル名と値を実機確認してから 適用してください。 ここが違っていると、 クエリは通るのに何も検知しないという最悪の状態になります。
「動いていることを監視する」より「動いていないことを監視する」ほうが難しい。 今回の件で、 そのことを実感として理解できた気がします。