AWS SAM触ってみた


2021/9/30(木) JAWS-UG CLI専門支部 #229R CloudWatch Logs入門で「AWS SAM触ってみた奮闘記」というタイトルでLTの時間をいただきました。「AWS SAMって何？」の状態から、なんとなく概要がつかめるようになるまでの奮闘記です。

https://speakerdeck.com/emiki/aws-samhong-tutemitafen-dou-ji

本記事ではSAMの概要と、チュートリアルの簡単な手順を記載します。

# 1.AWS SAMとは -AWS Serverless Application Model

https://aws.amazon.com/jp/serverless/sam/

公式のドキュメントに説明があるが、個人的に要点をギュッとまとめると以下の通り。

- AWSのサーバレスサービスを使ったアプリケーションの構築を自動化する便利ツール
- 内部的にはCloudFormation
- SAM専用CLIで使えるもの

# 2.準備
Cloud9環境にはSAM CLIがインストール済みなのでそのまま使える。
今回は「Hello worldアプリケーションのデプロイ」チュートリアルを実施する。

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

以下のような構成図で、API GatewayがGETメソッドでLambdaを呼び出し応答を返す簡単なアプリを作成する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/72bfa99d-6ad4-8223-92c2-35545a5b76e2.png)

SAM CLIを実行するCloud9環境のEC2インスタンスに、今回は以下AWS管理ポリシーを付与したIAMロールを関連付ける。

- API Gateway（AmazonAPIGatewayAdministrator）
- Lambda（AWSLambda_FullAccess）
- CloudFormation（AWSCloudFormationFullAccess）
- S3（AmazonS3FullAccess）
- IAM（IAMFullAccess）

# 3.SAM実行手順

以下3ステップを実行するだけでサンプルアプリケーションができる。

```bash
#①サンプルアプリケーションをダウンロード
sam init

#②アプリケーションをビルドする
cd sam-app
sam build

#③アプリケーションをデプロイする
sam deploy --guided
```

## ステップ①サンプルアプリケーションをダウンロード（sam init）

```sam init```コマンドを実行すると対話形式で選択が始まる。

```bash
#ステップ①サンプルアプリケーションをダウンロード
sam init
#Enter
```
以下のように説明文が表示され、選択肢が出る。

```
        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: 
```
今回は「Hello worldアプリケーションのデプロイ」チュートリアルを実施するので、「1」を入力しEnter

```
Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1
#Enter
```
パッケージタイプを聞かれる。「1」のZipを選択しEnter

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

Runtimeはなんでもいいが、今回は「9」python3.7を選択しEnter

```
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: 9
#Enter
```

プロジェクト名は[sam-app]のままEnter

```
Project name [sam-app]: 
#Enter
```

クイックスタートテンプレートは「1」Hello World Exampleを選択しEnter

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

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

以下のようにエラーなく終了すればOK。
（CLI最新バージョンが使えると出ているが今回は無視する）

```
    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Runtime: python3.7
    Dependency Manager: pip
    Application Template: hello-world
    Output Directory: .
    
    Next steps can be found in the README file at ./sam-app/README.md
        

SAM CLI update available (1.31.0); (1.19.0 installed)
To download: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html
[ec2-user@ip-10-0-0-104%]$
```

ここまで完了すると、以下のようなディレクトリができている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/2bf945b1-d5bb-6290-62cd-4536a66a22c0.png)

「app.py」がLambda関数としてデプロイされる。
「template.yaml」はCloudFormationテンプレートで、このテンプレートを展開することによってサーバレスリソースがデプロイされる。

「app.py」の中身↓↓↓
※SAMによって自動生成されるコメント行は省略している

```python
import json

def lambda_handler(event, context):

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world",
        }),
    }
```

API GatewayからのGETリクエストで```"message": "hello world"```と帰ってくるLambda関数が記載されている。今回は```"message": "hello CLI world"```と書き換えてみる。

```python
import json

def lambda_handler(event, context):

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello CLI world",
        }),
    }
```


## ステップ②アプリケーションをビルドする（sam build）
まずsam-appディレクトリに移動する。

```bash
cd sam-app
#Enter
```

続いて```sam build```コマンドを実行する。

```bash
sam build
#Enter
```

以下のようにビルドが実行され終了する。

```
Building codeuri: hello_world/ runtime: python3.7 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
    
[ec2-user@ip-10-0-0-104%]$
```

## ステップ③アプリケーションをデプロイする（sam deploy --guided）
最後に```sam deploy```コマンドでデプロイする。
``` --guided```オプションを付けることで対話形式になる。

```bash
sam deploy --guided
#Enter
```

以下のようにスタック名を聞かれる。sam-appのままEnter

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

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

        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [sam-app]: 
#Enter
```

リージョンを聞かれる。そのままでもいいが、今回は東京リージョンにしてみる。

```
        AWS Region [us-east-1]: ap-northeast-1
#Enter
```

以下のように```y/n```を聞かれる。すべて```y```(もしくは```Y```)で進める。

```
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        Confirm changes before deploy [y/N]: y #Enter
        #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 #Enter
        HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y #Enter
        Save arguments to configuration file [Y/n]: Y #Enter
```

SAM configuration fileとSAM configuration environmentを聞かれる。デフォルトのままEnterで進める。

```
        SAM configuration file [samconfig.toml]: #Enter
        SAM configuration environment [default]: #Enter 
```

```Creating the required resources...```で少し待つ。

```
        Looking for resources needed for deployment: Not found.
        Creating the required resources...
```

成功すると```Successfully created!```となり、先に進む。
```Waiting for changeset to be created..```で少し待つ。

```
        Successfully created!

                Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-huxxxxxxxxxx
                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 sam-app/4de18c6e8dcbeb886cc6a8xxxxxxxxxx  444598 / 444598  (100.00%)

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

Initiating deployment
=====================
HelloWorldFunction may not have authorization defined.
Uploading to sam-app/7ffc7fb8e4a0ab4fd99f40xxxxxxxxxx.template  1089 / 1089  (100.00%)

Waiting for changeset to be created..
```

```Changeset created successfully.```で変更セットの作成が終わると、最後に```Deploy this changeset?```（変更セットをデプロイしますか？）と聞かれるので、```y```で進める。

```
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                                         ServerlessRestApiDeployment47xxxxxxxxxx       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:xxxxxxxxxxxx:changeSet/samcli-deployxxxxxxxxxx/3b2fxxxx-xxxx-xxxx-xxxx-1fxxxxxxxxxx


Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y #Enter
```

以下のように、CloudFormationスタックのイベントがぬるぬる表示されていく。

```
2021-09-20 01:50:26 - 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::RestApi                      ServerlessRestApi                             -                                           
CREATE_IN_PROGRESS                            AWS::ApiGateway::RestApi                      ServerlessRestApi                             Resource creation Initiated                 
CREATE_COMPLETE                               AWS::ApiGateway::RestApi                      ServerlessRestApi                             -                                           
CREATE_IN_PROGRESS                            AWS::Lambda::Permission                       HelloWorldFunctionHelloWorldPermissionProd    -                                           
CREATE_IN_PROGRESS                            AWS::ApiGateway::Deployment                   ServerlessRestApiDeploymentxxxxxxxxxx         -                                           
CREATE_IN_PROGRESS                            AWS::ApiGateway::Deployment                   ServerlessRestApiDeploymentxxxxxxxxxx         Resource creation Initiated                 
CREATE_IN_PROGRESS                            AWS::Lambda::Permission                       HelloWorldFunctionHelloWorldPermissionProd    Resource creation Initiated                 
CREATE_COMPLETE                               AWS::ApiGateway::Deployment                   ServerlessRestApiDeploymentxxxxxxxxxx         -                                           
CREATE_IN_PROGRESS                            AWS::ApiGateway::Stage                        ServerlessRestApiProdStage                    -                                           
CREATE_IN_PROGRESS                            AWS::ApiGateway::Stage                        ServerlessRestApiProdStage                    Resource creation Initiated                 
CREATE_COMPLETE                               AWS::ApiGateway::Stage                        ServerlessRestApiProdStage                    -                                           
CREATE_COMPLETE                               AWS::Lambda::Permission                       HelloWorldFunctionHelloWorldPermissionProd    -                                           
CREATE_COMPLETE                               AWS::CloudFormation::Stack                    sam-app                                       -                                           
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs                                                                                                                                                                              
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 HelloWorldFunctionIamRole                                                                                                                                        
Description         Implicit IAM Role created for Hello World function                                                                                                               
Value               arn:aws:iam::xxxxxxxxxxxx:role/sam-app-HelloWorldFunctionRole-1KBxxxxxxxxxx                                                                                      

Key                 HelloWorldApi                                                                                                                                                    
Description         API Gateway endpoint URL for Prod stage for Hello World function                                                                                                 
Value               https://69xxxxxxxx.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:sam-app-HelloWorldFunction-mzxxxxxxxxxx                                                                      
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - sam-app in ap-northeast-1

[ec2-user@ip-10-0-0-31%]$
```

```Successfully created/updated stack - sam-app in ap-northeast-1（←設定したリージョン）```と表示されれば、デプロイ成功。

# 4.デプロイの結果を確認する
##### CloudFormation

CloudFormationからスタックを確認すると、以下二つのスタックが作成されている。

- sam-app
- aws-sam-cli-managed-default


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/5374c385-927f-52f4-6a53-2e437f337b48.png)

「sam-app」スタックの出力を見ると、作成されたAPI GatewayのエンドポイントURLリンクが表示されているので、クリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/d10c1e82-8810-22d4-8515-f4e62787438c.png)

ブラウザで以下のように```"message": "hello world"```と表示され、無事アプリケーションが稼働していることが確認できた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/306bbf06-08d6-6931-de36-d96a520a2da8.png)

CloudFormationスタックのリソースを確認する。
「sam-app」スタックのリソースを見ると、以下のリソースが作成されている。

- Lambda
- IAMロール
- API Gateway


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/e3854a0f-585b-952e-0982-38bf764f10d8.png)

「aws-sam-cli-managed-default」スタックのリソースを見ると、以下S3バケットとバケットポリシーが作成されている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/f353b6aa-be66-4175-f1f0-6686f0ff235a.png)

マネジメントコンソールからも、それぞれのリソースが作成されていることが確認できる。

##### S3
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/eb5f3289-d079-9f80-4c20-7f7c67dc145b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/dc5fba33-0b7b-63db-186c-5b7b028f594c.png)


##### IAMロール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/14cc48a2-8d3c-bcd3-2549-ef63d3b44ea1.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/7d677b53-2606-4e6b-78af-8c3ab5653670.png)


##### Lambda
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/2c820615-cc1f-629d-e8e6-24b5d62858ee.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/81413a42-e127-9a45-ecb7-5c9ae7ce1987.png)


##### API Gateway
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1723719/f9671e87-e9be-814d-5c66-5324f4a17d27.png)

# 5.片付け
以下コマンドで「sam-app」スタックを削除する。

```bash
aws cloudformation delete-stack \
  --stack-name sam-app \
  --region ap-northeast-1 #←選択したリージョン
```

これで「Lambda」「IAMロール」「API Gateway」の削除が完了。
もう一つのスタック「aws-sam-cli-managed-default」にはバージョニング有効なS3バケットが含まれているため、S3内のオブジェクトを完全に削除して、S3バケットを空にしてからスタックの削除を実施する。

```bash
#S3バケットを空にしてから実行する。
aws cloudformation delete-stack \
  --stack-name aws-sam-cli-managed-default \
  --region ap-northeast-1 #←選択したリージョン
```

Cloud9環境のEC2インスタンスに「sam-app」ディレクトリが残存するので、これも不要であれば以下コマンドで削除しておく。

```bash
rm -Rf ${HOME}/environment/sam-app
```

CloudWatch LogsもSAM用のロググループが残存しているので不要であれば削除する。

SAM CLIを実行するCloud9環境のEC2インスタンスに紐づいたロールから、不要であれば以下AWS管理ポリシーを剥奪しておく。

- API Gateway（AmazonAPIGatewayAdministrator）
- Lambda（AWSLambda_FullAccess）
- CloudFormation（AWSCloudFormationFullAccess）
- S3（AmazonS3FullAccess）
- IAM（IAMFullAccess）


# 参考
[AWS サーバーレスアプリケーションモデル](https://aws.amazon.com/jp/serverless/sam/)
[チュートリアル: Hello World アプリケーションのデプロイ](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-getting-started-hello-world.html)
[AWS SAMリファレンス](https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-sam-reference.html)
[書籍「ポケットスタディ AWS認定 デベロッパーアソシエイト」](https://www.yamamanx.com/pocketstudy-aws-dva/)