CloudWatch Syntheticsを使ってみた

## 概要

サービスの外型監視としてroute 53 ヘルスチェックをよく使っていましたが、アクセスはできるもののデータ取得に問題があって、一部のコンテンツが表示できていないなどの不具合を検知できない課題がありました。

それらの不具合を検知するべくAWSのCloudWatch SyntheticsのGUIワークフロービルダーという機能を使ってみました。

さもユーザーがブラウザを使って操作したかのような挙動を自動化することができます。

## **CloudWatch Syntheticsとは**

CloudWatch Syntheticsは、プロダクトのユーザーと同じアクションを実行することで、パーフォーマンスや可用性をモニタリングするためのAWSサービスです。Syntheticsでは、Canaryを設定することで、モニタリングを実現します。Canaryは、設定したスケジュール通りに実行されるスクリプトで、定期的にプロダクトの挙動を確認することができます。

料金は従量課金制で、初期費用や最低利用料金はかかりません。その月に使用した分だけ、月末に支払います。また、1ヶ月あたり100回のCanary実行までは、無料で使うことも可能です。詳しくは、AWS Cloud Watchの[公式ページ](https://aws.amazon.com/jp/cloudwatch/)をご覧ください。

## 使ってみた

まずはCloudWatch Syntheticsにアクセスして作成ボタンをクリック！

本機能はCloudWatchのメニューにあります。
![Canary-list-Synthetics-CloudWatch-ap-northeast-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2981403/b9d7759b-c1c4-b351-fb53-e8582480d417.png)



GUIワークフロービルダーを選択　→　好きな名前を入力　→　監視したいサイトのURLを入力

と進んでいきます。

この例ではナイトレイ社が展開するロケーションビッグデータの分析サービスのログイン画面を初期URLとして指定しています。

サービスの詳細は[こちらのページ](https://cityinsight.nightley.jp/)をご覧ください。

![2_Create-canary-Synthetics-CloudWatch-ap-northeast-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2981403/78c0ee4d-98aa-0e66-5953-3c2e7daab9c3.png)


上段でユーザーをアクションを指定していきます。すると下段のスクリプトエディタに自動的に反映されていきます。表示されているスクリプトは初期状態です。

言語はnodeとpythonのどちらかを選択できるようです。今回はnodeを採用しました。

アクションを設定しなくともスクリプトを直接書いてもいいようです。GUIのアクション定義になくともnodeやpythonがかければより複雑な処理も可能です。

![3_Create-canary-Synthetics-CloudWatch-ap-northeast-1 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2981403/33a9e03d-2a64-628b-51e3-0395db5c6f2b.png)

今回登録したアクションは、emailとパスワードを入力してTOPページへ遷移、指定のコンテンツをクリックして画面遷移、遷移後の画面で指定のコンテンツが表示される。という流れを想定して作りました。

ポイントとしては画面遷移後はセレクターを確認するアクションを都度入れた方がいいです。

このアクションが無いと後述する実行結果のスクリーンショットで確認したい画面がうまく取得されなかったです。

セレクターはidやclassなどを指定します。

複雑な指定をしたい場合は直接スクリプトを書いてしまった方がいいかもしれません。

![4_Create-canary-Synthetics-CloudWatch-ap-northeast-1 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2981403/40b25d68-2fff-dca9-52b3-d4a615fbe57b.png)

出来上がったスクリプトがこちら。一部直接編集してます。

emailとパスワードは適当なものに差し替えています。

初めて使ったので無駄も多いかもしれませんがひとまず意図した挙動になりました。

```python
const synthetics = require('Synthetics');
const log = require('SyntheticsLogger');
const syntheticsConfiguration = synthetics.getConfiguration();

const flowBuilderBlueprint = async function () {
    // INSERT URL here
    let url = "https://cityinsight-app.nightley.jp/";

    syntheticsConfiguration.setConfig({
        includeRequestHeaders: true, // Enable if headers should be displayed in HAR
        includeResponseHeaders: true, // Enable if headers should be displayed in HAR
        restrictedHeaders: [], // Value of these headers will be redacted from logs and reports
        restrictedUrlParameters: [] // Values of these url parameters will be redacted from logs and reports
    });
    let page = await synthetics.getPage();

    // Navigate to the initial url
    await synthetics.executeStep('navigateToUrl', async function (timeoutInMillis = 30000) {
        await page.goto(url, {waitUntil: ['load', 'networkidle0'], timeout: timeoutInMillis});
    });

    // Execute customer steps
    await synthetics.executeStep('input', async function () {
        await page.type("[id='email']", "test@nightley.jp");
    });
    await synthetics.executeStep('input', async function () {
        await page.type("[id='password']", "testtesttest");
    });
    await synthetics.executeStep('click', async function () {
        await page.waitForSelector("[class='el-button button-login el-button--primary']", { timeout: 30000 });
        await page.click("[class='el-button button-login el-button--primary']");
    });
    

    // Execute customer steps
    await synthetics.executeStep('verifySelector', async function () {
        await page.waitForSelector(".el-main.layout-map-main", { timeout: 10000 });
    });

    
    await synthetics.executeStep('clickSecondLink', async function () {
        const elementHandle = await page.$('.el-main.layout-map-main');
        const links = await elementHandle.$$('a');
        if (links.length >= 2) {
            await links[1].click();
        } else {
            log.error("Less than two 'a' tags found within the specified element.");
        }
    });

    // Execute customer steps
    await synthetics.executeStep('verifySelector', async function () {
        await page.waitForSelector(".place-name-link", { timeout: 10000 });
    });

};

exports.handler = async () => {
    return await flowBuilderBlueprint();
};
```

あとは細かい設定をしていきます。

実行スケジュールの指定、データの保持期間、実行結果のスクリーンショットの保存先、アクセス許可、CloudWatchアラームの設定などです。

アクセス許可は特に用意がなければ新規作成でいいと思います。

詳細割愛しますがエラーが出た場合の検知用に、CloudWatchアラームを設定し、SNS→Chatbot→Slack通知という仕組みにしました。

単純に触ってみたいという程度なら画像のようにデフォルトのままでOKです。

![5_Create-canary-Synthetics-CloudWatch-ap-northeast-1 (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2981403/94149450-0418-3609-912f-bcc65dcb7ab9.png)

![6_Create-canary-Synthetics-CloudWatch-ap-northeast-1 (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2981403/6f9132aa-ea30-5c7f-79b7-1c284637f116.png)

![7_Create-canary-Synthetics-CloudWatch-ap-northeast-1 (6).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2981403/6120e8f0-2846-eaea-6247-da407190806f.png)


あとは作成ボタンをクリックして実行結果を待ちましょう。

うまくいきました。スクリーンショットも取られています。

![8_Canary-details-Synthetics-CloudWatch-us-east-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2981403/b0610e09-9442-8b5d-4327-0d672951f721.png)

![9_Canary-details-Synthetics-CloudWatch-us-east-1 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2981403/65c22443-7bf5-be3f-f139-3729107dcbdb.png)



地方別ランキングのコンテンツが取得できていることを無事確認できました。

## 感想

画面上の説明がわかりづらくて最初はとっつきにくさを感じましたが、慣れると簡単でした。

料金もお手頃ですし、単純な外型監視だけでは対応できない場合は非常に有用と感じました。

CloudWatch SyntheticsにはGUIワークフロービルダー以外にも機能があるので、次回試してみたいと思います。