👀

[合成モニタリング] Google Cloudでユーザー操作を監視する

2024/12/10に公開

はじめに

こんにちは。クラウドエース第三開発部の工藤です。
Google Cloud の Cloud Monitoring では合成モニタリングという機能を提供しています。
この機能を使用することで一般的に合成監視や外形監視と呼ばれる監視を行うことができます。
本記事では、 Cloud Monitoring の合成モニタリングがどのようなものか、どのように作成するかなど解説していきます。

また、本記事と合わせて、弊社エンジニアによる以下のブログ記事も参照ください。
こちらは preview 段階の記事となりますが、より理解を深めることができるかと思います。

https://zenn.dev/cloud_ace/articles/03f8c188856886

Cloud Monitoring の合成モニタリング

合成モニタリングは外部のネットワークから擬似的なリクエストをサービスに対して行い、アクセスすることによってサービスをユーザー目線で監視することができます。

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

Cloud Monitoring の合成モニタリングでは以下の 2 つの方法を使用して、サービスやアプリケーションの正常性を監視します。

  • 稼働時間チェック
  • 合成モニター

稼働時間チェックはサービスやアプリケーションの URL または Google Cloud のリソースに対してリクエストを発行し、応答するかどうかを確認するサービスです。
今回、稼働時間チェックについての詳細は割愛しますが、こちらは従来から提供されていたサービスであり、合成モニターより手軽に正常性の監視が行えます。

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

合成モニターとは

合成モニターにはユーザ側で行われる一連の操作を想定したリクエストを定期的に発行し、
そのリクエストが成功するかどうかを記録します。
稼働時間チェックと比較すると、よりユーザーが使用する機能に即した監視が可能となります。

作成時には、監視用のコードを用意します。そのコードを Cloud Run functions にデプロイし、指定した頻度で実行することで監視を行います。
実行頻度は 1、5、10、15 分間隔で選択することができ、デフォルトでは最短の 1 分となります。

この際に作成するコードは以下の Synthetic SDK というフレームワークを使用するため、node.js によって動作するコードである必要があります。

https://github.com/GoogleCloudPlatform/synthetics-sdk-nodejs

また、合成モニターはテスト結果とリクエストのレイテンシを記録します。この際のレイテンシ データは Cloud Trace に自動的に収集され、保存されます。

https://cloud.google.com/monitoring/synthetic-monitors/create?hl=ja#overview

サンプルコード

上記の通り、合成モニターにはコードが必要なため、Google Cloud ではサンプルコードを用意しています。
generic テンプレート、Mocha テンプレート、Broken-link テンプレートという 3 つの種類があり、それぞれ以下のようにサンプルコードが用意されています。
作成の際はこれらのサンプルコードを参考にすると良いと思います。

generic テンプレート

サンプル名 概要
generic-synthetic-nodejs JavaScript を使用した最も基本的なサンプル
generic-synthetic-typescript TypeScript を使用したサンプル
generic-puppeteer-nodejs JavaScript のライブラリである Puppeteer を使用したサンプル
generic-selenium-nodejs Selenium WebDriver を使用したサンプル

Mocha テンプレート

サンプル名 概要
mocha-url-ok JavaScript テスト フレームワークである Mocha を使用したサンプル
サンプル名 概要
broken-links-ok Puppeteer を使用した無効なリンクチェックを行う時のサンプル

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

コードを作成する

では、実際にどのようにコードを作成するか解説していきます。
今回は例としてユーザーが書籍情報(ID、タイトル、カテゴリー、著者)の取得、登録、更新、削除といった操作を行うケースを想定し、監視対象は書籍情報を登録する操作のみとします。
(この書籍情報 API は仮想のものです)

ただ、登録操作を対象とする場合、合成モニタリング用のサンプルデータが定期的な実行のたびに登録され続けてしまうことが懸念されます。
毎回、手動で削除を行うのは管理上かなりの負担となるため、コード上で削除 API も呼び出し、合成モニターの実行毎にデータを削除します。

以下が監視用のコードと依存関係を管理する package.json です。
今回は generic-synthetic-nodejs をベースに作成しています。

コード全文
index.js
const {
  instantiateAutoInstrumentation,
  runSyntheticHandler,
} = require("@google-cloud/synthetics-sdk-api");
instantiateAutoInstrumentation();
const functions = require("@google-cloud/functions-framework");
const axios = require("axios");
const assert = require("node:assert");

const baseUrl = "http://xxx.xxx.xx.xxx/"; //APIエンドポイントのベースとなるURL
const RegisterEndpoint = `${baseUrl}api/aaa`; //登録APIエンドポイント
const DeleteEndpoint = (bookId) => `${baseUrl}api/bbb/${bookId}`; //削除APIエンドポイント

const bookData = {
  bookId: "000",
  title: "title-syntheticSample",
  category: "category-syntheticSample",
  author: "author-syntheticSample",
};

functions.http(
  "SyntheticFunction",
  runSyntheticHandler(async () => {
    await registerBookData();
    await deleteBookData(bookData.bookId);

    console.log("Synthetic monitoring test finished");
  })
);

async function registerBookData() {
  try {
    const response = await axios.post(RegisterEndpoint, bookData, {
      headers: {
        "Content-Type": "application/json",
        "X-Synthetic-Monitor": "true",
      },
    });

    assert.strictEqual(
      response.status,
      200,
      `Expected status code 200, but received ${response.status}`
    );

    console.log("Registration successful by synthetic monitoring");
  } catch (error) {
    console.log(`Registration failed: ${error.message}`);

    throw error;
  }
}

async function deleteBookData(bookId) {
  try {
    const response = await axios.delete(DeleteEndpoint(bookId), {
      headers: {
        "X-Synthetic-Monitor": "true",
      },
    });

    assert.strictEqual(
      response.status,
      200,
      `Expected status code 200, but received ${response.status}`
    );

    console.log("Delete successful by synthetic monitoring");
  } catch (error) {
    console.log(`Delete failed: ${error.message}`);
  }
}
package.json
{
  "name": "synthetic-nodejs-sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {
    "@google-cloud/functions-framework": "^3.1.2",
    "@google-cloud/synthetics-sdk-api": "^0.4.1",
    "axios": "^1.5.0"
  },
  "license": "Apache-2.0",
  "author": "Google Inc."
}

初期化

まず、以下のように必要なライブラリやモジュールをインポートして初期設定を行います。

index.js
const {
  instantiateAutoInstrumentation,
  runSyntheticHandler,
} = require("@google-cloud/synthetics-sdk-api");
instantiateAutoInstrumentation();
const functions = require("@google-cloud/functions-framework");
const axios = require("axios");
const assert = require("node:assert");

@google-cloud/synthetics-sdk-api から instantiateAutoInstrumentation 関数と runSyntheticHandler 関数をインポートしています。
それぞれ以下のような役割となっています。

  • instantiateAutoInstrumentation 関数
    • 呼び出すことでログとトレースの収集を有効にできます。
  • runSyntheticHandler 関数
    • 合成モニタリングのハンドラーを実行するために使用します。

https://github.com/GoogleCloudPlatform/synthetics-sdk-nodejs/tree/main/packages/synthetics-sdk-api/src

また、先に説明した通りこのコードは Cloud Run functions で実行されるため、@google-cloud/functions-framework モジュールをインポートする必要があります。
このモジュールによって HTTP リクエストを処理する関数の作成を行います。

メイン関数

以下の箇所では HTTP リクエストを処理する関数を定義しています。

index.js
functions.http(
  "SyntheticFunction",
  runSyntheticHandler(async () => {
    await registerBookData();
    await deleteBookData(bookData.bookId);

    console.log("Synthetic monitoring test finished");
  })
);

最初にインポートした @google-cloud/functions-framework モジュールから http() を使用し、関数の名前と、HTTP リクエストを処理するハンドラ関数を渡します。
ハンドラ関数として runSyntheticHandler を使用し、書籍情報を登録する registerbookData 関数と書籍情報を削除する deleteBookData 関数を呼び出します。

データを登録する関数

以下の箇所では書籍情報を登録する registerbookData 関数の処理を定義しています。

index.js
async function registerBookData() {
  try {
    const response = await axios.post(RegisterEndpoint, bookData, {
      headers: {
        "Content-Type": "application/json",
        "X-Synthetic-Monitor": "true",
      },
    });

    assert.strictEqual(
      response.status,
      200,
      `Expected status code 200, but received ${response.status}`
    );

    console.log("Registration successful by synthetic monitoring");
  } catch (error) {
    console.log(`Registration failed: ${error.message}`);

    throw error;
  }
}

事前に定義していた書籍情報を登録 API のエンドポイントである RegisterEndpoint に対して POST リクエストを送信します。
この際に、axios というライブラリを使用してリクエストを送信します。

https://axios-http.com/ja/docs/intro

このレスポンスが 200 を返すかどうか assert を使用してアサーションを行います。
200 を返さず失敗した場合にはエラーを再スローし、runSyntheticHandler のエラー ハンドリングで処理します。
再スローしてエラーを処理することで、合成モニタリングは失敗となります。

監視用データを削除する関数

以下の箇所では書籍情報を削除する deleteBookData 関数の処理を定義します。

index.js
async function deleteBookData(bookId) {
  try {
    const response = await axios.delete(DeleteEndpoint(bookId), {
      headers: {
        "X-Synthetic-Monitor": "true",
      },
    });

    assert.strictEqual(
      response.status,
      200,
      `Expected status code 200, but received ${response.status}`
    );

    console.log("Delete successful by synthetic monitoring");
  } catch (error) {
    console.log(`Delete failed: ${error.message}`);
  }
}

登録時と同様に axios を使用して、削除 API エンドポイントにリクエストを送ります。
一方で今回の監視対象は登録 API のみのため、削除 API は対象から外れます。
そのためエラーを再スローせずに処理することによって、削除 API でエラーが発生した場合でも合成モニターでは検知せず、正常に終了するようにしています。

合成モニターを作成する

必要なロール

コードを作成したら、合成モニターの作成を行います。
作成時には以下のロールが必要です。

  • モニタリング編集者(roles/monitoring.editor)
  • Cloud Functions デベロッパー(roles/cloudfunctions.developer)

また、トレースデータの収集のためサービス アカウントには Cloud Trace エージェント(roles/cloudtrace.agent)ロールを付与します。
プロジェクトにデフォルトの Compute Engine サービス アカウントが含まれており、それを使用する場合は編集者(roles/editor)のロールが付与されていることを確認してください。

コードのデプロイと合成モニター作成

上記で作成したコードを以下コマンドを使用して Cloud Run functions にデプロイします。

gcloud functions deploy FUNCTION_NAME \
--gen2 --region="asia-northeast1" --source="." \
--entry-point=SyntheticFunction --trigger-http --runtime=nodejs20

コンソールで作成する場合は、合成モニター作成ページの [関数を作成] からインライン エディタに直接コードを入力するか、ZIP ファイルをアップロードすることでデプロイできます。

コードのデプロイが完了したら以下のコマンドで合成モニターを作成します。

gcloud monitoring uptime create DISPLAY_NAME --synthetic-target=TARGET

コンソールの場合は、ページ下部の [作成] をクリックすると作成を行えます。
また、Terraform を使用して作成することも可能です。詳細は以下の公式ドキュメントをご参照ください。
https://cloud.google.com/monitoring/synthetic-monitors/create?hl=ja#create

結果を確認する

作成が完了すると、合成モニターの詳細ページにステータスやレイテンシなどの情報が表示されます。

合成モニターによる監視が失敗した場合は以下のようにステータスが失敗となり、表示されます。このような失敗の際に通知を行うようにアラート作成することができます。
アラートを作成する際は、コンソールで合成モニターのページからオプションを使用してアラート ポリシーを作成することができます。

[成功率]と[現在のステータス]に関する補足

ここで表示されている[成功率]については選択している期間においての割合を表示しています。
そのため、選択している期間では[成功率] 100% だが現時点においては失敗している場合、[成功率]は 100% と表示されていても、[現在のステータス]は失敗と表示されます。
反対に、選択している期間の[成功率]が 100% を切っていても現時点で成功していれば[現在のステータス]には成功と表示されるため、結果を確認する際には少し注意が必要かもしれません。

例えば、以下画像のように選択している 10/30 18:27 ~ 10/31 06:27 の期間は成功していましたが、10/31 18:26 現在では失敗しているため、[成功率]と[現在のステータス]に乖離が発生します。

実行履歴の実行列に表示されているリンク(例 : cc0b7a2e)をクリックすると、コードとログデータが表示されます。

このページの右上にある[View trace details]をクリックするとトレースデータが確認できます。

[Trace で表示]をクリックすると以下のように Cloud Trace からも確認することができます。

まとめ

以上、合成モニターを使用して API を監視する方法を紹介しました。

JavaScript などを使用して、ある程度スクリプトを作成する必要はありますが、そこまで複雑なコードを書かずとも API の監視が行えるかと思います。
また、レイテンシやトレースデータの収集が自動で行える点や簡単にアラートを作成できる点はメリットですね。

本記事を通じて、Google Cloud で合成監視を行いたい方の参考になれば嬉しいです。最後まで閲覧いただきありがとうございました。

Discussion