CloudWatch SyntheticsでオンプレミスのProxyサーバーを経由して監視する

## はじめに
2020/4/23に CloudWatch Synthetics がついに一般利用可能になりました。
アプリケーションのエンドポイントのモニタリングが簡単に実行できるようになります。
基本的な使い方に関しては本記事では割愛しますが、以下の公式Blogが参考になります。

**New – Use CloudWatch Synthetics to Monitor Sites, API Endpoints, Web Workflows, and More**
https://aws.amazon.com/jp/blogs/aws/new-use-cloudwatch-synthetics-to-monitor-sites-api-endpoints-web-workflows-and-more/

## やりたいこと
オンプレミスの Proxy サーバーを経由して CloudWatch Synthetics によるエンドポイント監視を行う。
以下のような止むに止まれぬ事情があっても対応できます。

* VPC とオンプレミス環境を閉域接続している
* VPC 側はプライベートサブネットのみの構成で、NAT Gateway すら使いたくない（使えない）
* インターネットに抜ける口はオンプレミス側の Proxy サーバーのみ

### 参考: CloudWatch Synthetics の VPC 対応
GA に先駆けて 2020/4/16 にプライベートエンドポイント対応がリリースされています。

**Monitor your private internal endpoints 24×7 using CloudWatch Synthetics**
https://aws.amazon.com/jp/blogs/mt/monitor-your-private-endpoints-using-cloudwatch-synthetics/

Canary の実体となる Lambda 関数が VPC 内で起動することによってプライベートサブネットや
オンプレミス内のエンドポイントをモニタリングできるようになっています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/cf17bb34-229c-1dc3-fbf2-096c0d98f62e.png)
(AWS Blogより引用)

また上記のブログでは VPC内で起動する Canary が NAT Gatewayを経由してインターネットアクセスする
例も記載されています。アクセス元がNat Gateway の Elastic IP に固定できるため、
接続元 IP アドレス制限を行っているようなアプリケーションであってもモニタリングできるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/0acf3a65-f8c6-6ee4-23be-23f23c7dbbee.png)
(AWS Blogより引用)

### やりたいことの構成図
VPC 内で起動した Canary の Lambda 関数がオンプレミス側の Proxy サーバーを介して
外部アプリケーションを参照するように設定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/b0a46b53-6d8c-eea5-faa5-e4622a3ed068.png)
これにより VPC 側がプライベートサブネットのみでもアプリケーションエンドポイントを監視できます。
またアクセス元が Proxy サーバーが持つグローバル IP に固定されるため、
接続先アプリケーションで IP アドレス制限を行っている場合でも、Proxy からのアクセスが
許可されていれば 接続先の Firewall 変更等は不要になります。

## 手順
前置きが長くなってしまいましたが、やってみます。
### 前提
さきほどの構成図のように AWS 側がプライベートサブネットのみとなる場合は、Canaryの実行結果を
保存するために CloudWatch および S3 の VPC エンドポイントを予め作成しておく必要があります。
本記事では割愛します。

### Canary の作成
コンソールの Canary を作成から、設計図を使用するを選択します。
設計図はここでは一番単純なハートビートのモニタリングを選択しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/8d595335-3f54-99c6-033d-5b8e52f93441.png)
Canary ビルダーで Canary の名前を設定し、エンドポイントURLには
http://checkip.amazonaws.com を設定します。
AWS が提供する接続元の IP アドレスを表示するだけのサイトです。
それだけなのですが、クライアント側の IP を使用した処理が必要な場合に重宝します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/171cfed7-d207-5446-7b15-a1db08c0d6b4.png)
スクリプトエディタでデフォルトの内容を以下のように編集します。
`http://<ipaddress>:<port>` に Proxy サーバーを指定します。

```diff
var synthetics = require('Synthetics');
const log = require('SyntheticsLogger');

const pageLoadBlueprint = async function () {

    // INSERT URL here
    const URL = "http://checkip.amazonaws.com";
+   const launchOptions = {args: ['--proxy-server=http://<ipaddress>:<port>']};
-   let page = await synthetics.getPage();
+   let page = await synthetics.getPage(launchOptions);   
    const response = await page.goto(URL, {waitUntil: 'domcontentloaded', timeout: 30000});
    //Wait for page to render.
    //Increase or decrease wait time based on endpoint being monitored.
    await page.waitFor(15000);
    await synthetics.takeScreenshot('loaded', 'loaded');
    let pageTitle = await page.title();
    log.info('Page title: ' + pageTitle);
    if (response.status() !== 200) {
        throw "Failed to load page!";
    }
};

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

テスト目的ですので、ここではスケジュールは1回実行、
データ保持、データストレージ、アクセス許可はデフォルトのままとします。アラームも設定しません。

VPC 設定では Canary を起動する VPC、サブネット、セキュリティグループをそれぞれ選択し、
設定を保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/668b1cac-b18a-c691-d3d7-cf49b9887f69.png)
Canary 実行が成功し、保存されたスクリーンショットに Proxy サーバーの IP が表示されていれば OK です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125105/67c2cc25-455b-5aa4-9091-7cfa6460e169.png)

### 解説
Canary の Lambda 関数内で使用されている Synthetics ランタイムでは Puppeteer-core が使用されており
`synthetics.getPage();` では Puppeteer オブジェクトが返ります。
そのため、ドキュメントには詳細の記載はありませんが、Pupppeteer のオプションを渡すように
記述することで Proxy サーバー経由の通信となることが確認できました。
UI Canary でのみ指定可能なことに注意してください。

**Library Functions Available for Canary Scripts**
https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries_Function_Library.html#CloudWatch_Synthetics_Library_getPage

> #### getPage();
Returns the current open page as a Puppeteer object. For more information, see Puppeteer API v1.14.0.

以上です。
参考になれば幸いです。