CloudWatch Synthetics Canaryを使用してAPI監視を実現する

# はじめに
本記事はAWS環境でCloudWatch Syntheticsを使用して、API監視を行うための方法について記載しています。

クラウド環境におけるシステム監視を行うためには、クラウドを提供するサービスプロバイダーによってビルトインされたモニタリングシステムや、Datadogなどサードパーティ製ツールを使用するなどの手段があります。

AWSではモニタリングシステムとしてCloudWatchが無料で利用できます。
Syntheticsの監視についてもサードパーティ製ツールを使用せずに、コストを下げてAWSでインテグレーションしたモニタリングシステムを構築できます。

一見便利そうなSyntheticsですが、仕様が分かりにくかったり、痒い所に手が届かない部分もあります。
本記事ではSyntheticsを使ってたナレッジについてまとめました。

## CloudWatch Synthetics
日本ではAPIなどエンドポイントに対する監視のことを外形監視といった表現が多いですが、AWSはDatadogと同じ**Synthetics**と呼び、CloudWatchの**Synthetics Canaries**として提供されています。

![canary.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/74963a89-80aa-b129-d3de-c524833ef89e.png)

CloudWatch SyntheticsではAPIなどエンドポイントに対する**死活監視**及び**障害監視**や、取得したメトリクスを利用した**性能監視**が実現できます。

また、CloudWatch Syntheticsの動作環境は以下の通りです。

- スケジュールに沿って実行可能なスクリプトである**Canary**を作成し、APIなどエンドポイントをモニタリングできます。
- Node.jsまたはPythonで記述されたスクリプトをサポートし、HTTP/TTPSプロトコルの両方で動作します。
- 内部的な仕組みとしてCanaryで作成したスクリプトは、Lambda関数として作成されます。

テンプレートで用意されているCanaryのスクリプトで要件を満たせる場合は、テンプレートでも問題ないと思います。
CloudWatch Syntheticsの仕様を踏まえて、Pythonなどサードパーティライブラリを使用する際は少し工夫が必要です。

例えば、HTTPリクエストに対するResponse timeを条件に判定したい場合は、Requestsモジュールが必要になると思います。

通常、LamdaではLayerファイルを使用してRequestsモジュールなど、サードパーティライブラリが使用できます。
しかし、Canaryで作成したスクリプトを保存すると、関数ではなくレイヤーに保存されます。
そのため、Canaryで作成したスクリプトから別のレイヤーに入っているモジュールを呼び出すことができません。

解決方法としてS3バケットを介してプログラムをインポートすることで、独自に用意したスクリプトからRequestsモジュールなどサードパーティライブラリを使用できます。

その他の振替手段としては、コストと相談した上で[AWS X-Ray](https://aws.amazon.com/jp/xray/pricing/)のサービスを使用して監視することもできます。

## Canaryの作成
本記事ではサードパーティライブラリを含めた独自に用意したスクリプトでAPI監視を行う方法について記載しています。

必要な作業は以下になります。

- [ ] スクリプトの用意
- [ ] S3にアップロード
- [ ] Canaryの作成

### スクリプトの用意
はじめにローカルにてスクリプトを格納するためのフォルダを用意します。

注意点としては、下記ドキュメントの通りにPythonでCanaryを利用している場合、ファイルをZIP形式でパッケージングする際には、**python**フォルダ内に、メインの Python スクリプトが格納されている必要があります。

::: note warn
例えば、pythonhogeなど、python以外の名前のフォルダ名にした場合、正しくCanaryを作成することができません。
:::

- 参考：[Python Canary スクリプトの記述](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_WritingCanary_Python.html)

![スクリーンショット 2022-07-11 19.36.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/6274a3b7-71e9-0995-140d-6445a9f9c92b.png)

サードパーティライブラリを使用したい場合は、ローカルにpipでインストールします。
例えば、requestsモジュールを使用する場合は以下のコマンドを実行します。

`$ mkdir python && cd python && pip3 install -t ./ requests`

<details><summary>出力例</summary>

```console
WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip.
Please see https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue.
To avoid this problem you can invoke Python with '-m pip' instead of running pip directly.
Collecting requests
  Downloading requests-2.28.1-py3-none-any.whl (62 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 62.8/62.8 KB 1.9 MB/s eta 0:00:00
Collecting certifi>=2017.4.17
  Downloading certifi-2022.6.15-py3-none-any.whl (160 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 160.2/160.2 KB 5.3 MB/s eta 0:00:00
Collecting idna<4,>=2.5
  Using cached idna-3.3-py3-none-any.whl (61 kB)
Collecting urllib3<1.27,>=1.21.1
  Downloading urllib3-1.26.10-py2.py3-none-any.whl (139 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 139.2/139.2 KB 4.9 MB/s eta 0:00:00
Collecting charset-normalizer<3,>=2
  Downloading charset_normalizer-2.1.0-py3-none-any.whl (39 kB)
Installing collected packages: urllib3, idna, charset-normalizer, certifi, requests
Successfully installed certifi-2022.6.15 charset-normalizer-2.1.0 idna-3.3 requests-2.28.1 urllib3-1.26.10
WARNING: You are using pip version 22.0.4; however, version 22.1.2 is available.
You should consider upgrading via the '/Library/Developer/CommandLineTools/usr/bin/python3 -m pip install --upgrade pip' command.
```

</details>

上記、コマンド実行後、作成した独自のスクリプトも合わせて格納します。
以下の例では`test_canaries.py`が独自に用意したスクリプトを指します。

また、スクリプトのファイル名は後で設定する際に必要になるため、控えておきます。

```console
drwxr-xr-x   3 foo  staff   96  7 11 19:44 bin
drwxr-xr-x   7 foo  staff  224  7 11 19:44 certifi
drwxr-xr-x   8 foo  staff  256  7 11 19:44 certifi-2022.6.15.dist-info
drwxr-xr-x  14 foo  staff  448  7 11 19:44 charset_normalizer
drwxr-xr-x   9 foo  staff  288  7 11 19:44 charset_normalizer-2.1.0.dist-info
drwxr-xr-x  11 foo  staff  352  7 11 19:44 idna
drwxr-xr-x   8 foo  staff  256  7 11 19:44 idna-3.3.dist-info
drwxr-xr-x  20 foo  staff  640  7 11 19:44 requests
drwxr-xr-x   9 foo  staff  288  7 11 19:44 requests-2.28.1.dist-info
-rw-r--r--   1 foo  staff 1980  7 11 19:51 test_canaries.py
drwxr-xr-x  16 foo  staff  512  7 11 19:44 urllib3
drwxr-xr-x   8 foo  staff  256  7 11 19:44 urllib3-1.26.10.dist-info
```

下準備の最後にファイルをzip化します。Macの場合はファイルを選択して右クリックで圧縮することができます。
上記で説明した通りにパッケージングする際のフォルダ名については制約がありますが、zip化したファイル名については任意のファイル名でも大丈夫です。

![スクリーンショット 2022-07-11 19.54.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/922b37cd-e9eb-736a-87b7-75777e044d6a.png)

### S3にアップロード
S3に任意のバケットを作成して、zip化したファイルをアップロードします。
S3のバケットの作成方法などについては特に要件はないため、割愛します。

### Canaryの作成
CloudWatchの画面からSynthetics Canariesを選択し、「Canaryを作成」を押します。

![スクリーンショット 2022-07-11 19.29.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/7645f2cf-29c4-2a77-813f-662202e1dc0c.png)

用意されているテンプレートを利用する場合は「設計図を使用する」を選択しますが、独自に用意したスクリプトを使用するためには、「S3からインポート」を選択します。

![スクリーンショット 2022-07-11 19.30.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/eab794f5-db25-c282-e1b3-f22b95092037.png)

**Canary ビルダー**の「名前」に、Canaryの名前を入力します。
名前は、21 文字までの小文字、数字、ハイフン、またはアンダースコアで構成され、スペースを含めることはできません。

![スクリーンショット 2022-07-12 19.50.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/f9c4b240-3f33-0f43-c3bd-15e031e05bea.png)

**ソースの場所**の「ランタイムバージョン」は**Node.js**または、**Python**どちらかのランタイムバージョンを選択します。
以下の例では`syn-python-selenium-1.3`を選択しています。Pythonのバージョンは3.8になります。

![スクリーンショット 2022-07-14 19.57.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/4a59b445-353a-c410-9c9c-da5d0f452c41.png)

また、「S3 の場所」にCanary のスクリプトが保存されている S3 の場所 (.zip 形式) を入力します。
「S3 の場所」はS3 URIのことを指し、「Browse S3」から選択することもできます。

![スクリーンショット 2022-07-19 19.34.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/f39942a7-b1e6-59be-25ed-870aa8439191.png)

**環境変数**を利用することで、Canaryを再作成することなく、コードの変更が可能なります。
例えば、URLや監視で使用するしきい値の設定などに活用できます。

- 参考：[Using AWS Lambda environment variables](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime)

![スクリーンショット 2022-07-25 20.08.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/84fa46f8-e6ae-9d05-48b3-cf226a9706b8.png)

環境変数はスクリプトから以下のようにして読み取ることができます。
以下は環境変数`THRESHOLD`を読み込んで、レスポンスタイムを基に判定を行なう例です。
レスポンスタイムがしきい値である3秒以下の場合はCanaryが失敗するため、アラームによってシステムの異常を検知することができます。

```ptyhon
# sample
import requests
import os

response = requests.get(url, headers=headers)
response_time = response.elapsed.total_seconds()

threshold = os.environ['THRESHOLD']

if response_time > float(threshold):
        logger.error("Response Time: %s" % response_time)
        raise Exception("Failed Response Time Over: %s" % response_time)
```

:::note
Pythonは動的型付け言語になり環境変数を読み込んだ際はstr型になるため、数字で判定を行う場合はデータ型を考慮する必要があります。
:::

**Lambda ハンドラー**の「スクリプトエントリポイント」に、実行するメインのプログラムのファイル名を`.handler`形式で入力します。
ファイル名が`test_canaries`の場合は、`test_canaries.handle`と入力します。

![スクリーンショット 2022-07-14 19.58.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/79f713e4-07fc-8511-b661-045b1c16e860.png)

**スケジュール**は実行スケジュールを編集します。
設定した周期で監視する場合は「継続的に実行」を選択します。

![スクリーンショット 2022-07-14 19.58.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/8e948729-55e5-b9b5-a1a7-526a7887dd70.png)

「データ保持」はAWS CLIの`get-canary-runs`コマンドで取得できるデータのことを指します。
モニタリングで表示するためのデータはメトリクスから取得しているため、関連性がありません。
なお、メトリクスの保持期間はCloudWatchのメトリクス仕様を踏まえて、455日(15か月)間保持されます。

![スクリーンショット 2022-07-14 19.59.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/41284f75-418f-148b-e3f5-45b4977604ea.png)

AWS CLIが使用可能な環境にて以下のコマンドを実行することで、上記、データ保持で指定している期間のデータが取得できます。

`$ aws synthetics get-canary-runs --name ＜canary名＞`

- 参考：[get-canary-runs](https://docs.aws.amazon.com/cli/latest/reference/synthetics/get-canary-runs.html)  

**アクセス許可**は「新しいロールを作成」または、既存のロールを選択します。

![スクリーンショット 2022-07-14 19.59.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/eaa744e0-1e67-f530-96bb-c40aee342320.png)

**CloudWatch アラーム - オプション**はアラームで監視するためのメトリクスを指定します。
Canaryの実行が失敗したときに、アラームを鳴らしたい場合、メトリクス名は「失敗」を選択します。

![スクリーンショット 2022-07-14 20.00.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/d1b503d6-ad08-c7fd-0a86-e5e5385694c8.png)

**この Canary の通知を設定する**は、アラームの通知先として使用するトピックを選択します。

![スクリーンショット 2022-07-14 20.00.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/a610abd0-f880-dc91-326e-da5013ae60c7.png)

:::note warn
本記事執筆時点で上記、「CloudWatch アラーム - オプション」についてAWSの不具合が発生しています。アラームは作成されますが、アクションの設定が反映されないため、手動で設定する必要があります。AWSサポートに問い合わせて報告済みですが、まだ修正されていないようです。
:::

最後に「Canay を作成」を押します。

![スクリーンショット 2022-07-19 19.35.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/d292c177-4fb4-72ed-faa2-58c3419c4fc6.png)

Canayの作成処理が始まるのため、この画面のまま待ちます。

![スクリーンショット 2022-07-19 19.39.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/d0fe4a13-0806-a70e-9c7d-aed1cff7f8bf.png)

## Canaryの動作確認
### Canaryの起動
Canayが正常に作成された場合、Canayが起動されます。状態を見ると「起動中」に変化したのが確認できます。

![スクリーンショット 2022-07-19 19.40.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/143d42af-1d7e-1230-d1dc-2e9f76ad93af.png)

Canay起動後、状態は「実行中」に変わります。

![スクリーンショット 2022-07-19 19.40.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/53ffcf4d-fa0b-b024-189b-8bbaa56b9543.png)

作成したCanayの名前を押して、「Availability」より、ログが確認できます。
起動が失敗した場合は問題に表示されるので、出力されるログから調査します。

処理が正常に動いた場合、最新の実行は成功と表示されます。

![スクリーンショット 2022-07-19 19.41.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/cb903223-06e1-62da-b074-f93a64845544.png)

「Configuration」では各設定値が確認できます。
上記、Canaryの作成で記載した通りに、本記事執筆時点で問題が発生しています。
アラームは作成されますが、アクションの設定が反映されないという不具合が発生しています。

![スクリーンショット 2022-07-19 19.42.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/8dbb1793-9f54-4cfc-3a99-7ca20377d882.png)

そのため、アラームのアクションの設定画面から「通知の追加」を押して、通知先を再度設定する必要があります。

![スクリーンショット 2022-07-19 19.43.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/65d8f1f7-9de9-38c2-0839-c22aca6cdea5.png)

### Canaryの停止
Calaryの処理を停止したい場合は、アクションから「中止」を選択します。

![スクリーンショット 2022-07-19 19.44.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/cb2b8ccf-7a70-4f0f-eec8-b72ecb1bc65c.png)

Canayが停止した場合、状態は「停止」に変化します。
停止している状態では課金されません。

![スクリーンショット 2022-07-19 19.47.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/16f0bf41-eda1-45eb-6e5c-bc875c52ed9e.png)

### Canaryの削除
Calaryを削除したい場合は、Calaryを停止した状態でアクションから「削除」を選択します。

![スクリーンショット 2022-07-19 19.48.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/3d0e8c15-7b05-fa9a-2225-8d3de11f3c67.png)

以下、生成されたアラーム、IAMロール及びポリシーが不要な場合は全て選択して、「削除」を押します。

![スクリーンショット 2022-07-19 19.48.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/0ab9832d-1f04-0c86-6ec8-bd017ecc55ef.png)

Deleteを入力して、「確認」を押します。

![スクリーンショット 2022-07-19 19.49.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/e8205d42-10d6-7263-6c8b-7ae40cc704d7.png)

なお、S3に格納されるCanaryの実行ログは削除されないため、不要な場合は手動で削除します。

## モニタリング
「Monitoring」を押すと、Canaryのメトリクスについて確認できます。

![スクリーンショット 2022-07-28 21.18.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/221759/3022af86-6a1c-a502-e56d-a8e9cc4de5e6.png)

:::note
期間（Durationメトリクス）はCanary及びその実行基盤となるLambda関数が起動するまでの時間は含まれてないため、スクリプト処理実行のみに要した合計時間が表示されています。単位はMillisecond（ミリ秒）です。
:::

# おわりに
総括としてサードパーティ製ツールを使用せずに、コストを抑えて監視を実現する場合、CloudWatch Syntheticsは最適なサービスだと思います。

弱みとしてはランタイムのバージョンアップなど、保守作業が発生します。
その他、処理の判定条件を変更したい時に環境変数で対応できない場合は、Canaryの再作成が必要になるため、Datadogなどと比較すると設定変更時の煩わしさがあると思います。

## 参考
- [Amazon CloudWatch の概念](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/cloudwatch_concepts.html)
- [模擬モニタリングの使用](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries.html)
- [CloudWatch metrics published by canaries](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_metrics.html)