AWS SAM を使って、Lambda のローカル実行環境を試してみる

# はじめに

Lambda を開発するときに、どういった環境を使っているでしょうか。AWS マネージドコンソールには、オンラインエディターがあり、ブラウザ上でプログラミングができます。しかし、本格的に開発するときには、多機能で使い慣れた IDE が使いたくなります。手元のノートPC上にインストールしている IDE を使って開発するときに、動作確認のたびに、いちいちプログラミング途中のソースコードをパッケージングして Lambda へアップロードするのは面倒です。出来るだけローカルで完結させて、素早く動作確認と修正を行いたい気持ちになります。

こういう時に便利に使えるのが、SAM (Serverless Application Model) です。SAM は、Lambda を開発するときに便利に使えるオープンソースフレームワークで、次の特徴があります。

- Lambda ライクなローカル環境が使えて、手元で動作確認が出来る
- Lambda Function と、それに付随する API Gateway などの定義して、デプロイ出来る



今回は、SAM の Quick Start をやってみたので、備忘録的にメモを残しておきます。Quick Start は、このドキュメントに書かれているものを参考にしています。

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-cli-install-linux.html




# 前提条件

- Ubuntu 環境 (WSL2)
- SAM CLI インストール済み
- AWS CLI インストール済み
- Docker インストール済み
- Homebrew on Linux インストール済み



## memo : Docker Install

次の URL を参考に適当にインストールします

https://docs.docker.com/engine/install/ubuntu/



Docker 起動

```
sudo service docker start
```



## memo : Homebrew Install

Homebrew を Ubuntu に適当にインストールします
https://docs.brew.sh/Homebrew-on-Linux



fish の場合 

```
vim ~/.config/fish/config.fish
```



末尾に追記

```
set -x HOMEBREW_PREFIX /home/linuxbrew/.linuxbrew
set -x HOMEBREW_CELLAR /home/linuxbrew/.linuxbrew/Cellar
set -x HOMEBREW_REPOSITORY /home/linuxbrew/.linuxbrew/Homebrew
set -x PATH /home/linuxbrew/.linuxbrew/bin $PATH
set -x PATH /home/linuxbrew/.linuxbrew/sbin $PATH
set -x MANPATH /home/linuxbrew/.linuxbrew/share/man $MANPATH
set -x INFOPATH /home/linuxbrew/.linuxbrew/share/info $INFOPATH
```



# SAM Init

SAM の Document を参考に、Quick Start を進めていきます。

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-getting-started-hello-world.html

作業用 dir 作成

```
mkdir ~/samdir
cd ~/samdir
```



sam init で初期設定

```
sam init
```



1の `AWS Quick Start Templates` を選択

```
> sam init

        SAM CLI now collects telemetry to better understand customer needs.

        You can OPT OUT and disable telemetry collection by setting the
        environment variable SAM_CLI_TELEMETRY=0 in your shell.
        Thanks for your help!

        Learn More: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-telemetry.html

Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice:1
```



1 の Zip を選択

```
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
```



10 の Python を選択

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



Project Name は、hello-world

```
Project name [sam-app]: hello-world
```



1の `Hello World Example` を選択

```
AWS quick start application templates:
        1 - Hello World Example
        2 - EventBridge Hello World
        3 - EventBridge App from scratch (100+ Event Schemas)
        4 - Step Functions Sample App (Stock Trader)
Template selection: 1
```



こんな感じで自動生成されている

```
> tree hello-world
hello-world
├── README.md
├── __init__.py
├── events
│   └── event.json
├── hello_world
│   ├── __init__.py
│   ├── app.py
│   └── requirements.txt
├── template.yaml
└── tests
    ├── __init__.py
    └── unit
        ├── __init__.py
        └── test_handler.py

4 directories, 10 files
```



template.yaml の中身はこんな感じ

```
> cat hello-world/template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  hello-world

  Sample SAM Template for hello-world

# 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.lambda_handler
      Runtime: python3.6
      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
```



# SAM Build

init で生成した環境を Build します

```
cd hello-world/
sam build
```



実行例

```
> sam build
Building codeuri: hello_world/ runtime: python3.6 metadata: {} functions: ['HelloWorldFunction']
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided

```



# SAM Deploy

Build が正常にできたので、SAM をつかって Deploy 

```
sam deploy --guided
```



CloudFormation の Stack 名入力 : sam-hello-world-stack

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

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

        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [sam-app]: sam-hello-world-stack
```



Region このまま Enter

```
        AWS Region [ap-northeast-1]:
```



変更点を確認するために Yy

```
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        Confirm changes before deploy [y/N]:y
```



必要な IAM Role を自動作成 y

```
        #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]:y
```



Public に公開する y

```
        HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
```



Config の Save で y

```
        Save arguments to configuration file [Y/n]: y
```



このまま Enter

```
        SAM configuration file [samconfig.toml]:
```



Default

```
        SAM configuration environment [default]:
```



CloudFormation で Stack が作成されている。これは、SAM のメタデータ管理のために、S3 バケットが作成されている

![image-20210220173809555.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/c00b36af-fb32-e768-679c-a7055f4c7316.png)



SAM を経由して、CloudFormation の ChangeSet(変更点) が表示されます

```
Initiating deployment
=====================
HelloWorldFunction may not have authorization defined.
Uploading to sam-hello-world-stack/1b9a60561935a1e52fff89b5860c516a.template  1111 / 1111  (100.00%)

Waiting for changeset to be created..

CloudFormation stack changeset
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation                                     LogicalResourceId                             ResourceType                                  Replacement
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add                                         HelloWorldFunctionHelloWorldPermissionProd    AWS::Lambda::Permission                       N/A
+ Add                                         HelloWorldFunctionRole                        AWS::IAM::Role                                N/A
+ Add                                         HelloWorldFunction                            AWS::Lambda::Function                         N/A
+ Add                                         ServerlessRestApiDeployment47fc2d5f9d         AWS::ApiGateway::Deployment                   N/A
+ Add                                         ServerlessRestApiProdStage                    AWS::ApiGateway::Stage                        N/A
+ Add                                         ServerlessRestApi                             AWS::ApiGateway::RestApi                      N/A
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:539926008561:changeSet/samcli-deploy1613810205/c6116475-2528-40ab-8a50-0555891081b0
```



これで問題ないことを確認し、y

```
Deploy this changeset? [y/N]: y
```



CloudFormation 上で、Resource 群が作成されている

![image-20210220174037338.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/5e5b8cfb-11db-65e9-1942-3062a12334ee.png)



作成完了

```
Successfully created/updated stack - sam-hello-world-stack in ap-northeast-1
```

![image-20210220174138660.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/983d1b08-76bc-6b61-164a-2d16c30f1618.png)



Lambda Function や、API Gateway などが作成されている

![image-20210220174204731.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/2a8df343-f899-593e-801f-1c3fda693eff.png)



CloudFormation Stack の Output には、API Gateway の Endpoint が出力されています

https://hdz2hgwco1.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/

![image-20210220192910787.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/f5c5d2d9-4b35-8f11-243a-2d602d4836b7.png)



実行すると、Hello World が返ってきます

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



API Gateway のページでも、Endpoint が確認可能

![image-20210220211832062.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/83d08471-72be-1d93-d008-6dd4e51acecc.png)



# SAM Local Test

SAM CLI には、Docker を使って Lambda のローカル実行環境をシミュレートする機能があります。次の2つのローカル実行方法があります。

- ローカルAPI を生成して実行
- 直接実行



## ローカルAPIを生成

ローカルで、API 実行エンドポイントを生成

```
sam local start-api
```



実行例。プロンプトは返ってこない

```
> 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
2021-02-20 21:58:34  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

```



別の Shell から実行

```
curl http://127.0.0.1:3000/hello
```



返ってこないプロンプト上で Build が走る

```

Invoking app.lambda_handler (python3.6)
Image was not found.
Building image................................................................................................................................................................................................................................................................................................................
```



Build が完了すると、レスポンスが返ってくる

```
> curl http://127.0.0.1:3000/hello
{"message": "hello world"}⏎
```



## 直接実行

```
sam local invoke "HelloWorldFunction" -e events/event.json
```



実行例

```
> sam local invoke "HelloWorldFunction" -e events/event.json
Invoking app.lambda_handler (python3.6)
Skip pulling image and use local one: amazon/aws-sam-cli-emulation-image-python3.6:rapid-1.18.2.

Mounting /home/sugi/samdir/hello-world/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
START RequestId: 45d0fd3b-1f3a-4dbf-8cfc-f15ca9fa6153 Version: $LATEST
END RequestId: 45d0fd3b-1f3a-4dbf-8cfc-f15ca9fa6153
REPORT RequestId: 45d0fd3b-1f3a-4dbf-8cfc-f15ca9fa6153  Init Duration: 0.16 ms  Duration: 112.95 ms     Billed Duration: 200 ms Memory Size: 128 MB     Max Memory Used: 128 MB
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}⏎
```

# ローカルで新しいプログラムの動作確認

手元のノートPC 上で、プログラミングをしている途中で、すぐに動作確認したいときは、Build をしたあとに、ローカルで実行するコマンドを続けて打てばよいです



Bash 使っているとき

```
sam build && sam local invoke
```



Fish 使っているとき

```
sam build; and sam local invoke
```



# IDE で開発しているときの動作確認

手元のノートPC 上で、プログラミングをしている途中で、すぐに動作確認したいときは、Build をしたあとに、ローカルで実行するコマンドを続けて打てばよいです



Bash 使っているとき

```
sam build && sam local invoke
```



Fish 使っているとき

```
sam build; and sam local invoke
```

# SAM CLI を使って、Lambda Function を更新

ソースコードで、Hello World 2 に変更後、sam deploy を再度行うと、AWS 上で構成された、Lambda Function が更新される

Bash 使っているとき

```
sam build && sam deploy
```



Fish 使っているとき

```
sam build; and sam deploy
```



実行

```
> curl https://hdz2hgwco1.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": "hello world2"}
```


# 参考URL

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/what-is-sam.html