12/03 AWS Lambda with AWS SAM CLI

이 글은 Serverless Hello World Advent Calendar 2020의 셋째 날이다.

AWS SAM CLI 설치


Linux에 AWS SAM CLI 설치에 따라 가져옵니다.
% sam --version
SAM CLI, version 1.13.1

프로젝트 작성

sam init에 프로젝트를 만듭니다.
OCI 컨테이너가 지원되고 있습니다.
% sam init
Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1
What package type would you like to use?
        1 - Zip (artifact is a zip uploaded to S3)
        2 - Image (artifact is an image uploaded to an ECR image repository)
Package type: 1

Which runtime would you like to use?
        1 - nodejs12.x
        2 - python3.8
        3 - ruby2.7
        4 - go1.x
        5 - java11
        6 - dotnetcore3.1
        7 - nodejs10.x
        8 - python3.7
        9 - python3.6
        10 - python2.7
        11 - ruby2.5
        12 - java8.al2
        13 - java8
        14 - dotnetcore2.1
Runtime: 1

Project name [sam-app]: adventcalendar03

Cloning app templates from https://github.com/aws/aws-sam-cli-app-templates

AWS quick start application templates:
        1 - Hello World Example
        2 - Step Functions Sample App (Stock Trader)
        3 - Quick Start: From Scratch
        4 - Quick Start: Scheduled Events
        5 - Quick Start: S3
        6 - Quick Start: SNS
        7 - Quick Start: SQS
        8 - Quick Start: Web Backend
Template selection: 1

    -----------------------
    Generating application:
    -----------------------
    Name: adventcalendar03
    Runtime: nodejs12.x
    Dependency Manager: npm
    Application Template: hello-world
    Output Directory: .
    
    Next steps can be found in the README file at ./adventcalendar03/README.md

생성된 파일 확인

template.yaml에는 Lambda 함수와 공개 API 이벤트가 설정되어 있습니다.언뜻 보기에는 길지만 절반 가까이가 등록된 AWS 자원 정보를 출력하는 Outputs 섹션이다.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  adventcalendar03

  Sample SAM Template for adventcalendar03
  
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs12.x
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn
hello-world/app.js는 함수 주체이다.
// const axios = require('axios')
// const url = 'http://checkip.amazonaws.com/';
let response;

/**
 *
 * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
 * @param {Object} event - API Gateway Lambda Proxy Input Format
 *
 * Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html 
 * @param {Object} context
 *
 * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
 * @returns {Object} object - API Gateway Lambda Proxy Output Format
 * 
 */
exports.lambdaHandler = async (event, context) => {
    try {
        // const ret = await axios(url);
        response = {
            'statusCode': 200,
            'body': JSON.stringify({
                message: 'hello world',
                // location: ret.data.trim()
            })
        }
    } catch (err) {
        console.log(err);
        return err;
    }

    return response
};
hello-world/tests/unit/test-handler.js에서 단원 테스트를 생성하여 높은 평가를 받았다.최근 많은 것Jest이 아니라 Chai의 기초다.
'use strict';

const app = require('../../app.js');
const chai = require('chai');
const expect = chai.expect;
var event, context;

describe('Tests index', function () {
    it('verifies successful response', async () => {
        const result = await app.lambdaHandler(event, context)

        expect(result).to.be.an('object');
        expect(result.statusCode).to.equal(200);
        expect(result.body).to.be.an('string');

        let response = JSON.parse(result.body);

        expect(response).to.be.an('object');
        expect(response.message).to.be.equal("hello world");
        // expect(response.location).to.be.an("string");
    });
});

로컬 환경에서의 동작 확인(단일 함수)


AWS SAM CLI에서는 Docker를 사용하여 로컬 환경에서 함수의 실행 환경을 시뮬레이션할 수 있습니다.
% cd adventcalendar03
% sam local invoke "HelloWorldFunction" -e events/event.json
Invoking app.lambdaHandler (nodejs12.x)
Image was not found.
Building image...........................................................................................................................................................................................................................
Skip pulling image and use local one: amazon/aws-sam-cli-emulation-image-nodejs12.x:rapid-1.13.1.

Mounting /home/masa/work/serverless-helloworld/03/adventcalendar03/hello-world as /var/task:ro,delegated inside runtime container
START RequestId: b1371030-63c0-4a3d-a450-d1a743585468 Version: $LATEST
END RequestId: b1371030-63c0-4a3d-a450-d1a743585468
REPORT RequestId: b1371030-63c0-4a3d-a450-d1a743585468  Init Duration: 0.23 ms  Duration: 150.02 ms       Billed Duration: 200 ms Memory Size: 128 MB     Max Memory Used: 128 MB
{"statusCode":200,"body":"{\"message\":\"hello world\"}"}%                                
는 AWS에서 CloudWatch Logs로 출력된 로그와 함수의 실행 결과를 보여줍니다.

로컬 환경에서 동작 확인(API Gateway 경유)


함수를 개별적으로 실행할 수 있을 뿐만 아니라 API Gateway도 로컬에서 시작해 동작을 확인할 수 있다.
% sam local start-api
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2020-12-04 00:51:25  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
다른 터미널에서 요청을 보내려고 합니다.
% curl -v http://127.0.0.1:3000/hello
*   Trying 127.0.0.1:3000...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 3000 (#0)
> GET /hello HTTP/1.1
> Host: 127.0.0.1:3000
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Content-Length: 25
< Server: Werkzeug/1.0.1 Python/3.8.6
< Date: Thu, 03 Dec 2020 15:51:38 GMT
< 
* Closing connection 0
{"message":"hello world"}%      
사용자의 터미널 디스플레이 로그.
2020-12-04 00:51:34 127.0.0.1 - - [04/Dec/2020 00:51:34] "GET /api HTTP/1.1" 403 -
Invoking app.lambdaHandler (nodejs12.x)
Skip pulling image and use local one: amazon/aws-sam-cli-emulation-image-nodejs12.x:rapid-1.13.1.

Mounting /home/masa/work/serverless-helloworld/03/adventcalendar03/hello-world as /var/task:ro,delegated inside runtime container
START RequestId: c434e64a-c280-40ef-9087-d34d9c925275 Version: $LATEST
END RequestId: c434e64a-c280-40ef-9087-d34d9c925275
REPORT RequestId: c434e64a-c280-40ef-9087-d34d9c925275  Init Duration: 0.51 ms  Duration: 138.93 ms       Billed Duration: 200 ms Memory Size: 128 MB     Max Memory Used: 128 MB
No Content-Type given. Defaulting to 'application/json'.
2020-12-04 00:51:38 127.0.0.1 - - [04/Dec/2020 00:51:38] "GET /hello HTTP/1.1" 200 -

프로그램 설계


AWS로 디버깅합니다.
프로그램에 필요한 정보를 처음 입력하는 옵션sam local start-api.입력한 내용이 --guided에 저장되므로 두 번째 시작은 필요하지 않습니다.
% sam deploy --guided

Configuring SAM deploy
======================

        Looking for config file [samconfig.toml] :  Not found

        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [sam-app]: adventcalendar03
        AWS Region [us-east-1]: ap-northeast-1
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        Confirm changes before deploy [y/N]: 
        #SAM needs permission to be able to create roles to connect to the resources in your template
        Allow SAM CLI IAM role creation [Y/n]: 
        HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
        Save arguments to configuration file [Y/n]: 
        SAM configuration file [samconfig.toml]: 
        SAM configuration environment [default]: 

        Looking for resources needed for deployment: Found!

                Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-3gtctbms54wr
                A different default S3 bucket can be set in samconfig.toml

        Saved arguments to config file
        Running 'sam deploy' for future deployments will use the parameters saved above.
        The above parameters can be changed by modifying samconfig.toml
        Learn more about samconfig.toml syntax at 
        https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
Uploading to adventcalendar03/d40748b23cc2b52639ac30cbe755a890  1510 / 1510.0  (100.00%)

        Deploying with following values
        ===============================
        Stack name                   : adventcalendar03
        Region                       : ap-northeast-1
        Confirm changeset            : False
        Deployment s3 bucket         : aws-sam-cli-managed-default-samclisourcebucket-3gtctbms54wr
        Capabilities                 : ["CAPABILITY_IAM"]
        Parameter overrides          : {}
        Signing Profiles           : {}

Initiating deployment
=====================
HelloWorldFunction may not have authorization defined.
Uploading to adventcalendar03/8938c1ff6b9bbd55bd210f5b20dde7fe.template  1116 / 1116.0  (100.00%)

Waiting for changeset to be created..

CloudFormation stack changeset
-------------------------------------------------------------------------------------------------
Operation                LogicalResourceId        ResourceType             Replacement            
-------------------------------------------------------------------------------------------------
+ Add                    HelloWorldFunctionHell   AWS::Lambda::Permissio   N/A                    
                         oWorldPermissionProd     n                                               
+ Add                    HelloWorldFunctionRole   AWS::IAM::Role           N/A                    
+ Add                    HelloWorldFunction       AWS::Lambda::Function    N/A                    
+ Add                    ServerlessRestApiDeplo   AWS::ApiGateway::Deplo   N/A                    
                         yment47fc2d5f9d          yment                                           
+ Add                    ServerlessRestApiProdS   AWS::ApiGateway::Stage   N/A                    
                         tage                                                                     
+ Add                    ServerlessRestApi        AWS::ApiGateway::RestA   N/A                    
                                                  pi                                              
-------------------------------------------------------------------------------------------------

Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:changeSet/samcli-deploy1607011170/c6bf5c9a-823a-423a-8171-e441ca22ad66


2020-12-04 00:59:41 - Waiting for stack create/update to complete

CloudFormation events from changeset
-------------------------------------------------------------------------------------------------
ResourceStatus           ResourceType             LogicalResourceId        ResourceStatusReason   
-------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS       AWS::IAM::Role           HelloWorldFunctionRole   -                      
CREATE_IN_PROGRESS       AWS::IAM::Role           HelloWorldFunctionRole   Resource creation      
                                                                           Initiated              
CREATE_COMPLETE          AWS::IAM::Role           HelloWorldFunctionRole   -                      
CREATE_IN_PROGRESS       AWS::Lambda::Function    HelloWorldFunction       -                      
CREATE_IN_PROGRESS       AWS::Lambda::Function    HelloWorldFunction       Resource creation      
                                                                           Initiated              
CREATE_COMPLETE          AWS::Lambda::Function    HelloWorldFunction       -                      
CREATE_IN_PROGRESS       AWS::ApiGateway::RestA   ServerlessRestApi        -                      
                         pi                                                                       
CREATE_IN_PROGRESS       AWS::ApiGateway::RestA   ServerlessRestApi        Resource creation      
                         pi                                                Initiated              
CREATE_COMPLETE          AWS::ApiGateway::RestA   ServerlessRestApi        -                      
                         pi                                                                       
CREATE_IN_PROGRESS       AWS::ApiGateway::Deplo   ServerlessRestApiDeplo   -                      
                         yment                    yment47fc2d5f9d                                 
CREATE_IN_PROGRESS       AWS::Lambda::Permissio   HelloWorldFunctionHell   Resource creation      
                         n                        oWorldPermissionProd     Initiated              
CREATE_IN_PROGRESS       AWS::Lambda::Permissio   HelloWorldFunctionHell   -                      
                         n                        oWorldPermissionProd                            
CREATE_COMPLETE          AWS::ApiGateway::Deplo   ServerlessRestApiDeplo   -                      
                         yment                    yment47fc2d5f9d                                 
CREATE_IN_PROGRESS       AWS::ApiGateway::Deplo   ServerlessRestApiDeplo   Resource creation      
                         yment                    yment47fc2d5f9d          Initiated              
CREATE_IN_PROGRESS       AWS::ApiGateway::Stage   ServerlessRestApiProdS   -                      
                                                  tage                                            
CREATE_IN_PROGRESS       AWS::ApiGateway::Stage   ServerlessRestApiProdS   Resource creation      
                                                  tage                     Initiated              
CREATE_COMPLETE          AWS::ApiGateway::Stage   ServerlessRestApiProdS   -                      
                                                  tage                                            
CREATE_COMPLETE          AWS::Lambda::Permissio   HelloWorldFunctionHell   -                      
                         n                        oWorldPermissionProd                            
CREATE_COMPLETE          AWS::CloudFormation::S   adventcalendar03         -                      
                         tack                                                                     
-------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
-------------------------------------------------------------------------------------------------
Outputs                                                                                         
-------------------------------------------------------------------------------------------------
Key                 HelloWorldFunctionIamRole                                                   
Description         Implicit IAM Role created for Hello World function                          
Value                                                                                           
arn:aws:iam::XXXXXXXXXXXX:role/adventcalendar03-HelloWorldFunctionRole-1IW4M6LIINSG6            

Key                 HelloWorldApi                                                               
Description         API Gateway endpoint URL for Prod stage for Hello World function            
Value               https://ycl8rpimeg.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/     

Key                 HelloWorldFunction                                                          
Description         Hello World Lambda Function ARN                                             
Value               arn:aws:lambda:ap-                                                          
northeast-1:XXXXXXXXXXXX:function:adventcalendar03-HelloWorldFunction-1OL861863RRDY             
-------------------------------------------------------------------------------------------------

Successfully created/updated stack - adventcalendar03 in ap-northeast-1

동작 확인


그럼 바로 두드리러 갈게요.
% curl -v https://ycl8rpimeg.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/

...(省略)...

* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x555b6c256db0)
> GET /Prod/hello/ HTTP/2
> Host: ycl8rpimeg.execute-api.ap-northeast-1.amazonaws.com
> user-agent: curl/7.68.0
> accept: */*
> 
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 200 
< content-type: application/json
< content-length: 25
< date: Thu, 03 Dec 2020 16:06:00 GMT
< x-amzn-requestid: 3af4e0fd-6112-4a94-b2c4-5050e6977251
< x-amz-apigw-id: W-70OFFltjMFZmA=
< x-amzn-trace-id: Root=1-5fc90ce7-5cf15a065808962753bbae65;Sampled=0
< x-cache: Miss from cloudfront
< via: 1.1 d930c4e4b6bd1abceb94d9d7f03e8e5f.cloudfront.net (CloudFront)
< x-amz-cf-pop: NRT12-C3
< x-amz-cf-id: BVYMQFeXTH4mQDXsO7FWSO6TEmAdhFd2T2WG0bcBUt2uOg9wA-f0eg==
< 
* Connection #0 to host ycl8rpimeg.execute-api.ap-northeast-1.amazonaws.com left intact
{"message":"hello world"}%       
안전운전 확인.

좋은 웹페이지 즐겨찾기