AWS SAMでアプリケーションをローカルテスト＆デプロイ

#はじめに

[こちらの記事](https://qiita.com/Geeryo/items/f05af3d72a808160a007)で作成した環境をテンプレート化したい！と思ったので、
AWS SAMを使ってみました。
SAMはリソースをプロビジョニングできるだけでなく、ローカルでLambda、API Gatewayをテストできる便利ツールだったのでその使い方も紹介します。
途中、cloudformationコマンドとの比較もしていますのでご参考までに。

#環境
* mac OS：Monterey 12.0.1
* AWS CLI：2.2.33
* SAM CLI：1.35.0

#SAMとは
* AWS Serverless Application Modelの略
* サーバーレスアプリケーション構築用のオープンソースフレームワーク
* YAML,JSON形式でテンプレート記述
* CloudFormationの拡張機能で、簡単にテンプレートを記述することが可能
* SAM CLIあり

#SAM CLIのインストール
こちらからどうぞ

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

#SAM利用の流れ

1. SAMテンプレートの記述
1. パッケージング
1. デプロイ




#1. SAMテンプレートの記述

###テンプレート構造

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-specification-template-anatomy.html

###実際のコード

```yaml:template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AWS SAM Serverless demo
Resources:
  TranslateLambda:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: translation-function
      CodeUri: translation-function/
      Handler: translation-function.lambda_handler
      Runtime: python3.8
      Timeout: 5
      MemorySize: 256
      Policies:
        - TranslateFullAccess
      Events:
        GetApi:
          Type: Api
          Properties:
            Path: /translate
            Method: get
            RestApiId:
              Ref: TranslateAPI
  TranslateAPI:
    Type: AWS::Serverless::Api
    Properties:
      Name: translate-api
      StageName: dev
      EndpointConfiguration: REGIONAL
  TranslateDynamoDbTbl:
    Type: AWS::Serverless::SimpleTable
    Properties:
      TableName: tranlate-history
      PrimaryKey:
        Name: timestamp
        Type: String
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
```

###1. リソースタイプの選択
`AWS::Serverless::Function`
`AWS::Serverless::Api`
`AWS::Serverless::SimpleTable`
など

###2. リソースタイプごとに必要なプロパティを記述
AWS::Serverless::Functionであれば、
`Handler`,`Runtime`が必須
`CodeUri`,`InlineCode`のいずれか必須　など

#2. パッケージング　

###デプロイするためにzip化し、S3にアップロードする
- s3パスをSAMテンプレートのCodeUriに記載する必要がある
- パッケージングコマンドで、アプリケーションをzip化し、CodeUriを書き換えている

###パッケージングには2つの方法がある
* cloudformationコマンド
* SAM CLI

##cloudformationコマンド実行
※AWS CLIインストールかつログイン済みであることが前提

```
aws cloudformation package \
--template-file template.yaml \
--s3-bucket serverless-sam-demo \
--output-template-file packaged-template.yaml
```


コマンド実行後、下記ファイルが出力される

```yaml:packaged-template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AWS SAM Serverless demo
Resources:
  TranslateLambda:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: translation-function
      CodeUri: s3://serverless-sam-demo/*************
      Handler: translation-function.lambda_handler
      Runtime: python3.8
      Timeout: 5
      MemorySize: 256
      Policies:
      - TranslateFullAccess
      Events:
        GetApi:
          Type: Api
          Properties:
            Path: /translate
            Method: get
            RestApiId:
              Ref: TranslateAPI
  TranslateAPI:
    Type: AWS::Serverless::Api
    Properties:
      Name: translate-api
      StageName: dev
      EndpointConfiguration: REGIONAL
  TranslateDynamoDbTbl:
    Type: AWS::Serverless::SimpleTable
    Properties:
      TableName: translate-history
      PrimaryKey:
        Name: timestamp
        Type: String
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1

```


パッケージ前

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AWS SAM Serverless demo
Resources:
  TranslateLambda:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: translation-function
      CodeUri: translation-function/
      Handler: translation-function.lambda_handler
```


パッケージ後

```yaml
Resources:
  TranslateLambda:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: translation-function
      CodeUri: s3://serverless-sam-demo/*************
      Handler: translation-function.lambda_handler
```

**先ほどのyamlファイルのcodeuriの表記が変わっていることが確認できます**

さらにS3を見ると、確かにアップされている！
![スクリーンショット 2021-11-28 18.56.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2097948/30d77050-9d89-b5ac-34c7-f81e0643e4c9.png)



##SAM CLIでビルド

コマンドはたったこれだけ

```
sam build
```

実行結果

```
(base) ~~~~~@~~~~~~~~~ sam-serverless-demo % sam build
Building codeuri: /Users/***********/sam-serverless-demo/translate-function runtime: python3.8 metadata: {} architecture: x86_64 functions: ['TranslateLambda']
requirements.txt file not found. Continuing the build without dependencies.
Running PythonPipBuilder:CopySource
Skipping copy operation since source /Users/ohsugiryoya/sam-serverless-demo/translate-function does not exist

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

`.aws-sam/build`が作成され、配下に下記テンプレートファイルが作成される

```yaml:template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AWS SAM Serverless demo
Resources:
  TranslateLambda:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: translation-function
      CodeUri: TranslateLambda
      Handler: translation-function.lambda_handler
      Runtime: python3.8
      Timeout: 5
      MemorySize: 256
      Policies:
      - TranslateFullAccess
      Events:
        GetApi:
          Type: Api
          Properties:
            Path: /translate
            Method: get
            RestApiId:
              Ref: TranslateAPI
  TranslateAPI:
    Type: AWS::Serverless::Api
    Properties:
      Name: translate-api
      StageName: dev
      EndpointConfiguration: REGIONAL
  TranslateDynamoDbTbl:
    Type: AWS::Serverless::SimpleTable
    Properties:
      TableName: tranlate-history
      PrimaryKey:
        Name: timestamp
        Type: String
      ProvisionedThroughput:
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1

```

先ほどと異なりCodeUriがS3リンクになっていないが、バケットとオブジェクトが作成されていました
![スクリーンショット 2021-11-28 19.10.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2097948/87fb3836-3b5f-66c4-a49a-107a33dedac5.png)




#3. デプロイ
デプロイコマンドで、cloudformation本来のテンプレートに変換されリソースが作成される

##cloudformationコマンド実行

```
aws cloudformation deploy \
--template-file ./packaged-template.yaml
--stack-name serverless-sam-demo
--capabilities CAPABILITY_IAM
```

##SAM CLIでデプロイ


インタラクティブに実行することが可能

```
sam deploy --guided
```



```
(base)  sam-serverless-demo % sam deploy --guided

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

        Looking for config file [samconfig.toml] :  Found
        Reading default arguments  :  Success

        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [sam-app-demo]: 
        AWS Region [ap-northeast-1]: 
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
        #変更点の確認
        Confirm changes before deploy [Y/n]: 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
        #Preserves the state of previously provisioned resources when an operation fails
        Disable rollback [Y/n]: y
	    #パブリックに公開する
        TranslateLambda may not have authorization defined, Is this okay? [y/N]: y
        Save arguments to configuration file [Y/n]: y
        SAM configuration file [samconfig.toml]: 
        SAM configuration environment [default]: 

        Looking for resources needed for deployment:
         Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-w09u3i2wjkab
         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
```

できた！！

![スクリーンショット 2021-11-28 19.17.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2097948/94c00e7e-28a3-20e4-926c-ed0131b08fd4.png)


# ローカル環境でテスト実行

SAMを使って感じたことは、**ローカルでテスト実行することができる**点だと感じました。
lambda、api gatewayだけのようなシンプルな構成はローカルテスト可能です。
上記で作成したものはdynamoDBやtranslate apiを使用しているので、ローカル環境ではエラーがでます。

- ソースコードの構文チェック

```shell
sam validate
```

- 関数をローカルで呼び出す

```
sam local invoke
```

- Lambda ローカルテスト

```shell
sam local start-lambda

#別ターミナルで以下コマンド叩く
aws lambda invoke --function-name "TranslateLambda" \
--endpoint-url "http://127.0.0.1:3001" \
--no-verify-ssl out.txt
```


- API Gateway　ローカルテスト

```shell
sam local start-api

#別ターミナルで以下コマンド叩く
curl -w "\n" http://127.0.0.1:3000/translate
```


#（参考）ハマりポイント

- 初回デプロイ時エラーからの再デプロイ

```bash
Initiating deployment
=====================
Uploading to sam-app-demo/ec906befd23684dfd9dd718c1f5d6539.template  1114 / 1114  (100.00%)
Error: Failed to create changeset for the stack: sam-app-demo, An error occurred (ValidationError) when calling the CreateChangeSet operation: Stack:arn:aws:cloudformation:ap-northeast-1:624840664351:stack/sam-app-demo/515b2930-49e5-11ec-865e-06680d75238d is in DELETE_FAILED state and can not be updated.
```

⇨スタックを削除してから再デプロイする

```bash
aws cloudformation delete-stack --stack-name hogehoge
```

- FAILEDとなった時の対処法

```bash
aws cloudformation describe-stack-events \
--stack-name sam-app-demo \
| jq '.StackEvents[] \
| select(.ResourceStatus == "CREATE_FAILED") \
| .ResourceStatusReason' -r
```