撰寫 Node.js Canary 指令碼 - Amazon CloudWatch

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

撰寫 Node.js Canary 指令碼

從頭開始建立 CloudWatch Synthetics Canary

下面是基本 Synthetics Canary 指令碼的範例。此指令碼作為成功執行傳遞,並傳回一個字串。若想查看失敗的 Canary 看起來是什麼樣子,請將 let fail = false; 變更為 let fail = true;

您必須定義 Canary 指令碼的進入點函數。若要了解如何將檔案上傳至指定為 Canary 的 Amazon S3 位置ArtifactS3Location,請在 下建立這些檔案,the /tmp folder. After the script runs, the pass/fail status and the duration metrics are published to CloudWatch and the files under /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('SyntheticsLogger'); 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 檔案

如果您使用 Simple Storage Service (Amazon S3) 位置上傳 Canary 指令碼,則 zip 檔案應在此資料夾結構下包含指令碼:nodejs/node_modules/myCanaryFilename.js file

如果您有多個.js檔案,或者您有指令碼依賴的相依性,您可以將它們全部綁定到包含資料夾結構 的單一ZIP檔案中nodejs/node_modules/myCanaryFilename.js file and other folders and files。如果您是使用 syn-nodejs-puppeteer-3.4 或更高版本,您可以選擇將 Canary 檔案放在另一個資料夾中,並像這樣建立資料夾結構:nodejs/node_modules/myFolder/myCanaryFilename.js file and other folders and files

處理常式名稱

請務必將 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 使用

本節介紹如何採用 Puppeteer 指令碼和修改它們以作為 Synthetics Canary 指令碼執行。如需 Puppeteer 的詳細資訊,請參閱 Puppeteer v1API.14.0

我們將從這個 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('Synthetics');
  • 使用 Synthetics.getPage 函數來取得 Puppeteer Page 物件。

    const page = await synthetics.getPage();

    Synthetics.getPage function 傳回的頁面物件具有 page.on requestresponse和用於記錄requestfailed的事件。Synthetics 也會為頁面上的請求和回應設定HAR檔案產生,並將 Canary 新增至頁面上傳出請求ARN的使用者代理標頭。

指令碼現在已準備好作為 Synthetics Canary 執行。這是更新後的指令碼:

var synthetics = require('Synthetics'); // 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(); };

環境變數

建立 Canary 時,您可以使用環境變數。這允許您編寫單一 Canary 指令碼,然後使用具有不同數值的指令碼來快速建立具有類似任務的多個 Canary。

例如,假設您的組織在軟體開發的不同階段擁有諸如 proddevpre-release 之類的端點,並且您需要建立 Canary 來測試這些端點。您可以撰寫測試軟體的單一 Canary 指令碼,然後在建立三個 Canary 的每一個 Canary 時,為端點環境變數指定不同的數值。然後,當您建立 Canary 時,您可以指定要用於環境變數的指令碼和數值。

環境變數名稱可包含字母、數字和底線字元。其必須以字母開頭,且至少有兩個字元。環境變數的總大小不能超過 4 KB。您無法指定任何 Lambda 保留環境變數作為環境變數的名稱。如需有關保留環境變數的詳細資訊,請參閱執行時間環境變數

重要

環境變數索引鍵和值未加密。請勿在其中存放敏感資訊。

以下範例指令碼使用了兩個環境變數。這個指令碼可用於檢查網頁是否可用的 Canary。它使用環境變數來參數化其檢查URL的 及其使用的 CloudWatch Synthetics 日誌層級。

以下函數會將 LogLevel 設定為 LOG_LEVEL 環境變數的數值。

synthetics.setLogLevel(process.env.LOG_LEVEL);

此函數會將 URL 設定為 URL 環境變數的數值。

const URL = process.env.URL;

這是完整的指令碼。當您使用此指令碼建立 Canary 時,您可以指定要用於 LOG_LEVELURL 環境變數的數值。

var synthetics = require('Synthetics'); const log = require('SyntheticsLogger'); 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(); };

將環境變數傳遞給指令碼

若要在主控台中建立 Canary 時將環境變數傳遞至指令碼,請在主控台的 Environment variables (環境變數) 區段中指定環境變數的金鑰和數值。如需詳細資訊,請參閱建立 Canary

若要透過 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 服務整合

所有 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('Synthetics'); const log = require('SyntheticsLogger'); 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 地址

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

若要強制 Canary 使用靜態 IP 地址
  1. 建立新的 VPC。如需詳細資訊,請參閱DNS搭配使用您的 VPC

  2. 建立新的網際網路閘道。如需詳細資訊,請參閱將網際網路閘道新增至您的 VPC

  3. 在新的 內建立公有子網路VPC。

  4. 將新的路由表新增至 VPC。

  5. 在新的路由表中新增一個路由,而該路由從 0.0.0.0/0 移至網際網路閘道。

  6. 將新的路由表與公有子網路建立關聯。

  7. 建立彈性 IP 地址。如需詳細資訊,請參閱彈性 IP 地址

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

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

  10. 將路由新增至VPC預設路由表,從 0.0.0.0/0前往NAT閘道

  11. 建立 Canary。