编写 Node.js 金丝雀脚本 - Amazon CloudWatch

编写 Node.js 金丝雀脚本

从 Scratch 创建 CloudWatch Synthetics 金丝雀

这里是一个极简 Synthetics 金丝雀脚本示例。此脚本将成功通过一次运行,并返回一个字符串。要查看失败的金丝雀示例,请将 let fail = false; 更改为 let fail = true;

您必须为金丝雀脚本定义入口点函数。要查看如何将文件上载到指定作为金丝雀 ArtifactS3Location 的 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) 位置上传金丝雀脚本,则 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 或者之后版本,您可以选择将金丝雀文件放在另一个文件夹中,然后创建与此类似的文件夹结构: nodejs/node_modules/myFolder/myCanaryFilename.js file and other folders and files

处理程序名称

请务必将您的金丝雀脚本入口点(处理程序)设置为 myCanaryFilename.functionName,以匹配脚本入口点的文件名。如果您使用 syn-nodejs-puppeteer-3.4 之前版本的运行时,则 functionName 必须是 handler。如果您使用 syn-nodejs-puppeteer-3.4 或之后版本,您可以选择任何函数名称作为处理程序。如果您使用 syn-nodejs-puppeteer-3.4 或之后版本,您也可以选择将金丝雀存储在单独的文件夹中,例如 nodejs/node_modules/myFolder/my_canary_filename。如果将其存储在单独的文件夹中,请在脚本入口点中指定该路径,例如 myFolder/my_canary_filename.functionName

更改现有 Puppeteer 脚本以将其用作 Synthetics 金丝雀

本节介绍如何对 Puppeteer 脚本进行修改,以将其作为 Synthetics 金丝雀脚本运行。有关 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 requestresponserequestfailed 事件。Synthetics 还为页面上的请求和响应设置 HAR 文件生成,并将金丝雀 ARN 添加到页面上的传出请求的 user-agent 标头。

该脚本现已准备好作为 Synthetics 金丝雀运行。更新的脚本如下:

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(); };

环境变量

您可以在创建金丝雀时使用环境变量。这样,您就能够编写单个金丝雀脚本,然后将该脚本与不同的值相结合,快速创建具有类似任务的多个金丝雀。

例如,假定您的企业具有用于不同软件开发阶段的 proddevpre-release 端点,而您需要创建金丝雀来测试其中每个端点。您可以编写一个测试软件的金丝雀脚本,然后在分别创建三个金丝雀时为端点环境变量指定不同的值。之后,在创建金丝雀时,您可以指定要用于环境变量的脚本和值。

环境变量的名称可以包含字母、数字和下划线字符,必须以字母开头,并且至少为两个字符。环境变量的总大小不能超过 4 KB。不能将任何 Lambda 预留环境变量指定为环境变量的名称。有关预留环境变量的更多信息,请参阅运行时环境变量

重要

环境变量键和值未加密。请勿在其中存储敏感信息。

下面的示例脚本使用两个环境变量。此脚本用于检查网页是否可用的金丝雀。该金丝雀使用环境变量来参数化所检查的 URL 和所使用的 CloudWatch Synthetics 日志级别。

以下函数将 LogLevel 设置为 LOG_LEVEL 环境变量的值。

synthetics.setLogLevel(process.env.LOG_LEVEL);

此函数将 URL 设置为 URL 环境变量的值。

const URL = process.env.URL;

以下是完整的脚本。当您使用此脚本创建金丝雀时,您可以指定 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(); };

将环境变量传递到脚本

要在控制台中创建金丝雀时将环境变量传递给脚本,请在控制台上的 Environment variables(环境变量)部分指定环境变量的密钥和值。有关更多信息,请参阅 创建金丝雀

要通过 API 或 AWS CLI 传递环境变量,请使用 RunConfig 部分中的 EnvironmentVariables 参数。以下为 AWS CLI 命令示例,该命令创建一个使用两个环境变量(具有 EnvironmentRegion 密钥)的金丝雀。

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" }'

将您的金丝雀与其他 AWS 服务集成

所有金丝雀都可以使用 AWS SDK 库。在编写金丝雀时,您可以使用此库将金丝雀与其他 AWS 服务集成。

为此,您需要将以下代码添加到您的金丝雀中。对于这些例子,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();

下面的金丝雀脚本代码段更详细地演示了与 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(); };

强制金丝雀使用静态 IP 地址

您可以设置金丝雀,使其使用静态 IP 地址。

强制金丝雀使用静态 IP 地址
  1. 创建新的 VPC。有关更多信息,请参阅在您的 VPC 中使用 DNS

  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. 创建金丝雀。