

# Puppeteer 런타임을 사용하여 Node.js 카나리 스크립트 작성
<a name="CloudWatch_Synthetics_Canaries_WritingCanary_Nodejs_Pup"></a>

**Topics**
+ [Scratch에서 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)
+ [다른 AWS 서비스와 canary 통합](#CloudWatch_Synthetics_Canaries_AWS_integrate)
+ [canary가 고정 IP 주소를 사용하도록 지정](#CloudWatch_Synthetics_Canaries_staticIP)

## Scratch에서 CloudWatch Synthetics canary 생성
<a name="CloudWatch_Synthetics_Canaries_write_from_scratch"></a>

다음은 최소 Synthetics canary 스크립트 예입니다. 이 스크립트는 성공적인 실행으로 전달되고 문자열을 반환합니다. 실패한 canary가 어떻게 보이는지 확인하려면 `let fail = false;`를 `let fail = true;`로 변경합니다.

canary 스크립트의 진입점 함수를 정의해야 합니다. 파일이 카나리의 `ArtifactS3Location`으로 지정된 Amazon S3 위치에 어떻게 업로드되는지 보려면 `/tmp` 폴더 아래에 이러한 파일을 생성합니다. 모든 카나리 아티팩트는 유일하게 쓰기 가능한 디렉터리인 `/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\_modules의 추가 종속성(필요한 경우)

패키징 구조 예제:

```
  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 이상에서 선택적 하위 폴더 지원을 추가하는 방법:** 

```
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을 페이지의 발신 요청의 사용자 에이전트 헤더에 추가합니다.

이제 스크립트를 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를 생성할 때 스크립트 및 환경 변수에 사용할 값을 지정합니다.

환경 변수의 이름에는 문자, 숫자, 밑줄 문자가 포함될 수 있습니다. 이름은 문자로 시작해야 하며 2자 이상이어야 합니다. 환경 변수의 총 크기는 4KB를 초과할 수 없습니다. 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` 파라미터를 사용합니다. 다음은 `Environment` 및 `Region` 키가 있는 두 개의 환경 변수를 사용하는 canary를 생성하는 AWS CLI 명령의 예입니다.

```
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 서비스와 canary 통합
<a name="CloudWatch_Synthetics_Canaries_AWS_integrate"></a>

모든 canary는 AWS SDK 라이브러리를 사용할 수 있습니다. canary를 다른 AWS 서비스와 통합하기 위해 canary를 작성할 때 이 라이브러리를 사용할 수 있습니다.

이렇게 하려면 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를 설정할 수 있습니다.

**canary가 고정 IP 주소를 사용하도록 지정하려면**

1. 새 VPC를 생성합니다. 자세한 내용은 [VPC에서 DNS 사용하기](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. `0.0.0.0/0`에서 NAT 게이트웨이로 이동하는 경로를 VPC 기본 라우팅 테이블에 추가합니다.

1. canary를 생성합니다.