

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 使用 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();
};
```

接下來，我們將展開指令碼以使用 Synthetics 記錄，並使用 AWS SDK 進行呼叫。為了示範，此指令碼會建立一個 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');
  ```
+ 使用 `Synthetics.getPage` 函數來取得 Puppeteer `Page` 物件。

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

  Synthetics.getPage 函數傳回的頁面物件具有 ** page.on** `request``response`和用於記錄` requestfailed`的事件。Synthetics 也會針對頁面上的請求和回應設定 HAR 檔案產生，並將 Canary ARN 新增至頁面上傳出請求的 user-agent 標頭。

指令碼現在已準備好作為 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 來測試這些端點。您可以撰寫測試軟體的單一 Canary 指令碼，然後在建立三個 Canary 的每一個 Canary 時，為端點環境變數指定不同的數值。然後，當您建立 Canary 時，您可以指定要用於環境變數的指令碼和數值。

環境變數名稱可包含字母、數字和底線字元。其必須以字母開頭，且至少有兩個字元。環境變數的總大小不能超過 4 KB。您無法指定任何 Lambda 保留環境變數作為環境變數的名稱。如需有關保留環境變數的詳細資訊，請參閱[執行時間環境變數](https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime)。

**重要**  
環境變數金鑰和值會使用 AWS 擁有 AWS KMS 的金鑰進行靜態加密。然而，環境變數在用戶端並未經過加密處理。請勿在其中存放敏感資訊。

以下範例指令碼使用了兩個環境變數。這個指令碼可用於檢查網頁是否可用的 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` 參數。以下是建立 Canary 的範例 AWS CLI 命令，該 Canary 使用兩個具有 `Environment`和 金鑰的環境變數`Region`。

```
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。對於這些範例， AWS Secrets Manager 用作 Canary 正在整合的服務。
+ 匯入 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>

您可以設定 Canary，以便使用靜態 IP 地址。

**若要強制 Canary 使用靜態 IP 地址**

1. 建立新 VPC 如需詳細資訊，請參閱[使用 DNS 與您的 VPC 搭配](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. 建立彈性 IP 地址。如需詳細資訊，請參閱[彈性 IP 地址](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html)。

1. 建立新的 NAT 閘道，並將其指派給公有子網路和彈性 IP 地址。

1. 在 VPC 內部建立私有子網路。

1. 將路由新增至 VPC 預設路由表，即從 `0.0.0.0/0` 移至 NAT 閘道

1. 建立 Canary。