🐿️

AWS SAM (Serverless Application Model) の始め方

2023/08/15に公開

AWS SAM とは

AWS SAM は AWS 上でサーバーレスアプリケーションを構築するための IaC (Infrastructure as Code) ツールです。
テンプレート仕様と CLI から構成されます。

sam_squirrel.jpg

  • テンプレート仕様
    • CloudFormation テンプレートの拡張版です。
    • サーバーレスな AWS リソースをより簡潔に記述できるようになっています。
  • CLI
    • 以下のようなコマンドが用意されています。
    • sam init
      • SAM アプリケーションの雛形構築
    • sam build
      • Lambda関数コードのビルド、SAMテンプレートをデプロイ用に変換するなど、ワークフローの後続ステップのための準備
    • sam validate
      • SAMテンプレートの検証
    • sam deploy
      • AWS環境へのデプロイ
    • sam local
      • ローカル環境での動作確認

環境構築

以下を準備します。

SAM のワークフロー

以降、チュートリアル: Hello World アプリケーションのデプロイ - AWS Serverless Application Model をベースにワークフローを確認します。

雛形からの IaC ベース作成 (sam init)

sam init 開始
$ sam init

 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

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

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 - Hello World Example With Powertools
 16 - DynamoDB Example
 17 - Machine Learning
Template: 1
Lambdaのランタイムとパッケージタイプを選択
Lambdaのランタイムとパッケージタイプを選択
Use the most popular runtime and package type? (Python and zip) [y/N]: N

Which runtime would you like to use?
 1 - aot.dotnet7 (provided.al2)
 2 - dotnet6
 3 - go1.x
 4 - go (provided.al2)
 5 - graalvm.java11 (provided.al2)
 6 - graalvm.java17 (provided.al2)
 7 - java17
 8 - java11
 9 - java8.al2
 10 - java8
 11 - nodejs18.x
 12 - nodejs16.x
 13 - nodejs14.x
 14 - python3.9
 15 - python3.8
 16 - python3.7
 17 - python3.11
 18 - python3.10
 19 - ruby3.2
 20 - ruby2.7
 21 - rust (provided.al2)
Runtime: 11

What package type would you like to use?
 1 - Zip
 2 - Image
Package type: 1

Based on your selections, the only dependency manager available is npm.
We will proceed copying the template using npm.
各種オプション
言語オプション
Select your starter template
 1 - Hello World Example
 2 - Hello World Example TypeScript
Template: 2
X-ray, CloudWatch Application Insights オプション
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]:
sam init 終了
Cloning from https://github.com/aws/aws-sam-cli-app-templates (process may take a moment)

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Runtime: nodejs18.x
    Architectures: x86_64
    Dependency Manager: npm
    Application Template: hello-world-typescript
    Output Directory: .
    Configuration file: sam-app/samconfig.toml

    Next steps can be found in the README file at sam-app/README.md


Commands you can use next
=========================
[*] Create pipeline: cd sam-app && sam pipeline init --bootstrap
[*] Validate SAM template: cd sam-app && sam validate
[*] Test Function in the Cloud: cd sam-app && sam sync --stack-name {stack-name} --watch
プロジェクト構成
プロジェクト構成
$ cd sam-app
$ tree
.
├── README.md
├── events
│   └── event.json
├── hello-world
│   ├── app.ts
│   ├── jest.config.ts
│   ├── package.json
│   ├── tests
│   │   └── unit
│   │       └── test-handler.test.ts
│   └── tsconfig.json
├── samconfig.toml
└── template.yaml
template.yaml
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for 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.lambdaHandler
      Runtime: nodejs18.x
      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
    Metadata: # Manage esbuild properties
      BuildMethod: esbuild
      BuildProperties:
        Minify: true
        Target: "es2020"
        Sourcemap: true
        EntryPoints: 
        - app.ts

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

GET /hello という API エンドポイントのバックグラウンドとして Lambda 関数が1つだけ定義されたテンプレートが出力されました。

テンプレートの検証 (sam validate)

テンプレートの検証 (正常時)
$ sam validate
./sam-app/template.yaml is a valid SAM Template

template.yaml を適当に変更してみると、エラーとなる

template.yaml
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs18.x
      Architectures:
        - x86_64
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
-            Method: get
テンプレートの検証 (異常時)
$ sam validate
E0001 Error transforming template: Resource with id [HelloWorldFunction] is invalid. Event with id [HelloWorld] is invalid. Property 'Method' is required.
./sam-app/template.yaml:1:1

Error: Linting failed. At least one linting rule was matched to the provided template.

ビルド (sam build)

ビルド
sam build
$ sam build
Starting Build use cache
Manifest file is changed (new hash: 2188d26bd084f69dde27148437a18531) or dependency folder
(.aws-sam/deps/054fea8b-50d2-47b5-b560-b35b887b0364) is missing for (HelloWorldFunction), downloading dependencies and copying/building
source
Building codeuri: ./sam-app/hello-world runtime: nodejs18.x metadata: {'BuildMethod': 'esbuild', 'BuildProperties':
{'Minify': True, 'Target': 'es2020', 'Sourcemap': True, 'EntryPoints': ['app.ts']}} architecture: x86_64 functions: HelloWorldFunction
Running NodejsNpmEsbuildBuilder:CopySource
Running NodejsNpmEsbuildBuilder:NpmInstall
Running NodejsNpmEsbuildBuilder:EsbuildBundle
Running NodejsNpmEsbuildBuilder:CleanUp
Running NodejsNpmEsbuildBuilder:MoveDependencies

Sourcemap set without --enable-source-maps, adding --enable-source-maps to function HelloWorldFunction NODE_OPTIONS

You are using source maps, note that this comes with a performance hit! Set Sourcemap to false and remove NODE_OPTIONS: --enable-source-maps
to disable source maps.


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
ビルド結果
$ tree .aws-sam/ -L 3
.aws-sam/
├── build
│   ├── HelloWorldFunction
│   │   ├── app.js
│   │   └── app.js.map
│   └── template.yaml
├── build.toml
├── cache
└── deps
    └── 054fea8b-50d2-47b5-b560-b35b887b0364
        └── node_modules

7 directories, 4 files

デプロイ (sam deploy)

デプロイ開始(sam deploy --guided)
デプロイ(初回)
# 初回は --guided をつけてインタラクティブに設定を行う。
$ 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]:
 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]: N
 HelloWorldFunction has no authentication. Is this okay? [y/N]: y
 Save arguments to configuration file [Y/n]: Y
 SAM configuration file [samconfig.toml]:
 SAM configuration environment [default]:

デプロイを開始すると、初回はビルドコードなどを保持するS3バケットを作成する。

デプロイ準備
デプロイ進捗
 Looking for resources needed for deployment:
 Creating the required resources...
 Successfully created!

 Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-p9y6ngjl7raj
 A different default S3 bucket can be set in samconfig.toml and auto resolution of buckets turned off by setting resolve_s3=False

        Parameter "stack_name=sam-app" in [default.deploy.parameters] is defined as a global parameter [default.global.parameters].
        This parameter will be only saved under [default.global.parameters] in ./sam-app/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/4da1b77c01e1dde3cda23928fcb199a5  1324 / 1324  (100.00%)

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

Initiating deployment
=====================

 Uploading to sam-app/b804bea4cec12b244df6df6bf1259b20.template  1421 / 1421  (100.00%)


Waiting for changeset to be created..

デプロイ準備が完了すると、CloudFormation スタックの変更確認を行い、問題なければスタックの作成/更新を行う。

変更確認とデプロイ実行
変更確認とデプロイ実行
CloudFormation stack changeset
-----------------------------------------------------------------------------------------------------------------------------------------
Operation                          LogicalResourceId                  ResourceType                       Replacement
-----------------------------------------------------------------------------------------------------------------------------------------
+ Add                              HelloWorldFunctionHelloWorldPerm   AWS::Lambda::Permission            N/A
                                   issionProd
+ Add                              HelloWorldFunctionRole             AWS::IAM::Role                     N/A
+ Add                              HelloWorldFunction                 AWS::Lambda::Function              N/A
+ Add                              ServerlessRestApiDeployment47fc2   AWS::ApiGateway::Deployment        N/A
                                   d5f9d
+ Add                              ServerlessRestApiProdStage         AWS::ApiGateway::Stage             N/A
+ Add                              ServerlessRestApi                  AWS::ApiGateway::RestApi           N/A
-----------------------------------------------------------------------------------------------------------------------------------------


Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:{アカウントID}:changeSet/samcli-deploy1692002833/bb9b4198-fcd7-4a45-ac01-db1755c4eeab


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

2023-08-14 17:49:33 - Waiting for stack create/update to complete

SAM テンプレートの記述としては Lambda 関数についての定義がほとんどでしたが、API用のリソースや Role, Lambda Permission など Lambda 関数に付随して必要なリソースも自動で作成されているのが分かります。
この記事の冒頭で、サーバーレスな AWS リソースをより簡潔に記述できる、と説明したのはこのことを指していました。

デプロイした API のデフォルトエンドポイントにアクセスしてみると、ちゃんとレスポンスが返ってきます。

$ curl "https://{APIのID}.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/"
{"message":"hello world"}

ローカル環境で動作確認 (sam local)

sam local invoke

ローカルのDocker環境でLambda 関数を起動し、1回だけ呼び出します。

Lambda 関数単発呼び出し (Docker がない場合)
$ sam local invoke HelloWorldFunction
Error: Running AWS SAM projects locally requires Docker. Have you got it installed and running?
Lambda 関数単発呼び出し (Docker がある場合)
$ sam local invoke HelloWorldFunction
Invoking app.lambdaHandler (nodejs18.x)
Local image was not found.
Removing rapid images for repo public.ecr.aws/sam/emulation-nodejs18.x
Building image.....................................................................................................................................................................................................................................................................................................................................
Using local image: public.ecr.aws/lambda/nodejs:18-rapid-x86_64.

Mounting ./sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside runtime container
START RequestId: 387371be-324a-443d-995c-a19c1dd9ff92 Version: $LATEST
END RequestId: 387371be-324a-443d-995c-a19c1dd9ff92
REPORT RequestId: 387371be-324a-443d-995c-a19c1dd9ff92 Init Duration: 0.82 ms Duration: 965.25 ms Billed Duration: 966 ms Memory Size: 128 MB Max Memory Used: 128 MB 
{"statusCode": 200, "body": "{\"message\":\"hello world\"}"}%

sam local start-lambda

ローカルの Docker 環境で Lambda 関数をエミュレートするエンドポイントを起動します。
このローカルエンドポイントに CLI や SDK で接続することによって、
ローカル環境で Lambda 関数の動作を確認することができます。

Lambda 関数エンドポイント起動
$ sam local start-lambda
Initializing the lambda functions containers.
Local image is up-to-date
Using local image: public.ecr.aws/lambda/nodejs:18-rapid-x86_64.

Mounting ./sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside runtime container
Containers Initialization is done.
Starting the Local Lambda Service. You can now invoke your Lambda Functions defined in your template through the endpoint.
2023-08-15 01:31:52 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:3001
2023-08-15 01:31:52 Press CTRL+C to quit
CLI で Lambda 関数ローカルエンドポイント呼び出し
$ aws lambda invoke --function-name "HelloWorldFunction" --endpoint-url "http://127.0.0.1:3001" --no-verify-ssl out.txt
{
    "StatusCode": 200
}

$ cat out.txt
{"statusCode": 200, "body": "{\"message\":\"hello world !!!\"}"}%

sam local start-api

ローカルの Docker 環境で Lambda 関数 + API Gateway をエミュレートするエンドポイントを起動します。
このローカルエンドポイントによって HTTP サーバーホストが提供されるため、
HTTP クライアントによって、ローカル環境で API の動作を確認することができます。

API エンドポイント起動
$ sam local start-api
Initializing the lambda functions containers.
Local image is up-to-date
Using local image: public.ecr.aws/lambda/nodejs:18-rapid-x86_64.

Mounting ./sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside runtime container
Containers Initialization is done.
Mounting HelloWorldFunction 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
2023-08-15 01:42:56 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
2023-08-15 01:42:56 Press CTRL+C to quit
API 呼び出し
$ curl http://127.0.0.1:3000/hello
{"message":"hello world"}

サーバーレスアプリケーションを削除

SAM でデプロイした環境が不要になったら削除できます。

サーバーレスアプリケーションを削除
$ sam delete
 Are you sure you want to delete the stack sam-app in the region ap-northeast-1 ? [y/N]: y
 Are you sure you want to delete the folder sam-app in S3 which contains the artifacts? [y/N]: y
        - Deleting S3 object with key sam-app/d0fc9b8b19b413ef42deeaaaea6d380f
        - Deleting S3 object with key sam-app/4a36372aab5a71b08809638d800685db.template
        - Deleting S3 object with key sam-app/4da1b77c01e1dde3cda23928fcb199a5
        - Deleting S3 object with key sam-app/b804bea4cec12b244df6df6bf1259b20.template
 - Deleting Cloudformation stack sam-app

Deleted successfully

SAM 設定ファイル (samconfig.toml) の書き方

sam init の際に samconfig.toml という設定ファイルができました。
このファイルは sam コマンドの各種パラメータのデフォルト設定を保持しておくものです。

AWS SAM CLI の設定ファイル - AWS Serverless Application Model

基本構造

version = 0.1
[{environment}]
[{environment}.{command}]
[{environment}.{command}.parameters]
{option} = {parameter value}
  • {environment}: 環境名
    • 具体的には dev とか prod とかを入れる。
    • sam init した直後だと、デフォルトの環境名として default が設定されている。
    • sam の --config-env オプションで使用する環境名を指定すると samconfig.toml に記載の設定の中で該当する環境の設定が使われる。
  • {command}: コマンド
    • 具体的には build, deploy, local_invoke などの sam のサブコマンドを入れる。
    • スペースやハイフン (-) は、アンダースコア (_) に置き換える。
  • {option} = {parameter value}: パラメータのキーと値のペア
    • 使用可能なパラメータキーは各コマンドのオプション参照。
    • ハイフン (-) は、アンダースコア (_) に置き換える。

参考資料

以上

Discussion