

# Puppeteer ランタイムを使用した Node.js Canary スクリプトの記述
<a name="CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs_Pup"></a>

**Topics**
+ [CloudWatch Synthetics Canary を最初から作成する](#CloudWatch_Synthetics_Canaries_write_from_scratch)
+ [Node.js Canary ファイルのパッケージング](#CloudWatch_Synthetics_Canaries_package)
+ [既存の Puppeteer スクリプトを変更して Synthetics の Canary として使用する](#CloudWatch_Synthetics_Canaries_modify_puppeteer_script)
+ [環境変数](#CloudWatch_Synthetics_Environment_Variables)
+ [Canary と他の AWS のサービスとの統合](#CloudWatch_Synthetics_Canaries_AWS_integrate)
+ [Canary に静的 IP アドレスの使用を強制する](#CloudWatch_Synthetics_Canaries_staticIP)

## CloudWatch Synthetics Canary を最初から作成する
<a name="CloudWatch_Synthetics_Canaries_write_from_scratch"></a>

次の例は、Synthetics の最小の Canary スクリプトを示しています。このスクリプトは、合格して正常な実行となり、文字列を返します。不合格となる Canary の例を確認するには、`let fail = false;` を `let fail = true;` に変更します。

Canary スクリプトのエントリポイント関数を定義する必要があります。Canary の `ArtifactS3Location` として指定した先の Amazon S3 にファイルがアップロードされる方法を確認するには、これらのファイルを `/tmp` フォルダに作成します。Canary アーティファクトは書き込み可能な唯一のディレクトリであるため、すべて `/tmp` に保存する必要があります。スクリプトによって作成されたスクリーンショットやその他のファイルについては、スクリーンショットパスが `/tmp` に設定されていることを確認してください。Synthetics は、` /tmp` のファイルを S3 バケットに自動的にアップロードします。

```
/tmp/<name>
```

スクリプトを実行すると、合格/不合格のステータスと所要時間のメトリクスが CloudWatch に発行され、`/tmp` の下のファイルが S3 にアップロードされます。

```
const basicCustomEntryPoint = async function () {

    // Insert your code here

    // Perform multi-step pass/fail check

    // Log decisions made and results to /tmp

    // Be sure to wait for all your code paths to complete 
    // before returning control back to Synthetics.
    // In that way, your canary will not finish and report success
    // before your code has finished executing

    // Throw to fail, return to succeed
    let fail = false;
    if (fail) {
        throw "Failed basicCanary check.";
    }

    return "Successfully completed basicCanary checks.";
};

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

次に、AWS SDK を使用して、Synthetics のログ記録を使用して呼び出しを行うようにスクリプトが拡張されます。デモのために、このスクリプトは Amazon DynamoDB クライアントを作成し、DynamoDB listTables API を呼び出します。リクエストに対するレスポンスを記録し、リクエストが成功したかどうかに応じて合格または不合格を記録します。

```
const log = require('@aws/synthetics-logger');
const AWS = require('aws-sdk');
// Require any dependencies that your script needs
// Bundle additional files and dependencies into a .zip file with folder structure
// nodejs/node_modules/additional files and folders

const basicCustomEntryPoint = async function () {

    log.info("Starting DynamoDB:listTables canary.");
    
    let dynamodb = new AWS.DynamoDB();
    var params = {};
    let request = await dynamodb.listTables(params);
    try {
        let response = await request.promise();
        log.info("listTables response: " + JSON.stringify(response));
    } catch (err) {
        log.error("listTables error: " + JSON.stringify(err), err.stack);
        throw err;
    }

    return "Successfully completed DynamoDB:listTables canary.";
};

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

## Node.js Canary ファイルのパッケージング
<a name="CloudWatch_Synthetics_Canaries_package"></a>

 **syn-nodejs-puppeteer-11.0 以降の場合** 

 古いパッケージング構造 (syn-nodejs-puppeteer-10.0 以前) は、新しいバージョンでも引き続きサポートされています。

以下のいずれかのオプションを使用してスクリプトを作成します。
+ .js ファイル (CommonJS 構文)
+ .mjs ファイル (ES モジュール構文)

ES モジュールの場合は、以下のいずれかのオプションを使用します。
+ .js ファイル (CommonJS 構文)
+ .mjs ファイル (ES モジュール構文)

パッケージ構造は以下のとおりです。
+ ルートレベルのハンドラーファイル (index.js/index.mjs)
+ オプションの設定ファイル (synthetics.json)
+ node\$1modules の追加依存関係 (必要な場合)

パッケージング構造の例:

```
  my_function/
├── index.mjs
├── synthetics.json
├── helper-utils.mjs
└── node_modules/
    └── dependencies
```

パッケージ化するには、以下のステップに従います。

1. 依存関係 (存在する場合) をインストールします。

   ```
   npm install
   ```

1. .zip パッケージを作成します。

   ```
   zip -r my_deployment_package.zip
   ```

 **syn-nodejs-puppeteer-11.0 以下の場合** 

Amazon S3 を使用する場合は、以下の構造が必要です。

```
  nodejs/
└── node_modules/
    └── myCanaryFilename.js
```

 **syn-nodejs-puppeteer-3.4\$1 でオプションのサブフォルダサポートを追加するには:** 

```
nodejs/
└── node_modules/
    └── myFolder/
        └── myCanaryFilename.js
```

**注記**  
設定のハンドラーパスは、ファイルの場所と一致する必要があります。

 **ハンドラー名** 

スクリプトのエントリポイント (ハンドラー) のファイル名に一致するように、Canary のスクリプトのエントリポイントを ` myCanaryFilename.functionName` として設定します。`syn-nodejs-puppeteer-3.4` より前のランタイムを使用している場合は、`functionName` は `handler` である必要があります。` syn-nodejs-puppeteer-3.4` 以降を使用している場合、ハンドラーとして任意の関数名を選択できます。`syn-nodejs-puppeteer-3.4` 以降を使用している場合、オプションで Canary を ` nodejs/node_modules/myFolder/my_canary_filename` などの別のフォルダに保存することもできます。別のフォルダに保存する場合は、スクリプトエントリポイントでそのパスを指定します (` myFolder/my_canary_filename.functionName` など)。

## 既存の Puppeteer スクリプトを変更して Synthetics の Canary として使用する
<a name="CloudWatch_Synthetics_Canaries_modify_puppeteer_script"></a>

このセクションでは、Puppeteer スクリプトを変更して Synthetics の Canary スクリプトとして実行する方法について説明します。Puppeteer の詳細については、「[Puppeteer API v1.14.0](https://github.com/puppeteer/puppeteer/blob/v1.14.0/docs/api.md)」を参照してください。

まず、次の Puppeteer スクリプトの例から始めます。

```
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://example.com');
  await page.screenshot({path: 'example.png'});

  await browser.close();
})();
```

変更の手順は次のとおりです。
+ `handler` 関数を作成してエクスポートします。このハンドラーは、スクリプトのエントリポイント関数です。` syn-nodejs-puppeteer-3.4` より前のランタイムを使用している場合、ハンドラー関数には `handler` という名前を付ける必要があります。`syn-nodejs-puppeteer-3.4` 以降を使用している場合、関数には任意の名前を付けることができますが、スクリプトで使用されている名前と同じである必要があります。また、`syn-nodejs-puppeteer-3.4` 以降を使用している場合は、スクリプトを任意のフォルダに保存し、そのフォルダをハンドラー名の一部として指定できます。

  ```
  const basicPuppeteerExample = async function () {};
  
  exports.handler = async () => {
      return await basicPuppeteerExample();
  };
  ```
+ `Synthetics` 依存関係を使用します。

  ```
  var synthetics = require('@aws/synthetics-puppeteer');
  ```
+ Puppeteer の `Page` オブジェクトを取得するには、`Synthetics.getPage` 関数を使用します。

  ```
  const page = await synthetics.getPage();
  ```

  Synthetics.getPage 関数から返されるページオブジェクトには、ログ記録用にインストルメント化された **page.on**、`request`、`response`、および ` requestfailed` の各イベントがあります。また、Synthetics は、ページのリクエストおよびレスポンス用の HAR ファイルの生成を設定し、ページの送信リクエストのユーザーエージェントヘッダーに Canary ARN を追加します。

これで、スクリプトを Synthetics の Canary として実行できるようになりました。更新されたスクリプトは次のとおりです。

```
var synthetics = require('@aws/synthetics-puppeteer');  // Synthetics dependency

const basicPuppeteerExample = async function () {
    const page = await synthetics.getPage(); // Get instrumented page from Synthetics
    await page.goto('https://example.com');
    await page.screenshot({path: '/tmp/example.png'}); // Write screenshot to /tmp folder
};

exports.handler = async () => {  // Exported handler function 
    return await basicPuppeteerExample();
};
```

## 環境変数
<a name="CloudWatch_Synthetics_Environment_Variables"></a>

Canary を作成する際に環境変数を使用できます。これにより、単一の Canary スクリプトを記述し、そのスクリプトを異なる値で使用して、同様のタスクを持つ複数の Canary をすばやく作成できます。

例えば、組織が、ソフトウェア開発のさまざまな段階向けに `prod`、` dev`、`pre-release` などのエンドポイントを有しており、これらの各エンドポイントをテストするために Canary を作成する必要があるとします。ソフトウェアをテストする 1 つの Canary スクリプトを記述し、3 つの Canary をそれぞれ作成するときに、エンドポイント環境変数に異なる値を指定できます。その後、Canary を作成するときに、環境変数に使用するスクリプトと値を指定します。

環境変数の名前には、文字、数字、およびアンダースコアを使用できます。文字で始まり、少なくとも 2 文字である必要があります。環境変数の合計サイズは 4 KB を超えることはできません。Lambda の予約済み環境変数を環境変数の名前として指定することはできません。予約済み環境変数の詳細については、「[ランタイム環境変数](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime)」をご参照ください。

**重要**  
環境変数のキーと値は、保管時に AWS が所有する AWS KMS キーを使用して暗号化されます。ただし、クライアント側で環境変数が暗号化されます。機密情報は保存しないでください。

次のスクリプト例では、2 つの環境変数を使用しています。このスクリプトは、ウェブページが利用可能かどうかをチェックする Canary 用です。環境変数を使用して、チェックする URL と、使用する CloudWatch Synthetics ログレベルの両方をパラメータ化します。

次の関数は、`LogLevel` を ` LOG_LEVEL` 環境変数の値に設定します。

```
 synthetics.setLogLevel(process.env.LOG_LEVEL);
```

この関数は、`URL` を `URL` 環境変数の値に設定します。

```
const URL = process.env.URL;
```

これは完全なスクリプトです。このスクリプトを使用して Canary を作成するときは、`LOG_LEVEL` および `URL` 環境変数の値を指定します。

```
var synthetics = require('@aws/synthetics-puppeteer');
const log = require('@aws/synthetics-logger');

const pageLoadEnvironmentVariable = async function () {

    // Setting the log level (0-3)
    synthetics.setLogLevel(process.env.LOG_LEVEL);
    // INSERT URL here
    const URL = process.env.URL;

    let page = await synthetics.getPage();
    //You can customize the wait condition here. For instance,
    //using 'networkidle2' may be less restrictive.
    const response = await page.goto(URL, {waitUntil: 'domcontentloaded', timeout: 30000});
    if (!response) {
        throw "Failed to load page!";
    }
    //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);
    log.debug('Environment variable:' + process.env.URL);

    //If the response status code is not a 2xx success code
    if (response.status() < 200 || response.status() > 299) {
        throw "Failed to load page!";
    }
};

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

### 環境変数をスクリプトに渡す
<a name="CloudWatch_Synthetics_Canaries_pass_variables"></a>

コンソールで Canary を作成するときに環境変数をスクリプトに渡すには、コンソールの [**Environment variables**] (環境変数) セクションで環境変数のキーと値を指定します。詳細については、「[Canary を作成する](CloudWatch_Synthetics_Canaries_Create.md)」を参照してください。

API または AWS CLI を介して環境変数を渡すには、`RunConfig` セクションの ` EnvironmentVariables` パラメータを使用します。以下は、キー `Environment` とキー `Region` を持つ 2 つの環境変数を使用する Canary を作成する AWS CLI コマンドの例です。

```
aws synthetics create-canary --cli-input-json '{
   "Name":"nameofCanary",
   "ExecutionRoleArn":"roleArn",
   "ArtifactS3Location":"s3://amzn-s3-demo-bucket-123456789012-us-west-2",
   "Schedule":{
      "Expression":"rate(0 minute)",
      "DurationInSeconds":604800
   },
   "Code":{
      "S3Bucket": "canarycreation",
      "S3Key": "cwsyn-mycanaryheartbeat-12345678-d1bd-1234-abcd-123456789012-12345678-6a1f-47c3-b291-123456789012.zip",
      "Handler":"pageLoadBlueprint.handler"
   },
   "RunConfig": {
      "TimeoutInSeconds":60,
      "EnvironmentVariables": {
         "Environment":"Production",
         "Region": "us-west-1"
      }
   },
   "SuccessRetentionPeriodInDays":13,
   "FailureRetentionPeriodInDays":13,
   "RuntimeVersion":"syn-nodejs-2.0"
}'
```

## Canary と他の AWS のサービスとの統合
<a name="CloudWatch_Synthetics_Canaries_AWS_integrate"></a>

すべての Canary では AWS SDK ライブラリを使用できます。このライブラリを Canary の作成時に使用すると、Canary を他の AWS のサービスと統合できます。

これを行うには、Canary に次のコードを追加する必要があります。これらの例では、Canary に統合されているサービスとして AWS Secrets Manager が使用されます。
+ AWS SDK をインポートします。

  ```
  const AWS = require('aws-sdk');
  ```
+ 統合する AWS のサービスのクライアントを作成します。

  ```
  const secretsManager = new AWS.SecretsManager();
  ```
+ このクライアントを使用して、サービスへの API コールを行います。

  ```
  var params = {
    SecretId: secretName
  };
  return await secretsManager.getSecretValue(params).promise();
  ```

次の Canary スクリプトのコードスニペットは、Secrets Manager との統合例をより詳細に示しています。

```
var synthetics = require('@aws/synthetics-puppeteer');
const log = require('@aws/synthetics-logger');
 
const AWS = require('aws-sdk');
const secretsManager = new AWS.SecretsManager();
 
const getSecrets = async (secretName) => {
    var params = {
        SecretId: secretName
    };
    return await secretsManager.getSecretValue(params).promise();
}
 
const secretsExample = async function () {
    let URL = "<URL>";
    let page = await synthetics.getPage();
    
    log.info(`Navigating to URL: ${URL}`);
    const response = await page.goto(URL, {waitUntil: 'domcontentloaded', timeout: 30000});
    
    // Fetch secrets
    let secrets = await getSecrets("secretname")
   
    /**
    * Use secrets to login. 
    *
    * Assuming secrets are stored in a JSON format like:
    * {
    *   "username": "<USERNAME>",
    *   "password": "<PASSWORD>"
    * }
    **/
    let secretsObj = JSON.parse(secrets.SecretString);
    await synthetics.executeStep('login', async function () {
        await page.type(">USERNAME-INPUT-SELECTOR<", secretsObj.username);
        await page.type(">PASSWORD-INPUT-SELECTOR<", secretsObj.password);
        
        await Promise.all([
          page.waitForNavigation({ timeout: 30000 }),
          await page.click(">SUBMIT-BUTTON-SELECTOR<")
        ]);
    });
   
    // Verify login was successful
    await synthetics.executeStep('verify', async function () {
        await page.waitForXPath(">SELECTOR<", { timeout: 30000 });
    });
};

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

## Canary に静的 IP アドレスの使用を強制する
<a name="CloudWatch_Synthetics_Canaries_staticIP"></a>

静的 IP アドレスを使用するように Canary を設定できます。

**Canary に静的 IP アドレスの使用を強制するには**

1. 新しい VPC を作成します。詳細については、「[VPC での DNS の使用](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-dns.html)」を参照してください。

1. 新しいインターネットゲートウェイを作成します。詳細については、「[インターネットゲートウェイを VPC に追加する](https://docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html#working-with-igw)」を参照してください。

1. 新しい VPC 内にパブリックサブネットを作成します。

1. 新しいルートテーブルを VPC に追加します。

1. `0.0.0.0/0` からインターネットゲートウェイに向かうルートを、新しいルートテーブルに追加します。

1. 新しいルートテーブルをパブリックサブネットに関連付けます。

1. Elastic IP アドレスを作成します。詳細については、「[Elastic IP アドレス](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html)」を参照してください。

1. 新しい NAT ゲートウェイを作成し、パブリックサブネットと Elastic IP アドレスに割り当てます。

1. VPC の内部にプライベートサブネットを作成します。

1. `0.0.0.0/0` から NAT ゲートウェイへのルートを VPC デフォルトルートテーブルに追加する

1. Canary を作成します。