AWS SAMでLambda関数を作成

AWS SAM(Serverless Application Model) は、CloudFormation をサーバーレスアプリケーションで利用しやすくなるように変形したものです。ここでは、sam init で生成されるサンプルアプリケーションをもとに動作確認を行います。( API Gateway のリクエストをトリガーに、Lambda関数 が実行されます。)

AWS SAM CLIをインストール

pip(Pythonのパーケージマネージャ) 経由で AWS SAM CLI をインストールできます。

Pyenv経由でPythonをインストール済みです。

$ python -V
Python 3.6.5
$ 
$ pip -V
pip 9.0.3 from /Users/xxxx/.pyenv/versions/3.6.5/lib/python3.6/site-packages (python 3.6)
Pyenvについて
下記ページで取り上げています。
Pyenv, Pipenvを利用したプロジェクト構築
$ pip install aws-sam-cli

AWS SAM CLI をインストールできました。

$ sam --help
Usage: sam [OPTIONS] COMMAND [ARGS]...

  AWS Serverless Application Model (SAM) CLI

  The AWS Serverless Application Model extends AWS CloudFormation to provide
  a simplified way of defining the Amazon API Gateway APIs, AWS Lambda
  functions, and Amazon DynamoDB tables needed by your serverless
  application. You can find more in-depth guide about the SAM specification
  here: https://github.com/awslabs/serverless-application-model.

Options:
  --debug    Turn on debug logging to print debug message generated by SAM
             CLI.
  --version  Show the version and exit.
  --info
  --help     Show this message and exit.

Commands:
  local     Run your Serverless application locally for...
  build     Build your Lambda function code
  deploy    Deploy an AWS SAM application. This is an alias for 'aws
            cloudformation deploy'.
  validate  Validate an AWS SAM template.
  init      Initialize a serverless application with a...
  logs      Fetch logs for a function
  package   Package an AWS SAM application. This is an alias for 'aws
            cloudformation package'.
  publish   Publish a packaged AWS SAM template to the AWS Serverless
            Application Repository.

アプリケーションの作成

サンプルアプリを作成
( sam init )

sam init でサンプルアプリを作成できます。

今回はランタイムに node.js を利用したいので以下のように実行します。

$ sam init --runtime nodejs8.10
[+] Initializing project structure...

Project generated: ./sam-app

Steps you can take next within the project folder
===================================================
[*] Invoke Function: sam local invoke HelloWorldFunction --event event.json
[*] Start API Gateway locally: sam local start-api

Read sam-app/README.md for further instructions

[*] Project initialization is now complete

以下ファイルが生成されていました。

sam-app/
├── hello-world
│   ├── tests
│   │   └── unit
│   │       └── test-handler.js     // ユニットテスト
│   ├── app.js                      // Lambda関数のソースコード
│   └── package.json
├── README.md
├── event.json                      // Lambdaに送られる疑似イベント(API Gateway)
└── template.yaml                   // SAMテンプレート

event.json は、ローカルでテストする際に活用できます。

app.js
( Lambda関数のソースコード )

message: 'hello world' というレスポンスを返すLambda関数のようです。

// 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
};

template.yaml
( SAMテンプレート )

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app
  
# 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: nodejs8.10
      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

プロパティについて

関数を追加したい場合、Resources 配下に設定を追加します。

Resources.HelloWorldFunction.Properties 配下の設定は以下のようになります。

プロパティ 概要
CodeUri Lambda関数のソースが存在するフォルダ指定します。
Handler 関数を指定します。
(今回は、app.jsファイルlambdaHandler を指定しています。)
Events 関数が起動されるイベントを設定します。
(今回は、APIで起動されるように指定しています。)

詳しくは、下記ページで確認できます。

CloudFormationについて

SAMCloudFormationテンプレート の拡張版なので、CloudFormation の知識も必要です。
CloudFormation については下記ページで取り上げています。

サンプルテンプレートについて

サンプルテンプレートの情報も役に立ちます。

ローカルテスト

指定Lambda関数を実行
( sam local invoke )

sam local invoke でLambda関数を指定して、ローカルテストを実行できます。

$ sam local invoke HelloWorldFunction --event event.json --region ap-northeast-1
2019-01-03 11:15:26 Invoking app.lambdaHandler (nodejs8.10)

Fetching lambci/lambda:nodejs8.10 Docker container image......................(省略)
2019-01-03 11:23:30 Mounting /xxxx/sam-app/hello-world as /var/task:ro inside runtime container
START RequestId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Version: $LATEST
END RequestId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
REPORT RequestId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  Duration: 11.64 ms      Billed Duration: 100 ms Memory Size: 128 MB     Max Memory Used: 31 MB

{"statusCode":200,"body":"{\"message\":\"hello world\"}"}

Dockerのコンテナイメージがダウンロードされて、指定したLambda関数が実行されました。

ローカルAPI Gatewayの起動
( sam local start-api )

sam local start-api を実行すると、ローカル上でAPI Gatewayを起動できます。

$ sam local start-api --region ap-northeast-1 --port 6000
2019-01-03 11:45:42 Mounting HelloWorldFunction at http://127.0.0.1:6000/hello [GET]
2019-01-03 11:45:42 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
2019-01-03 11:45:42  * Running on http://127.0.0.1:6000/ (Press CTRL+C to quit)

curlでリクエストしてみます。

$ curl http://127.0.0.1:6000/hello
{"message":"hello world"}

デプロイ

認証情報

デプロイ時、AWSリソースの作成・管理を行う権限を持ったIAMユーザーが必要です。

ここでは、wakuwaku-sam-admin というプロファイル情報を利用します。

$ cat ~/.aws/credentials 

[wakuwaku-sam-admin]
aws_access_key_id = xxxxxxxxxxxxxxxxxxxxxxx
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxx
region = ap-northeast-1

テンプレートを検証
( sam validate )

sam validate でテンプレートを検証できます。

$ env AWS_DEFAULT_REGION=ap-northeast-1 sam validate --profile wakuwaku-sam-admin
2019-01-03 13:37:14 Found credentials in shared credentials file: ~/.aws/credentials
/xxxx/sam-app/template.yaml is a valid SAM Template

ただ、sam validate を実行すると、 AttributeError: 'NoneType' object has no attribute 'lower' というエラーがでたので、下記 issueを参考に env AWS_DEFAULT_REGION=ap-northeast-1 をつけて実行しています。

https://github.com/awslabs/aws-sam-cli/issues/442

S3バケットを作成
( aws s3 mb )

AWS CLI でS3バケットを作成します。

$ aws s3 mb s3://sample-sam-hello-world --profile wakuwaku-sam-admin
make_bucket: sample-sam-hello-world
$ 
$ aws s3 ls --profile wakuwaku-sam-admin
2019-01-03 13:53:02 sample-sam-hello-world

パッケージ化してS3にアップロード
( sam package )

下記コマンドを実行します。

sam package \
    --output-template-file packaged.yaml \
    --s3-bucket sample-sam-hello-world \
    --profile wakuwaku-sam-admin

以下、実行結果です。aws cloudformationコマンド が実行されていることがわかります。

$ sam package \
>     --output-template-file packaged.yaml \
>     --s3-bucket sample-sam-hello-world \
>     --profile wakuwaku-sam-admin
Uploading to xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  1508 / 1508.0  (100.00%)
Successfully packaged artifacts and wrote output template to file packaged.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /xxxx/sam-app/packaged.yaml --stack-name <YOUR STACK NAME>

S3にアップロードされています。

$ aws s3 ls s3://sample-sam-hello-world --profile wakuwaku-sam-admin
2019-01-03 13:58:42       1508 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

デプロイ
( sam deploy )

下記コマンドを実行します。

sam deploy \
    --template-file packaged.yaml \
    --stack-name sam-app \
    --capabilities CAPABILITY_IAM \
    --profile wakuwaku-sam-admin

以下、実行結果です。

$ sam deploy \
>     --template-file packaged.yaml \
>     --stack-name sam-app \
>     --capabilities CAPABILITY_IAM \
>     --profile wakuwaku-sam-admin

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - sam-app

作成されたリソースを確認
( aws cloudformation list-stack-resources )

管理画面から確認できます。

640-aws-sam_stack.png

以下コマンドでも確認できます。

aws cloudformation list-stack-resources \
    --stack-name sam-app \
    --profile wakuwaku-sam-admin

API Gatewayのエンドポイントを確認

下記コマンドで確認できます。

aws cloudformation describe-stacks \
    --stack-name sam-app \
    --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \
    --output table \
    --profile wakuwaku-sam-admin
$ aws cloudformation describe-stacks \
>     --stack-name sam-app \
>     --query 'Stacks[].Outputs[?OutputKey==`HelloWorldApi`]' \
>     --output table \
>     --profile wakuwaku-sam-admin
------------------------------------------------------------------------------------------------------------------------------------------------------------------
|                                                                         DescribeStacks                                                                         |
+-------------------------------------------------------------------+----------------+---------------------------------------------------------------------------+
|                            Description                            |   OutputKey    |                                OutputValue                                |
+-------------------------------------------------------------------+----------------+---------------------------------------------------------------------------+
|  API Gateway endpoint URL for Prod stage for Hello World function |  HelloWorldApi |  https://xxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/  |
+-------------------------------------------------------------------+----------------+---------------------------------------------------------------------------+

curlでリクエストしてみます。

$ curl https://xxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message":"hello world"}

削除
( aws cloudformation delete-stack )

今回作成したスタックを削除します。

aws cloudformation delete-stack \
    --stack-name sam-app \
    --profile wakuwaku-sam-admin

参考