Google Cloud において認証が必要な Compute プロダクトの死活監視をする方法

本記事は [GCP(Google Cloud Platform) Advent Calendar 2023](https://qiita.com/advent-calendar/2023/gcp) の 11 日目の記事です．

昨年までは，機械学習で研究をしている大学 4 年生でしたが，2023 年 4 月からは新卒でエンジニアとして働いています．
現在は，MLOps エンジニア[^mlops]をしています．
[^mlops]: ML（Machine Learning）を活用したプロダクトを世に送り出すために必要となる，モデル開発以外のすべてのエンジニアリングを行うエンジニアを指す

昨年は 『[数独・ナンプレを画像から解く AI を作る](https://qiita.com/a5chin/items/6d35283a54a1022f9b24)』という記事を書いたので，興味があればぜひご覧ください．

https://qiita.com/a5chin/items/6d35283a54a1022f9b24

# 1. 結論
Google Cloud の機能である Synthetic Monitoring（合成モニタリング）を使うことで認証付きの Cloud Run に対して死活監視[^monitoring]をすることができました．
Synthecic Monitoring は Cloud Monitoring と JavaScript で動かす Cloud Functions の計 2 つを使用します．
[^monitoring]: サーバーやその上で実行されているソフトウェアの稼働状況を継続的にチェックすることを指す

<img width="100%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2972982/2e98ff3f-1f0e-c48e-a222-08ac22f5873c.png">

:::note info
2023\/12 現在，Synthetic Monitoring に用いる Cloud Functions には JavaScript のみ対応です．
:::


実際に，上記アーキテクチャをもとに Terraform[^terraform]を用いて実装してみたので，ぜひご覧ください．
再現コマンドも記載しています↓
[^terraform]:  Infrastructure as Code（IaC）と呼ばれるツールの一種でインフラの構成をソースコードとして管理できる

https://github.com/a5chin/synthetic-sample

## 1.1. Synthetic Monitoring とは

https://cloud.google.com/monitoring/uptime-checks/introduction?hl=ja

> 合成モニターを使用すると、テスト内容と一連のテストを定義できます。たとえば、アプリケーションのログインページ、e コマースストアのチェックアウト プロセス、アプリケーションがサードパーティ サービスに対して行う API 呼び出しをテストできます。

Synthetic Monitoring は合成モニタリングと訳すようです．
上記ドキュメントによると，Synthetic Monitoring とは，テスト内容をカスタムした Google Cloud の Uptime Check（死活監視[^monitoring]）をできるということです．

# 2. 導入
Google Cloud が提供している Compute プロダクト[^product]は以下の 5 種類存在します．
[^product]: 長谷部 光治. [アプリケーションはどこで動かすべきか - 2022 春](https://lp.cloudplatformonline.com/rs/808-GJW-314/images/App_Modernization_OnAir_q1_0301_Session1.pdf)

- Compute Engine
- Google Kubernetes Engine
- **Cloud Run** ← 本記事ではこのプロダクトに焦点を当てて死活監視[^monitoring]をします
- App Engine
- Cloud Functions

<img width="100%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2972982/06ec4c35-5647-b272-e13e-b480014eaf9e.png">

## 2.1. 要件を満たさなかったアーキテクチャ
実務において，認証が必要な Cloud Run に対して死活監視[^monitoring]が求められました．
しかし，以下は認証がない Cloud Run に対しての死活監視[^monitoring]アーキテクチャの例です．

認証が必要な Cloud Run に対する死活監視[^monitoring]のベストプラクティスに関する情報がドキュメントに明示されていないため，本記事ではその手法を紹介します．
<img width="100%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2972982/848cd0c3-c75c-53ac-d761-48666a60a0d6.png">

:::note alert
上図は認証が必要ない Cloud Run のヘルスチェック時に用いるアーキテクチャで，認証が必要な Cloud Run のヘルスチェックができません．
:::

# 3. 実装
以下の公式ドキュメントを参考に，実装をしていきます．
Synthetic Monitoring を実装する上で，Cloud Functions に対応しているのは`Node.js`, `TypeScript`, `Mocha`の 3 種類ありますが，今回は`Node.js`を使って実装していきます．

https://cloud.google.com/monitoring/synthetic-monitors/samples?hl=ja

## 3.1. 方針
<img width="100%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2972982/2e98ff3f-1f0e-c48e-a222-08ac22f5873c.png">

先に図示した，上図アーキテクチャを作成するにあたり，以下の技術を使って認証が必要な Cloud Run に対して死活監視[^monitoring]を実装します．

- Terraform[^terraform]
    - Cloud Run
    - Cloud Functions
    - Cloud Monitoring
        - Uptime Check (Synthetic Monitoring)
- JavaScript

## 3.2. 階層構造
Terraform[^terraform]のベストプラクティスに則って，以下の様な`environments`, `modules`に階層を分ける構成にして実装していきます．

https://github.com/a5chin/synthetic-sample

上記のリポジトリにおける，`tree`コマンドの出力を下記に示します．
```
.
├── environments
│   └── dev
│       ├── backend.tf
│       ├── main.tf
│       ├── provider.tf
│       ├── variables.tf
│       └── versions.tf
└── modules
    ├── main.tf
    ├── monitoring
    │   ├── bucket.tf
    │   ├── dashboard.tf
    │   ├── fuctions.tf
    │   ├── healthcheck.tf
    │   ├── policy.tf
    │   ├── src
    │   │   ├── index.js
    │   │   └── package.json
    │   └── variables.tf
    ├── run.tf
    ├── sa.tf
    └── variables.tf
```
## 3.3. ソースコード
https://github.com/a5chin/synthetic-sample

以下に主なソースコードを説明付きで，上記サンプルリポジトリから抜粋します．

```js:index.js
const functions = require('@google-cloud/functions-framework');
const fetch = require('node-fetch');
const { runSyntheticHandler } = require('@google-cloud/synthetics-sdk-api');
const assert = require('node:assert');
const { GoogleAuth } = require('google-auth-library');
const dotenv = require('dotenv').config();

// エントリーポイント
functions.http('SyntheticFunction', runSyntheticHandler(async () => {
  const auth = new GoogleAuth();

  // 環境変数から死活監視をする URL を受け取る
  const cloudRunUrl = process.env.BASE_URL;
  const reqUrl = new URL("/api/v1/healthz", cloudRunUrl);
  
  const client = await auth.getIdTokenClient(reqUrl);
  const resp = client.request({url: reqUrl});
  const status = (await resp).status

  // status が 200 の時のみ healthy とする
  return await assert.equal(status, 200);
}));
```

```terraform:modules/monitoring/fuctions.tf
resource "google_cloudfunctions2_function" "synthetic_monitoring" {
  name     = var.synthetic_settings.functions.name

  location = var.location
  project  = var.project_id

  build_config {
    runtime     = "nodejs20"

    # エントリーポイント
    entry_point = "SyntheticFunction"

    # Cloud Functions のソースコード置き場を定義する
    source {
      storage_source {
        bucket = google_storage_bucket_object.main.bucket
        object = google_storage_bucket_object.main.name
      }
    }
  }

  service_config {
    max_instance_count    = var.synthetic_settings.functions.max_instance_count
    available_memory      = var.synthetic_settings.functions.memory
    timeout_seconds       = var.synthetic_settings.functions.timeout
    service_account_email = var.run_info.invoker

    # 環境変数に死活監視対象の URI を渡す
    environment_variables = {
      BASE_URL = var.run_info.uri
    }
  }
}
```

```terraform:modules/monitoring/healthcheck.tf
resource "google_monitoring_uptime_check_config" "synthetic_monitoring" {
  display_name = var.synthetic_settings.monitoring.name

  # どのくらいの頻度で死活監視をするか
  period  = var.synthetic_settings.monitoring.period

  # タイムアウトの時間を指定
  timeout = var.synthetic_settings.monitoring.timeout

  synthetic_monitor {
    cloud_function_v2 {
      # Clodu Run にアクセスする Cloud Functions の ID
      name = google_cloudfunctions2_function.synthetic_monitoring.id
    }
  }
}

```

# 4. 結果
以下に，構築したリソースをコンソールで表示して期待の動作をしているか確認してみます．

## 4.1. Cloud Run
Authentication が Require authentication となっており，API の使用には認証が必要であることがわかります．
<img width="100%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2972982/6e2f8e33-66ca-fa66-41af-595f8e31275e.png">

## 4.2. Cloud Monitoring
Cloud Monitoring でアラートを出すためのしきい値を決めることができます．
今回は 1 分間に 1 回でもアラートがあれば Slack に通知を出すように設定をしています．
<img width="100%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2972982/f6bfe907-1260-cc05-85b4-707415d00711.png">

## 4.3. Synthetic Monitoring
ヘルスチェックが通っており，全てのリソースが想定通りに動いていることが確認できました．
<img width="100%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2972982/5d9772ad-8aa4-3fe2-1776-145e17c3d470.png">