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

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

撰寫 Node.js Canary 指令碼

從頭開始創建 CloudWatch Synthetics 金絲雀

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

您必須定義 Canary 指令碼的進入點函數。若要查看檔案如何上傳到指定為 Canary ArtifactS3Location 的 Simple Storage Service (Amazon S3) 位置,請在 /tmp 資料夾下建立這些檔案。指令碼執行之後,通過/失敗狀態和持續時間量度會發佈到, 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('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 金絲雀文件

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

如果您有多個 .js 檔案,或者您的指令碼有依賴的相依性,則可將這些項目全部封裝到包含資料夾結構 nodejs/node_modules/myCanaryFilename.js file and other folders and files 的單一 ZIP 檔案中。如果您是使用 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 API v1.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 函數傳回的頁面物件具有 page.on request,以及檢測用於記錄的 responserequestfailed 事件。Synthetics 也會針對頁面上的請求和回應設定 HAR 檔案產生,並將 Canary ARN 新增至頁面上傳出請求的 user-agent 標頭。

指令碼現在已準備好作為 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參數。下列範例 AWS CLI 命令會建立使用兩個環境變數與 and 的Environment金鑰建立初期測試Region

aws synthetics create-canary --cli-input-json '{ "Name":"nameofCanary", "ExecutionRoleArn":"roleArn", "ArtifactS3Location":"s3://cw-syn-results-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" }'

將您的金絲雀與其他 AWS 服務集成

所有金絲雀都可以使用 AWS SDK 庫。當您編寫金絲雀時,您可以使用此庫將金絲雀與其他 AWS 服務集成在一起。

要這麼做,您需要新增下面的程式碼到您的 Canary。在這些範例中 AWS Secrets Manager ,會用作初期測試與之整合的服務。

  • 匯入開 AWS 發套件。

    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。