【AWS】AWS SAMが想像以上にバケモノかもしれないことに気づいてしまったかもしれない

どうもこんにちは。

今回は、AWSのセミナー内でお話に出てきた、AWS SAMについて調べたのでまとめてみました。

ちなみに私、感動しております。

# AWS SAMってなに？

**AWS SAM (Serverless Application Model)** は、サーバーレスアプリケーションをローカルで開発、テスト、デプロイするための強力なオープンソースフレームワークです。AWS SAMを使用するには、AWS SAM CLIというものをインストールする必要があります。

ちなみに、AWS SAM自体は無料で使用できます。AWS SAMを使用して作成したAPI GatewayやLambda関数に対してのみ料金が発生します。

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

# 通常、サーバーレスアプリケーションを作成するにはどうする？

通常では、以下のような構成でサーバーレスアプリケーションを構築すると思います。（最も簡単な構成）

![スクリーンショット 2024-08-31 15.23.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2711491/fed15b56-3697-1986-f0c8-9a8fbcc3dd30.png)

ここに、`CloudFront`や`Amazon SQS`、`S3`を組み込んだりすると思います。（これは一旦おいときまして。）

上記の構成を実現するのに、以下の手順を踏む必要があるはずです。

1. AWSマネジメントコンソール上でDynamoDBを作成する
1. AWSマネジメントコンソール上でLambda関数を作成する
1. AWSマネジメントコンソール上でAPI Gatewayのメソッドを作成し、URLを発行する

これって全て、AWSマネジメントコンソール上で行ったり来たりする必要があります。（めんどくさい...）

# AWS SAMのお出ましだっ!

AWS SAMを使用すると、1つのYAMLファイルに定義するだけで、勝手にLambda関数などを作ってくれます。

# AWS SAMの使用方法

## 1. AWS SAM CLIのインストール

インストール方法は、`Windows`,`MacOS`,`Linux`によって変わります。
以下を参照してインストールしてください。（自分はパッケージインストーラ（GUI）を使用しました。）

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

以下が正常に実行されたら、インストール完了です。

```sh
$ which sam

$ sam --version
```

## 2. サーバーレスアプリケーションプロジェクトの作成

ターミナルで以下を実行していきます。

```sh
$ mkdir AWS_SAM_test && cd AWS_SAM_test
$ sam init
```

`sam init`を実行すると対話形式で必要なもの、不要なものを入力して行くことになります。

ここは1つ1つ解説します。

### 2-1. AWSのテンプレート or カスタムテンプレート

```sh
You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

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

初めて使うので、「1」を入力します。

### 2-2. どのAWSテンプレートを使う？

```sh
Choose an AWS Quick Start application template
        1 - Hello World Example
        2 - Data processing
        3 - Hello World Example with Powertools for AWS Lambda
        4 - Multi-step workflow
        5 - Scheduled task
        6 - Standalone function
        7 - Serverless API
        8 - Infrastructure event management
        9 - Lambda Response Streaming
        10 - Serverless Connector Hello World Example
        11 - Multi-step workflow with Connectors
        12 - GraphQLApi Hello World Example
        13 - Full Stack
        14 - Lambda EFS example
        15 - DynamoDB Example
        16 - Machine Learning
Template:
```

今回は、Lambdaを使ってみようと思うので、「１」を入力します。

### 2-3. どのランタイムとパッケージを使用する？

```sh
Use the most popular runtime and package type? (Python and zip) [y/N]: 
```

デフォルトでよく使われるランタイムとパッケージタイプを提案してくれていますね。
`python`と`zip`を使用してくれそうなので、「y」を入力します。

### 2-4. アプリケーションのトレース機能を使用する？

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

トレース機能を使用するか聞かれています。最初は必要ないので、「N」を入力します。

### 2-5. CloudWatchを使用する？

```sh
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]:
```

CloudWatchでログのモニタリングを使用するか聞かれていますね。今回は必要ないので「N」を入力します。

### 2-6. 関数のログをJSON形式で出力しますか？

```sh
Would you like to set Structured Logging in JSON format on your Lambda functions?  [y/N]:
```

関数のログを JSON 形式で出力する機能を有効にするか聞かれています。
これはどっちでも良いですが、Yesにすると追加料金がかかってしまう恐れがあるので注意が必要です。

### 2-7. プロジェクトの名前

```sh
Project name [sam-app]: sam-test-01
```

プロジェクト名を設定します。今回は、`sam-test-01`とします。

### 2-8. 完了！

```sh
    -----------------------
    Generating application:
    -----------------------
    Name: sam-test-01
    Runtime: python3.9
    Architectures: x86_64
    Dependency Manager: pip
    Application Template: hello-world
    Output Directory: .
    Configuration file: sam-test-01/samconfig.toml
    
    Next steps can be found in the README file at sam-test-01/README.md
        

Commands you can use next
=========================
[*] Create pipeline: cd sam-test-01 && sam pipeline init --bootstrap
[*] Validate SAM template: cd sam-test-01 && sam validate
[*] Test Function in the Cloud: cd sam-test-01 && sam sync --stack-name {stack-name} --watch
```

これで完了です。

## 3. template.yamlを編集

`sam init`実行時点で、以下のファイルが作成されます。スゲーですな。

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-test-01

  Sample SAM Template for sam-test-01

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3
    MemorySize: 128

    # You can add LoggingConfig parameters such as the Logformat, Log Group, and SystemLogLevel or ApplicationLogLevel. Learn more here https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#sam-function-loggingconfig.
    LoggingConfig:
      LogFormat: JSON
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.9
      Architectures:
      - x86_64
      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
```

今回は、以下のように変更します。

- Lambda関数の名前を`SamTest01Func`に変更します
    - `Resources.HelloWorldFunction`を`Resources.SamTest01Func`にします
- 関数のタイプアウト時間を10秒にしたいので、`Globals.Function.Timeout`の値を10にします
- `Resources.SamTestFunc.Properties.Runtime`をPython3.12に変更します

## 4. template.yamlに問題がないか確認

`sam validate`を実行すると、yamlファイルに問題がないか確認してくれます。

今回は、Lambda関数の名前を`SamTest01Func`に変更したとき、他の箇所も変更を加える必要があったようでした。

```sh
% sam validate  
[[E6101: Validate that outputs values are a string] ('HelloWorldFunction' is not one of ['SamTestFunc', 'SamTestFuncRole', 'SamTestFuncHelloWorldPermissionProd', 'ServerlessRestApi', 'ServerlessRestApiDeployment<>', 'ServerlessRestApiProdStage']) matched 42, [E6101: Validate that outputs values are a string] ('HelloWorldFunctionRole' is not one of ['SamTestFunc', 'SamTestFuncRole', 'SamTestFuncHelloWorldPermissionProd', 'ServerlessRestApi', 'ServerlessRestApiDeployment<>', 'ServerlessRestApiProdStage']) matched 45]
Error: Linting failed. At least one linting rule was matched to the provided template.
```

修正して再度挑戦！

```sh
% sam validate
/Users/testuser/code/AWS_SAM_test/sam-test-01/template.yml is a valid SAM Template
```

OKでした！

## 5. Lambda関数を編集

`sam-test-01/hello_world/app.py`にというファイルが作成されているので、開きます。
コメントアウトでたくさん書かれていますが、以下のように、`hello world, sam-test-01`とログが出力されるようにします。

```py
import json

def lambda_handler(event, context):
  return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world, sam-test-01",
        }),
    }
```

これでプロジェクトの作成は完了です。

## 6. プロジェクトをローカルでビルドしてみる

`sam build`を実行したらローカルでビルドできるっぽいです。

```sh
% sam build
Starting Build use cache                                                               

Manifest file is changed (new hash: <ハッシュ値>) or dependency folder (.aws-sam/deps/<何かの文字列>) is missing for (SamTestFunc), downloading     
dependencies and copying/building source                                               

Building codeuri: /Users/testuser/code/AWS_SAM_test/sam-test-01/hello_world runtime: python3.12 architecture: x86_64 functions: SamTestFunc

Running PythonPipBuilder:CleanUp

Running PythonPipBuilder:ResolveDependencies

Running PythonPipBuilder:CopySource

Running PythonPipBuilder:CopySource

Build Succeeded

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

Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided
```

うまくいったっぽいっすね。これで、ローカルでLambda関数のテストを実行する準備ができました。

## 7. ローカルでテストしてみます

`sam local invoke`を実行すると、Lambda関数のテストをしてくれます。
Lambda関数のテストというのは、以下のスクショの赤い四角部分のボタンを押した時に実行されるテストのことです。

![スクリーンショット 2024-08-31 18.52.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2711491/a0b93940-73da-39a7-6737-303d390e1000.png)

実際に実行してみると、以下のようになっています。

```sh
% sam local invoke
Invoking app.lambda_handler (python3.12)
Local image was not found.
Removing rapid images for repo public.ecr.aws/sam/emulation-python3.12
Building image...................................................................................................................................................................................................................................................................................................
Using local image: public.ecr.aws/lambda/python:3.12-rapid-x86_64.                     

Mounting /Users/testuser/code/AWS_SAM_test/sam-test-01/.aws-sam/build/SamTestFunc as /var/task:ro,delegated, inside runtime container
START RequestId: <リクエストID> Version: $LATEST
END RequestId: <リクエストID>
REPORT RequestId: <リクエストID>  Init Duration: 0.26 ms  Duration: 185.90 ms     Billed Duration: 186 ms Memory Size: 128 MB     Max Memory Used: 128 MB
{"statusCode": 200, "body": "{\"message\": \"hello world, sam-test-01\"}"}
```

手順5で`app.py`を修正して、`hello world, sam-test-01`という文字列を出力するように修正しましたが、ちゃんと出力されていますね！

## 8. ローカルでWebサーバーを立ち上げる

`sam local start-api`を実行するとWebサーバーが立ち上がってくれます。

```sh
% sam local start-api
Initializing the lambda functions containers.
Local image is up-to-date
Using local image: public.ecr.aws/lambda/python:3.12-rapid-x86_64.                     

Mounting /Users/testuser/code/AWS_SAM_test/sam-test-01/.aws-sam/build/SamTestFunc as /var/task:ro,delegated, inside runtime container                                       
Containers Initialization is done.
Mounting SamTestFunc 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. If you used sam build before running local commands, you will need to re-run sam build for the changes to be picked up. You only need to restart SAM CLI if you      
update your AWS SAM template
2024-08-31 19:02:09 WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:3000
2024-08-31 19:02:09 Press CTRL+C to quit
```

試しにブラウザで`http://127.0.0.1:3000`にアクセスしてみると、以下のように表示されました。

```
{
  "message": "Missing Authentication Token"
}
```

こういうものなのかな？わかる人いたら教えてください。

また、`Ctrl + C`でサーバーを止めることができます。

## 9. Pythonコードのテストを実行

Webサーバーを立ち上げている状態であれば、`python -m pytest <相対パス>`でPytestの実行もできるようです。

```sh
% python -m pytest tests/unit/test_handler.py
======================================================================== test session starts ========================================================================
platform darwin -- Python 3.11.3, pytest-8.3.2, pluggy-1.5.0
rootdir: /Users/testuser/code/AWS_SAM_test/sam-test-01
collected 1 item                                                                                                                                                    

tests/unit/test_handler.py .                                                                                                                                  [100%]

========================================================================= 1 passed in 0.01s =========================================================================
```

## 10. AWS環境にデプロイ

`sam deploy`コマンドを実行すると、デプロイしてくれます。

また、変更内容を詳しく確認したい場合は、sam deploy --guidedオプションを使って対話形式でデプロイを進めると、変更内容を一つずつ確認できます。

途中で、`Deploy this changeset? [y/N]:`というメッセージが出てきますが、そのままデプロイを進める場合や「y」を入力してください。

```sh
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key                 SamTestFunc
Description         Hello World Lambda Function ARN
Value               <lambdaのURL>                                                          

Key                 HelloWorldApi
Description         API Gateway endpoint URL for Prod stage for Hello World function
Value               <API GatewayのURL>

Key                 SamTestFuncIamRole
Description         Implicit IAM Role created for Hello World function
Value               <IAMロールのURL>
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------


Successfully created/updated stack - sam-test-01 in ap-northeast-1
```

## 11. API GatewayのURLへアクセス

デプロイした時にOutputsに出力されるAPI GatewayのURLに、ブラウザでアクセスします。

![スクリーンショット 2024-08-31 19.22.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2711491/7ecd1cf0-647e-d942-ae58-109f58d987a5.png)


アクセスできたー！

ほとんど言われるがままでできちゃいました...
すげー

## 12. デプロイしたやつを削除する

`sam delete`を実行することで、API GatewayやLambda関数を削除できます。また、自動で作成されたIAMロールやS3バケットも削除されます。

# まとめ

`template.yml`の書き方がマスターできれば、サーバーレスアプリケーションの構築が簡単にできてしまいますね！

また、**AWS Application Composer** というツールを使用すると、`template.yml`をGUI上で作成することができるそうです。これは、VScodeの拡張機能`AWS Toolkit for visual studio code`をインストールすることで使用することができます。

https://aws.amazon.com/jp/application-composer/

https://aws.amazon.com/jp/visualstudiocode/

早速試してみます。別の記事で！