编写 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/
的单个 ZIP 文件中。如果您使用 myCanaryFilename.js file and other folders and files
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
函数获取 PuppeteerPage
对象。const page = await synthetics.getPage();
Synthetics.getPage 函数返回的页面对象指示需要记录 page.on
request
、response
和requestfailed
事件。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(); };
环境变量
您可以在创建金丝雀时使用环境变量。这样,您就能够编写单个金丝雀脚本,然后将该脚本与不同的值相结合,快速创建具有类似任务的多个金丝雀。
例如,假定您的企业具有用于不同软件开发阶段的 prod
、dev
和 pre-release
端点,而您需要创建金丝雀来测试其中每个端点。您可以编写一个测试软件的金丝雀脚本,然后在分别创建三个金丝雀时为端点环境变量指定不同的值。之后,在创建金丝雀时,您可以指定要用于环境变量的脚本和值。
环境变量的名称可以包含字母、数字和下划线字符,必须以字母开头,并且至少为两个字符。环境变量的总大小不能超过 4 KB。不能将任何 Lambda 预留环境变量指定为环境变量的名称。有关预留环境变量的更多信息,请参阅运行时环境变量。
重要
环境变量键和值未加密。请勿在其中存储敏感信息。
下面的示例脚本使用两个环境变量。此脚本用于检查网页是否可用的金丝雀。该金丝雀使用环境变量来参数化所检查的 URL 和所使用的 CloudWatch Synthetics 日志级别。
以下函数将 LogLevel
设置为 LOG_LEVEL
环境变量的值。
synthetics.setLogLevel(process.env.LOG_LEVEL);
此函数将 URL
设置为 URL
环境变量的值。
const URL = process.env.URL;
以下是完整的脚本。当您使用此脚本创建金丝雀时,您可以指定 LOG_LEVEL
和 URL
环境变量的值。
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 命令示例,该命令创建一个使用两个环境变量(具有 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" }'
将您的金丝雀与其他 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 地址
创建新的 VPC。有关更多信息,请参阅在您的 VPC 中使用 DNS。
创建新的互联网网关。有关更多信息,请参阅在您的 VPC 中添加互联网网关。
在新的 VPC 内创建公有子网。
向 VPC 添加新的路由表。
在新路由表中添加一条从
0.0.0.0/0
到互联网网关的路由。将新的路由表与公有子网关联。
创建弹性 IP 地址。有关更多信息,请参阅弹性 IP 地址。
创建一个新的 NAT 网关,并将其分配给公有子网和弹性 IP 地址。
在 VPC 中创建私有子网。
向 VPC 默认路由表中添加一条从
0.0.0.0/0
到 NAT 网关的路由创建金丝雀。