[AWS] CloudWatch SyntheticsでVPC内外からのサービス監視を試す

CloudWatch Syntheticsはシンセティック監視(合成監視)を提供するサービスです。アプリケーションエンドポイント(REST API、URL、ウェブサイトコンテンツ等)の死活監視に利用することができますが、スクリプトを実装することで、Syntheticsの名の通り複雑なワークロード(UIアセットのロード、トランザクション、レイテンシー、ウィザードのフロー、途切れたリンク等)を継続的に検証できるようになります。

今回はそのCloudWatch Syntheticsを使用し、VPC内に構築したWebサイトをVPCの内側と外側から監視することを試してみました。

<b>試した構成：</b>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/c406ab0a-f6c2-e447-2962-1bc8104280b0.png)

- 監視対象はQiita似のOSS [Knowledge](https://information-knowledge.support-project.org/ja/) で試しました。
- CloudWatch Syntheticsで、KnowledgeのパブリックIP、プライベートをIPをそれぞれ指定して監視を行います。
- 図中のPublic IP(1)がKnowledgeのパブリックIP、Public IP(2)がNAT GatewayのIP(Knowledgeでアクセス許可するIP)を意味しています。
- 実際の監視設定は、CloudWatch SyntheticsでCanary(カナリア)と呼ばれるオブジェクトを作成して行います。

# 1．VPCの準備

- 構成図のように、パブリックサブネットとプライベートサブネットを持つVPCを作成します。
- 詳細の作成手順は省略しますが、試すだけならVPCウィザードの「パブリックとプライベートサブネットを持つVPC」そのままで大丈夫です。
- ポイントは、Canaryの実体がLambda Functionなので、VPC内での動作についてLambdaと同じ制約を受ける点です。具体的にはプライベートサブネットで動かす必要があるため、そのためにサブネット2つの構成になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/602fd5fd-b0a0-f074-abe6-01288662a90b.png)


# 2．knowledge構築

今回は監視対象として(動きのあるWebサイトなら何でもいいのですが、)Qiita似OSSのKnowledgeを試す機会があったので利用しました。
ここは手をかけるところではないので、EC2上で[Dockerhubのパブリックイメージ](https://hub.docker.com/r/koda/docker-knowledge)をそのまま動作させて試しました。

```shell
# docker run -d -p 8080:8080 koda/docker-knowledge:latest
```

起動後、テスト用の記事を登録しておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/d595d201-4b62-c2cb-7fce-30b1fd9b285b.png)

# 3．CloudWatch Synthetics(Canary)作成

ここからが本題です。コンソールでCloudWatch Syntheticsから「Canaryを作成」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/6f9562f1-3855-97c3-2d5f-75a7568b6b1a.png)

作成方法を指定します。最初に「GUIワークフロービルダー」を試してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/23d18b87-09e7-9288-ff17-d36be7ad6fec.png)

画面の下に行きランタイムを選択します。現状nodejsとpythonが選択できますが、今回はpythonを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/85514465-6bef-0a62-f3ac-04120b2bc384.png)

引き続き同じ画面の「ワークフロービルダー」でアクションを設定します。現状は以下のアクションが選択可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/9fdc1f23-572d-f3d5-3f2f-60b512e92af1.png)

例として、以下は検索ボックスに検索キーワードを入力する例です。GUIでセレクターとテキストを入力すると、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/10af5356-93d9-b6af-57f3-9d4c9a4300c4.png)

やはり同じ画面の「スクリプトエディタ」に下記のコードが自動生成されます。

```
# Execute customer steps
def customer_actions_1():
    browser.find_element_by_xpath("//input[@id='navSearch'][contains(text(),'test')]")

await syn_webdriver.execute_step('verifyText', customer_actions_1)
```

同様に、「検索」ボタンを押下する例で、アクション：クリックを選択してセレクターを入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/911c418a-a9e1-0d28-f076-3db203ec8526.png)

このKnowledgeの例ではid属性もname属性も無かったため、無理矢理classで検索しています。自動生成されるコードは以下です。

```
# Execute customer steps
def customer_actions_1():
    browser.find_element_by_xpath("//button[@class='btn-search']").click()

await syn_webdriver.execute_step('click', customer_actions_1)
```

今回は最終的に以下のコードを作成しました。内容としてはコメントにある通り、TopPageにアクセス → 検索ボックスに"test"を入力して検索 → 検索結果に"test"という記事があるか確認、という処理になっています。

```
import asyncio
from aws_synthetics.selenium import synthetics_webdriver as syn_webdriver
from aws_synthetics.common import synthetics_logger as logger, synthetics_configuration

TIMEOUT = 60

async def main():
    private_url = "http://10.0.0.13:8080/open.knowledge/list"
    public_url = "http://xx.xx.xx.xx:8080/open.knowledge/list"
    browser = syn_webdriver.Chrome()

    url = private_url # ここのみCanaryごとに編集

    # synthetics設定(スクリーンショットの取得)
    synthetics_configuration.set_config({
       "screenshot_on_step_start" : True,
       "screenshot_on_step_success": True,
       "screenshot_on_step_failure": True
    });

    # TopPageにアクセス
    def navigate_to_toppage():
        browser.implicitly_wait(TIMEOUT)
        browser.get(url)

    await syn_webdriver.execute_step("navigateToTopPage", navigate_to_toppage)

    # 検索ボックスにキーワード入力して検索
    def input_search_word_and_search():
        browser.find_element_by_xpath("//input[@id='navSearch']").send_keys("test")
        browser.find_element_by_xpath("//button[@class='btn btn-default']").click()

    await syn_webdriver.execute_step('inputSearchWordAndSearch', input_search_word_and_search)

    # 記事があるか確認
    def check_search_result():
        browser.find_element_by_xpath("//div[@class='list-title'][contains(.,'test')]")

    await syn_webdriver.execute_step('checkSearchResult', check_search_result)

    logger.info("Canary successfully executed")

async def handler(event, context):
    # user defined log statements using synthetics_logger
    logger.info("Selenium Python workflow canary")
    return await main()
```

後は

- S3バケット: 処理結果(スクリーンショット等)を保存するS3バケットを指定
- IAMロール: CloudWatchSyntheticsRole-canary-name-uuid という名前の新しいロールを作成するか、既存のロールを指定
- VPC: 事前に準備したVPC、プライベートサブネットを指定
- 定期実行かどうか

等設定して保存すると、Canaryが実行されます。なお、現状はGUIワークフロービルダーは新規作成時にしか使えず、一度作成した後はスクリプトエディタで直接スクリプトを編集していくようになります。

# 4．テストと結果の確認

テストの結果は、下記スクリーンショットのように表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/c78cc5ba-faf1-65d7-ce8c-400571baa1e1.png)

プライベートIP指定でのテスト結果を見てみます。実行の詳細に行くと、各ステップでの結果と、スクリーンショットが保存されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/78d9eacf-fbe8-48ec-97a2-0490380f40ee.png)

S3に保存されているスクリーンショットを確認すると、検索処理がされていることが確認できます。(アドレスバーが無いので結果は全く同じですが)パブリックIP指定の実行結果も同様に確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/92a79c43-3e74-e7e8-81d8-1426b8622ef7.png)

## パブリックアクセスのみブロックしてみる

テストとして、EC2セキュリティグループを変更して、パブリックIPでのアクセスを拒否してみると、以下のようにエラーが発生しました。保存されていたスクリーンショットは白一色の画像になっていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/5f510033-3d19-3db7-da80-1d1f9a75da55.png)

全体の確認結果を見ると、プライベートIP指定の方は成功していることが確認できます。両者はURLのみ変えて同じスクリプトを実行しているので、この状況ですとネットワーク経路に問題があるのではないかと推測できるかと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172826/6a707fba-fdcb-775c-3c00-0cdce7231753.png)

今回の検証結果は以上になります。

# 参考資料

- [AWS運用監視の実践～システムに問題が発生した時、迅速に対処する方法～](https://d1.awsstatic.com/events/jp/2020/innovate/pdf/S-8_AWSInnovate_Online_Conference_2020_Spring_management_monitoring.pdf)
- [「外形監視」という訳語の間違い](https://takehora.hatenadiary.jp/entry/2019/07/05/012036)
- [Canary を作成する](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_Create.html)
- [VPC で Canary を実行する](https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_VPC.html)
- [Knowledge](https://information-knowledge.support-project.org/ja/)
- [Dockerhub: koda/docker-knowledge](https://hub.docker.com/r/koda/docker-knowledge)