GCPで毎日特定の時間に起こるアラートをCloud Run Jobsでスヌーズする

# TL;DR

GCPでCloud Monitoringのアラートポリシーを設定してシステム監視を行っているものとします。
このポリシーについて、毎日特定の時間帯においてはアラートを無視したい、というシチュエーションを考えます。

実現方法として、[Cloud Monitoringのスヌーズ機能](https://cloud.google.com/monitoring/alerts/manage-snooze)[^1]を使います。
また、スヌーズの期間を毎日繰り返し設定するため、[Cloud Run Jobs](https://cloud.google.com/run/docs/create-jobs)を使います。

[^1]: 2023-04-15現在、スヌーズ機能は正式リリースされておらず、プレビューの段階

# スヌーズ機能について
Cloud Monitoringでは、アラートポリシーに対してスヌーズの設定をすることができます。
これによって、設定した期間でのインシデントの発生や通知を抑止することができます。


典型的なユースケースとしては、システムのメンテナンスなどの際にアラートの発火を抑制する、といった使い方が考えられるでしょう。

ただし、このスヌーズ機能単体では、夜間など特定の時間帯に繰り返しスヌーズを設定することはできないようです。

とはいえ、APIや `gcloud` コマンドでスヌーズ設定の操作が可能なので、そういった状況に対しても様々な方法でスヌーズを設定できるでしょう。

## gcloudコマンドによるスヌーズの設定

[公式ドキュメント](https://cloud.google.com/monitoring/alerts/manage-snooze)の通りですが、ある日の夜間に特定のアラートポリシーの監視をスヌーズする設定を `gcloud` コマンドを用いて作成する例を下に示します。

```sh:gcloudコマンド
gcloud alpha monitoring snoozes create --display-name="Snooze: Monitoring A & B at night 2023-04-14" \
    --criteria-policies="projects/my-project/alertPolicies/12345,projects/my-project/alertPolicies/23451" \
    --start-time="2023-04-14T22:00:00+0900" \
    --end-time="2023-04-15T08:59:59+0900"
```

以降ではこのコマンドをベースに、Cloud Run Jobsによって、毎晩のスヌーズ設定を毎日自動的に作成していきます。

# Cloud Run Jobsによるスクリプトの定期実行
GCPで定期的なジョブを実行する方法はいくつかありますが、今回は[Cloud Run Jobs](https://cloud.google.com/run/docs/create-jobs)を使ってみます。

## 下準備
### 毎日実行するシェルスクリプトを作成
下準備として、上に示した `gcloud` コマンドを実行するシェルスクリプトを、毎日実行して問題ない形に書き換える必要があります。

ここでは、次のようなスクリプトを作成します。

```sh:script.sh
#!/usr/bin/env bash

set -euo pipefail

PROJECT_ID=my-project
ALERT_POLICY_NUMBERS=(
  12345  # Alert Policy for A
  23451  # Alert Policy for B
)

policies=()
for num in ${ALERT_POLICY_NUMBERS[@]}; do
  policies+=("projects/${PROJECT_ID}/alertPolicies/${num}")
done
# 配列を `,` で文字列結合
criteria="$(IFS=,; echo "${policies[*]}")"

# TimeZoneをJSTに変更
ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

# スヌーズ作成
gcloud alpha monitoring snoozes create \
    --display-name="Snooze: Monitoring A & B at night $(date +%F)" \
    --criteria-policies="${criteria}" \
    --start-time="$(date +%F)T22:00:00+0900" \
    --end-time="$(date +%F -d tomorrow)T08:59:59+0900"

# 最新3件のスヌーズ設定を表示
gcloud alpha monitoring snoozes list --sort-by="~interval.end_time" --limit 3
```

補足:

* この後で利用するコンテナイメージのデフォルトのタイムゾーンがUTCですが、日本で使われることを想定してJSTに設定しています

### シェルスクリプトをSecret Managerに登録
このあと作成するCloud Run JobのPodから上で作成した `script.sh` をボリュームとしてマウントするため、Secret Managerに登録します[^2]。

[^2]: Cloud RunではConfig Mapがサポートされておらず、代替としてSecretを介してSecret Managerを利用する方法が示されています。 https://cloud.google.com/run/docs/migrate/from-kubernetes#config-maps

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168843/81708917-04e0-a9d8-b290-9181b3f6ce11.png)

シークレットの名前はなんでも構いませんが、ここでは `script-daily-gcloud-monitoring-snooze` としています。
その他の設定はデフォルトのままで構いません。

### サービスアカウント権限の設定
ジョブ実行時に作成したシークレットにアクセスするため、サービスアカウントに権限設定が必要です。
ここでは簡単のため、Cloud Run実行時のデフォルトである、Compute Engineのデフォルトサービスアカウントに対して権限を設定します[^3]。

[^3]: 現場で導入する際は、このジョブを実行する専用のサービスアカウントを作成する方がベターです。

Secret Managerの画面で先ほど作成したシークレットをチェックして右ペインにINFO PANELを表示し、「ADD PRINCIPAL」から権限を追加します[^4]。

[^4]: 筆者はGCPコンソールを英語で利用しているため、日本語で利用している方は適宜読み替えて下さい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168843/7cace813-f9a7-78e2-71c3-4303981f8135.png)

「New principals」の入力欄に「compute」と入力すると、Compute Engineのデフォルトサービスアカウントが候補として表れるので、これを選びます。
権限は、「Secret Manager Secret Accessor」を選択します。

以上で下準備は終わりです。

## ジョブの作成
Cloud Runでジョブを作成する方法はいくつかあります[^5]が、ここではジョブの構成をYAMLファイルで記し、それを元に作成します。

[^5]: https://cloud.google.com/run/docs/create-jobs

```YAML:job-daily-gcloud-monitoring-snooze.yml
apiVersion: run.googleapis.com/v1
kind: Job
metadata:
  name: daily-gcloud-monitoring-snooze
  labels:
    # ジョブを実行するGCPリージョンを指定
    cloud.googleapis.com/location: asia-northeast1
spec:
  template:
    spec:
      template:
        spec:
          containers:
            - name: daily-gcloud-monitoring-snooze
              # gcloudコマンドを使うため、Cloud SDKのイメージを使う
              image: google/cloud-sdk
              # bashでシェルスクリプトを実行する
              command: ["/bin/bash", "-x"]
              args:
                - "/secrets/script.sh"
              # ボリュームを /secrets/ にマウント
              volumeMounts:
                - name: script
                  mountPath: /secrets/
          # Secret Managerに登録したスクリプトをボリュームとして使用する
          volumes:
            - name: script
              secret:
                items:
                - key: latest  # 常に最新バージョンのSecretを参照する
                  path: script.sh
                secretName: script-daily-gcloud-monitoring-snooze
```

次のコマンドを実行し、ジョブをデプロイします。

```sh
gcloud run jobs replace job-daily-gcloud-monitoring-snooze.yml
```

## ジョブの実行

GCPコンソールからジョブを実行することができます。
ジョブの詳細画面で「EXECUTE」ボタンを押すことでジョブを実行できます。

![cloud-run-job-execution.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168843/7ec73599-2d58-bc95-0a18-3daadf2e397a.png)

## ジョブに定期実行トリガーを設定する

前掲のジョブ詳細画面で、「TRIGGERS」タブに遷移すると、ジョブを定期的に実行するためのトリガーを作成することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168843/30501ad5-cb77-fffb-ced8-c1471d5e450f.png)

「ADD SCHEDULER TRIGGER」リンクを押下し、スケジュール設定に進みます。
Cloud Scheduler APIを有効にしていない場合、ここで有効化する必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168843/32ce4d99-c96d-080e-228d-dc9b52a83286.png)

スケジュールはunix-cron形式[^6]で記述します。
ここでは、毎日13時にジョブを実行するように設定しました。

[^6]: https://man7.org/linux/man-pages/man5/crontab.5.html

以上で、スヌーズの繰り返し設定を自動化することができました。

# Terraformでコード化する

これまでの手順で作成したSecret Managerのシークレット、Cloud Runジョブ、及びSchedulerによるスケジュール設定を一括してTerraformで作成できるようなコードを書きました。

https://github.com/progrhyme/sample-terraform-gcp-run-job-snooze-alert

`script.sh` など設定値をベタ書きしている箇所があるため、汎用的に使えるモジュールにするにはもう少し工夫が必要ですが、本稿のような設定をTerraformで管理したいときには参考にできるかと思います。

なお、本稿で示した手順とこのTerraformコードの違いとして、サービスアカウントの設定があります。
上の手順ではCompute Engineのデフォルトサービスアカウントを利用していましたが、Terraformコードの方では専用のサービスアカウントを作成しています。

# まとめと所感

本稿では、GCP Cloud Monitoringのアラートに定期的なスヌーズを設定するために、Cloud Run Jobsを利用しました。

例えば、「夜間だけ」「週末だけ」のような繰り返し現れる特定の期間にノイジーなアラートが発生しているとき、今回示した方法が対応として使えるでしょう。

個人的にCloud Run自体にあまり親しみがなかったのですが、今回のようにちょっとしたスクリプトを定期実行したいようなときに、Cloud Run Jobsは有力な選択肢になりそうです。
今回構成した組み合わせは、他にも色々と応用が効きそうですね。