SAM初心者がSAMを使ってみて便利だと思った点

# はじめに
本記事はANGEL Dojo参加者によるアドベントカレンダー23日目の記事です｡
よかったら他の記事もチェックしてみてください!

https://qiita.com/organizations/2024-angel-dojo

今回はAWS SAM（以下SAM）初心者である私が､SAMを使ってみて便利だと感じた点を挙げたいと思います｡私と同じようにSAMを使ったことがない､使ってみたい方を対象としており､初心者向けの内容となっています｡

# そもそもAWS SAMって?
SAMとは､Serverless Application Modelの略であり､AWS上でサーバーレスアーキテクチャを構築するためのIaCフレームワークです｡
AWSインフラをコード化するには､AWS CloudFormationや､AWS CDK､Terraformあたりがよく使われています｡SAMは前述のものとは異なり､サーバーレスアーキテクチャに特化したIaCフレームワークとなります｡

AWSでサーバーレスアーキテクチャを構築する際は､このSAMを使うことで開発スピードが格段に上がります｡
今回はANGEL Dojoで実際にSAMを使って見て便利だと思った点を5点紹介します｡

## SAMの使い方
まず簡単にですがSAMの使い方です｡

インフラのデプロイやLambda関数のテストなどはSAM CLIというコマンドラインで行います｡そのSAM CLIをインストールする必要があります｡またAWSリソースをデプロイするので､デプロイのための権限も準備してあげる必要があります｡
詳しくは以下を参考にしてみてください｡

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

SAM CLIの一つに`sam init`コマンドがあります｡このコマンドはAWS側が用意してくれているSAMテンプレートを呼び出して､ディレクトリの雛形を作成することができます｡

今回はpython3.12で､パッケージマネージャーとしてpipを指定して､hello-worldテンプレートを使います｡
```
sam init \
    --app-template hello-world \
    --dependency-manager pip \
    --name my-sam-app \
    --runtime python3.12 \
    --no-interactive
```

このコマンドを実行すると以下のようなディレクトリとファイルが作成されます｡

```
.
└── my-sam-app
    ├── README.md
    ├── __init__.py
    ├── events
    │   └── event.json
    ├── hello_world
    │   ├── __init__.py
    │   ├── app.py
    │   └── requirements.txt
    ├── samconfig.toml
    ├── template.yaml
    └── tests
        ├── __init__.py
        ├── integration
        │   ├── __init__.py
        │   └── test_api_gateway.py
        ├── requirements.txt
        └── unit
            ├── __init__.py
            └── test_handler.py
```

AWSインフラを定義しているSAMテンプレートは､`template.yaml`で､以下が作成されます

```
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  my-sam-app

  Sample SAM Template for my-sam-app

# 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.12
      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

```

SAMはCloudFormationの拡張となっており

```
Transform: AWS::Serverless-2016-10-31
```
を記述することで使うことができます｡

このテンプレートでは以下の構成図のインフラを作成しています｡
![構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1573132/bc6b64af-4dc4-8436-25d7-4e5a676b8345.png)
API Gatewayに`/hello`へGETリクエストを送信し､Lambdaからhello-worldが返されるAPIを構築しています｡



SAMテンプレートのファイル以外にも､テストで使用するイベントが入っている`events`ディレクトリやテスト用の`test`ディレクトリ､SAMの設定を入れておく`samconfig.toml`､Python特有の`__init__.py`などが作成されています｡

Lambdaのコードは`hello_world`ディレクトリに存在し､テンプレートでは以下のようにディレクトリを指定します｡
```
      CodeUri: hello_world/
```


コードの詳細は適宜以下のGitHubを参照してください｡

https://github.com/aws/aws-sam-cli-app-templates/tree/master/python3.12/hello/%7B%7Bcookiecutter.project_name%7D%7D


## SAMを使ってみて便利だと感じた点

### 1. 簡略化された記述
#### 少ない記述量
SAMを使うことで少ない記述量でインフラを定義することができます｡
SAMはサーバーレスに特化しているので､サーバーレスアーキテクチャでよく使われるAWSサービスは簡単に書くことができます｡

例えば､Lambda関数をCloudFormationで定義するには､
```
Type: AWS::Lambda::Function
```
を使用しますが､SAMでは
```
Type: AWS::Serverless::Function
```
を使用して､Lambdaを定義します｡

これを使うことで､`Properties`の一つの`Events`を使うことができます｡
今回のようなAPI GatewayへのGETリクエストをトリガーとしてLambdaをキックしたい場合､CloudFormationではリソースを一対一で記述する必要があるので､API Gatewayも`AWS::ApiGateway::RestApi`で定義してあげる必要があります｡

ですが､SAMの`Events`を使うことで､以下のようにAPI Gatewayを明示的に定義してあげる必要がありません｡
```
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
```
Lambdaを定義する箇所でトリガーとなるパスとメソッドを指定するだけでよいので､API Gatewayも記述する必要があるCloudFormationに比べて記述量が格段に少なくなります｡

SAMテンプレートで使用できる
```
Type: AWS::Serverless::
```
は現在API GatewayやStep Functionsといった9サービスに対応しています｡

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-specification-resources-and-properties.html

#### Globalセクション
複数のLambda関数でタイムアウト値やランタイムバージョンなどを統一させたい場合は､Globalセクションを使うことができます｡
```
Globals:
  Function:
    Timeout: 3
```

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

CloudFormationであればParameterを使用して､変数化する必要がありましたが､
SAMではGlobalセクションに設定値を指定するだけで､テンプレートに設定値を反映させることができます｡

#### 権限周り

Lambdaが使用する権限も簡単に書くことができます｡
先ほど伝えた通りCloudFormationは一対一で記述する必要があるので､Lambda用のIAM Roleも定義する必要がありますが､SAMではRoleも自動で作成してくれます｡

よく使う権限がポリシーテンプレートとしてまとまっているので､例えば､｢DynamoDBの特定のテーブルに対するCRUD処理に必要な権限｣も以下のように書くことができ､AWS APIを一つ一つ指定する必要がないのでとても便利です｡

```
      Policies:
        DynamoDBCrudPolicy: 
          TableName: !Ref TableName
```

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-policy-template-list.html

以上のように､SAMを使うことで記述量が格段に少なくなります｡

### 2. ローカルでのテストのしやすさ
一般的な開発の流れは､開発→テスト→リリースだと思います｡
少ない記述量で開発スピードを素早くするだけでなく､ローカルでのテストも簡単に実施することができます｡

ローカルでのLambdaのテストしたい場合は､

```
sam local invoke
```
コマンドを使用します｡イメージとしては､`aws lambda invoke`コマンドのローカル版でしょうか｡

:::note warn
このコマンドを実行するにはdockerが必要です｡
:::

Lambdaが実行時に受け取るeventは､API Gatewayからjsonで渡されるので､テスト用としてそのjsonを用意してあげるだけでローカルでも実行することができます｡


`sam init`で作成したテンプレートには､テスト用イベントがあるので､それを指定してテストしてみます｡

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

```        
xxx@xxx my-sam-app % sam local invoke HelloWorldFunction -e events/event.json                         
Invoking app.lambda_handler (python3.12)                                                                                                                                                                                                                                        
Local image is up-to-date                                                                                                                                                                                                                                                       
Using local image: public.ecr.aws/lambda/python:3.12-rapid-x86_64.                                                                                                                                                                                                              
                                                                                                                                                                                                                                                                                
Mounting /Users/xxx/Desktop/blog/my-sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside runtime container                                                                                                                                        
START RequestId: c67408da-3498-4cfd-98de-30730e4a2fdd Version: $LATEST
END RequestId: 31216613-8c6b-4a00-9170-d6d33dbe91a4
REPORT RequestId: 31216613-8c6b-4a00-9170-d6d33dbe91a4  Init Duration: 0.41 ms  Duration: 284.04 ms     Billed Duration: 285 ms Memory Size: 128 MB     Max Memory Used: 128 MB
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}
```

dockerコンテナが起動され､無事`hello world`が返されています｡

またテスト用のeventを作成するためのコマンドもあるので､対象のアーキテクチャにあったイベントも簡単に作成することができます｡
```
sam local generate-event 
```



Lambda内でDynamoDBの処理を実行してる場合は､DynamoDB localを使うことで､ローカル環境でもDynamoDBを使うことができます｡

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DynamoDBLocal.html



### 3. デプロイも容易
テストが完了したあとはAWSへデプロイを行います｡このデプロイ作業もSAMを使うことで簡単に行うことができます｡


SAMを使わない場合は､まず外部ライブラリをpipでインストールして､同じZipパッケージに固めてからデプロイします｡SAMを使うことで外部ライブラリのインストールからデプロイまで行ってくれます｡

Lambdaの起動方法として､dockerを指定している場合はイメージのビルドを行ってくれます｡



:::note warn
Lambdaで使用するコードが3MBを超えるとコンソールに表示されないので､不都合がある場合は適宜Lambda Layerの利用を検討してください
:::


まずはデプロイ前の準備としてライブラリのインストールなどを行ってくれるコマンドを実行します｡


```
sam build
```

実行が完了すると`.aws-sam`ディレクトリが作成され､`.aws-sam/build/HelloWorldFunction`
に作成したLambdaコードと合わせて外部パッケージがインストールされます｡

このあと､以下のコマンドでデプロイします｡
```
sam deploy
```

```
Successfully created/updated stack - my-sam-app in us-east-1
```

上のようにコマンドが成功すると､正常にCloudFormation Stackが作成されており､Lambda関数やAPI Gatewayが作成されています｡

![CFn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1573132/0660b0ea-0c4b-b257-3625-01de76bf5b6b.png)



このようにAWS環境への簡単にデプロイすることができます｡


AWS環境にデプロイされたLambdaでのテストも以下のコマンドで可能です｡
```
sam remote invoke HelloWorldFunction --stack-name my-sam-app
```

テンプレートのアウトプットからURLを参照してリクエストでもできます｡
```
curl -X GET  $(sam list endpoints --output json | jq -r '.[1].CloudEndpoint.[0]')/hello
```



### 4. サクッとAWSへデプロイ
開発しているときに､ちょっとコードを修正して､サクッとAWS環境で確認したいといったこともよくあると思います｡
そのときには､`sam sync`コマンドが素晴らしいです｡

コマンドの通り､ローカルのコードとAWS環境を同期してくれるコマンドです｡

このコマンド実行中はコードの変更を監視して､コードの修正ならコードをアップロードしてくれたり､インフラの変更ならインフラデプロイしてくれます｡

`sam sync`コマンドを実行後､Lambdaコードを修正すると､AWS環境にすぐにコードを反映してくれます｡

```
   Syncing Lambda Function HelloWorldFunction...
   Manifest is not changed for (HelloWorldFunction), running incremental build
   Building codeuri: /Users/xxx/Desktop/blog/my-sam-app/hello_world runtime: python3.12 architecture: x86_64
   functions: HelloWorldFunction
   Running PythonPipBuilder:CopySource
   23/Sep/2024:14:46:07: Finished syncing Lambda Function HelloWorldFunction.
```
Lambdaのコード修正くらいであれば､体感10秒ほどで同期してくれます｡

常にAWS環境にデプロイしながら開発を進めることができるので､サクッと確認したいときや開発初期段階での開発スピード爆上がり間違いなしです｡



### 5. sam pipeline
個人だけの開発であれば`sam sync`でだけでもいいですが､複数人でのチーム開発になるとちゃんとCI/CDパイプラインを構築する方がよいです｡
パイプラインファーストという考え方もあるので初期段階から構築しておきましょう｡SAMにはCI/CDパイプラインを構築するためのコマンドもあります｡

```
sam pipeline
```
長くなるのでここでは詳細は書きませんが､AWS Code系サービスやGHA､Jenkisなど主要なパイプライン環境に対応しています｡
コマンド一つだけでCode系サービスをデプロイしてくれたり､`buildspec.yaml`や`/.github/workflows/pipeline.yaml`といったファイルも作成してくれるので､非常に便利です｡

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/deploying-cicd-overview.html


また本番環境と開発環境などをマルチアカウントで分割している場合でも､SAMの設定ファイルである`samconfig.toml`の中でパラメータを出し分けることで対応可能です｡


## 最後に
いかがでしたでしょうか｡
ここに記載した内容は基本的な内容なので､ぜひ習得してほしいです｡`sam sync`などは実際に使ってみないとその良さがわからないので､ぜひテンプレートで遊んでみてください｡