AWS SAMを使用してLambda+ApiGatewayのAPIをデプロイ

## SAM(Serverless Application Model)とは
サーバーレスアプリケーション構築用のオープンソースフレームワーク
https://aws.amazon.com/jp/serverless/sam/

簡単にまとめると
* Lambda開発の手間を削減
* ローカル上でLamabdaを再現してテスト、デバッグができる
* SAM 構文のテンプレートでサーバーレスアプリケーションの構築を高速化

## 実行環境
Windowsで実施
AWS SAMを使用するためのCLIをインストール

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

```
$ sam --version
SAM CLI, version 1.94.0
```
前提
Ubuntu 環境導入済み
Docker 導入済み
AWS CLI 導入済み

## プロジェクト作成
`sam init`コマンドでアプリケーションの初期設定
`--runtime`オプションでLamabdaのランタイムを設定(今回はpython3.9)

```
$ cd [作業ディレクトリ] #カレントディレクトリ配下にプロジェクトが作成されるためあらかじめ移動しておく

$ sam init --runtime python3.9
Which template source would you like to use?
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1

Choose an AWS Quick Start application template
        1 - Hello World Example
        2 - Hello World Example with Powertools for AWS Lambda
        3 - Infrastructure event management
        4 - Multi-step workflow
        5 - Lambda EFS example
        6 - Serverless Connector Hello World Example
        7 - Multi-step workflow with Connectors
Template: 1

Based on your selections, the only Package type available is Zip.
We will proceed to selecting the Package type as Zip.

Based on your selections, the only dependency manager available is pip.
We will proceed copying the template using pip.

Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]: n

Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]: n

Project name [sam-app]: sample-app

    -----------------------
    Generating application:
    -----------------------
    Name: sample-app
    Runtime: python3.9
    Architectures: x86_64
    Dependency Manager: pip
    Application Template: hello-world
    Output Directory: .
    Configuration file: sample-app\samconfig.toml

    Next steps can be found in the README file at sample-app\README.md


Commands you can use next
=========================
[*] Create pipeline: cd sample-app && sam pipeline init --bootstrap
[*] Validate SAM template: cd sample-app && sam validate
[*] Test Function in the Cloud: cd sample-app && sam sync --stack-name {stack-name} --watch
```
デプロイ確認までしたいので今回は`1 - Hello World Example`を選択し、ガイド通りに進みプロジェクト名を入力


プロジェクトディレクトリが作成されているのでカレントディレクトリを移動&構造を確認
(今後コマンドはsample-app/ で実施)
```
sample-app
│  .gitignore
│  README.md
│  samconfig.toml
│  template.yaml #samのテンプレートファイル
│  __init__.py
│
├─events
│      event.json #テストで使用するイベント
│
├─hello_world #アプリのディレクトリ
│      app.py 
│      requirements.txt
│      __init__.py
│
└─tests
    │ testsは省略
```
template.yamlのResourcesセクション
```yaml:template.yaml
Transform: AWS::Serverless-2016-10-31
```
SAMのテンプレートでは上記のTransform宣言がされている


template.yamlのResourcesセクション

```yaml:template.yaml
Resources:
  HelloWorldFunction: #リソース名 ※CFN内でのみ使用される名前
    Type: AWS::Serverless::Function #リソースの種別はLambda関数
    Properties: #リソースの設定
      CodeUri: hello_world/ #アプリソースのディレクトリ
      Handler: app.lambda_handler #Lambdaハンドラーの指定
      Runtime: python3.9
      Architectures:
        - x86_64
      Events: #関数をトリガーするイベントのを指定
        HelloWorld: #イベント名　※CFN内でのみ使用される名前
          Type: Api #イベントの種別
          Properties: #イベントの設定
            Path: /hello #呼び出される URI パス
            Method: get #HTTPメソッド
```

## テンプレートを検証
SAMテンプレートが有効かどうか検証
```
$ sam validate
template.yaml is a valid SAM Template
```
検証結果問題なし

試しにサポートされていないリソースタイプで実施
```diff_yaml:template.yaml
Resources:
  HelloWorldFunction:
-    Type: AWS::Serverless::Function
+    Type: AWS::Serverless::ValidateTest
```
```
$ sam validate
E3038 Serverless Transform required for Type AWS::Serverless::ValidateTest for resource HelloWorldFunction

Error: Linting failed. At least one linting rule was matched to the provided template.
```
template.yamlを変更してもビルドやデプロイ前に構文エラーを発見できるので初めのうちはお世話になりそう👍

## ビルド
```
$ sam build
```
成功するとビルドされたモジュールが`.aws-sam/` に生成


## テスト(省略可)

まずは期待される結果を確認
{"message": "hello world"}をボディとしてstatusCode: 200で返すアプリ
```python:app.py
import json

def lambda_handler(event, context):

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world",
        }),
    }
```
Lambda関数のローカル実行してテスト実施
```
$ sam local invoke
Invoking app.lambda_handler (python3.9)
Local image is up-to-date
Using local image: public.ecr.aws/lambda/python:3.9-rapid-x86_64.

Mounting ~~~\sample-app\.aws-sam\build\HelloWorldFunction as /var/task:ro,delegated, inside runtime container
START RequestId: 011dd512-cd77-4599-8ed8-0a4d88403790 Version: $LATEST
END RequestId: 011dd512-cd77-4599-8ed8-0a4d88403790
REPORT RequestId: 011dd512-cd77-4599-8ed8-0a4d88403790  Init Duration: 0.32 ms  Duration: 185.27 ms     Billed Duration: 186 ms Memory Size: 128 MB     Max Memory Used: 128 MB
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}
```
START RequestIdの行からREPORT RequestIdの行までがLambdaが出力したログ
最後の{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}がレスポンス内容

###### ローカルでapigwを起動して実際にAPIを叩いてみる
```
$ sam local start-api --port 6000
~
~
~
 * Running on http://127.0.0.1:6000
2023-08-05 18:19:47 Press CTRL+C to quit
```
最後の2行がこのように表示されたらローカルでapiを叩く準備は完了

template.yamlでapiの設定値は下記だったのでcurlコマンドを用意してテスト実施
Path: /hello
Method: get
```
$ curl http://127.0.0.1:6000/hello
{"message": "hello world"}
```
期待通りの内容が出力を確認

## パッケージング
```
$ sam package --template-file template.yaml --output-template-file packaged.yaml
~
Successfully packaged artifacts and wrote output template to file packaged.yaml.
~
```
sam package
--template-file テンプレートファイルを指定
--output-template-file デプロイ用に出力されるテンプレートファイル名を指定

Successfully packaged ~~~　が表示されていたら成功

## デプロイ
```
$ sam deploy --template-file packaged.yaml --stack-name sample-app-stack --region ap-northeast-1 --guided
~
Stack Name [sample-app-stack]:#そのままEnter
AWS Region [ap-northeast-1]:#そのままEnter
#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]: n
The resource AWS::Serverless::Function 'HelloWorldFunction' has specified S3 location for CodeUri. It will not be built and SAM CLI does not support invoking it locally.
        Save arguments to configuration file [Y/n]: y
        SAM configuration file [samconfig.toml]:#そのままEnter
        SAM configuration environment [default]:#そのままEnter
~
~
Deploy this changeset? [y/N]: y
~
~
Successfully created/updated stack - sample-app-stack in ap-northeast-1
```
sam deploy
--template-file パッケージングで出力されたファイルを指定
--stack-name スタック名を指定
--region リージョンを指定
--guided ガイド付きでデプロイ

Successfully ~~~　が最後に出力されていれば完了

コマンドライン上ではstackの差分やデプロイ状況など表示されている。
ローカル上での操作が完了したのでAWSコンソールでCloudFormationの画面を開きスタックのステータスを確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/801158/c3624dcd-d337-141f-31fa-fa690fbef845.png)

また、スタックごと削除すれば紐づいているリソースなどを綺麗に削除できる
逆にスタックで消さないとめんどくさくなる?

## 動作確認
API Gatewayから作成されたAPIのProdステージのURLを取得
https://*****.execute-api.[リージョン].amazonaws.com/Prod[path]
[path]は/helloにしてGetメソッドでapiを叩く
```
curl https://*****.execute-api.[リージョン].amazonaws.com/Prod/hello

StatusCode        : 200
StatusDescription : OK
Content           : {"message": "hello world"}
~
~
~
```
レスポンス内容はAPI Gatewayの設定により変更されているが{"message": "hello world"}とレスポンス自体を確認できたので動作確認完了