SAMを使ってAPI GatewayとLambdaでサーバーレスアプリケーションを作成する
以下のページで、API GatewayとLambdaを使ったサーバーレスアプリを作成する方法を取り上げましたが、今回はSAMを使用して、同様の構成を再現したいと思います。
SAM(Serverless Application Model)とは、AWSが提供するサーバーレスアプリケーションアプリ開発用フレームワークです。
AWS サーバーレスアプリケーションモデル (SAM、Serverless Application Model) は、サーバーレスアプリケーション構築用のオープンソースフレームワークです。迅速に記述可能な構文で関数、API、データベース、イベントソースマッピングを表現できます。 (中略) デプロイ中、SAM が SAM 構文を AWS CloudFormation 構文に変換および拡張することで、サーバーレスアプリケーションの構築を高速化することができます。
AWS サーバーレスアプリケーションモデル
上記に加えて、SAMの特徴の1つにローカルでのテストがあります。
sam local コマンドとそのサブコマンドのいずれかを使用して、アプリケーションについて、さまざまな種類のローカルテストを実行します。
sam local を使用する
このように簡便な構文とローカルテストによって、開発者はアプリ開発のスピードを高速化できますし、アプリのインフラ周りではなくアプリのロジックに対して注力することができます。
本ページでは、AWS公式のチュートリアル「チュートリアル: Hello World アプリケーションのデプロイ」の手順に従ってSAMを確認します。
構築する環境
「LambdaとAPI Gatewayを使用したサーバーレスアプリ」と同様の環境を構築します。
今回作成するアプリケーションは以下の設定で作成します。
- アプリケーション名:fa-006
- Lambda関数のランタイム環境:Python3.8
検証のシナリオ
以下の流れで進めます。
- SAMアプリケーション作成
- SAMアプリをローカルをテスト
- SAMアプリをデプロイ
SAMアプリ作成
AWS SAM CLIでアプリケーションを作成します。
同ツールを未インストールの場合は「AWS SAM CLI のインストール」をご確認ください。
$ sam init
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
What package type would you like to use?
1 - Zip (artifact is a zip uploaded to S3)
2 - Image (artifact is an image uploaded to an ECR image repository)
Package type: 1
Which runtime would you like to use?
1 - nodejs12.x
2 - python3.8
3 - ruby2.7
4 - go1.x
5 - java11
6 - dotnetcore3.1
7 - nodejs10.x
8 - python3.7
9 - python3.6
10 - python2.7
11 - ruby2.5
12 - java8.al2
13 - java8
14 - dotnetcore2.1
Runtime: 2
Project name [sam-app]: fa-006
Cloning app templates from https://github.com/aws/aws-sam-cli-app-templates
AWS quick start application templates:
1 - Hello World Example
2 - EventBridge Hello World
3 - EventBridge App from scratch (100+ Event Schemas)
4 - Step Functions Sample App (Stock Trader)
5 - Elastic File System Sample App
Template selection: 1
-----------------------
Generating application:
-----------------------
Name: fa-006
Runtime: python3.8
Dependency Manager: pip
Application Template: hello-world
Output Directory: .
Code language: Bash (bash)
SAM設定ファイル
以下のURLに、SAMのテンプレートファイルを配置します。
https://github.com/awstut-an-r/awstut-fa/blob/main/006/template.yaml
テンプレートファイルのポイント解説
今回の環境を構成するための、テンプレートファイルのポイントを取り上げます。
Lambda関数はAWS::Serverless::Functionで定義する
SAMでLambda関数を定義します。
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.8
Events:
HelloWorld:
#Type: Api
Type: HttpApi
Properties:
Path: /hello
Method: get
Code language: YAML (yaml)
CloudFormationでは、Typeプロパティで作成するリソースのタイプを指定します。通常、Lambda関数を定義する場合は「AWS::Lambda::Function」を使用しますが、SAMの記法に従うと「AWS::Serverless::Function」となります。
CodeUriプロパティで実行するスクリプトが設置されているディレクトリPATHを指定します。PATHの指定は本テンプレートファイルからの相対PATHで指定することが可能です。今回は本テンプレートファイルと同じ位置に設置されている「hello_world」ディレクトリにスクリプトを設置するという意味となります。
HandlerプロパティはLambda関数が呼び出された際に、実際に実行する関数を指定するパラメータです。「app.lambda_handler」と指定することで、先述の「hello_world」ディレクトリに設置されているapp.pyファイル内の、lamabda_handler関数を実行するという意味になります。
Runtimeプロパティは関数のランタイム環境を指定するパラメータです。今回はPython3.8で実行するように指定します。
Eventsプロパティは本関数に関連付くイベントを定義することができます。
この関数をトリガーするイベントを指定します。イベントは、1 つのタイプと、そのタイプに依存する一連のプロパティで構成されます。
AWS::Serverless::Function
今回は本プロパティを使用して、本関数に紐づくAPI Gatewayを定義します。
Eventsプロパティ内のTypeプロパティで、作成するリソースを指定できます。
このイベントマッピングのプロパティを記述するオブジェクト。プロパティのセットは、定義された Type に準拠する必要があります。
Type: S3|SNS|Kinesis|DynamoDB|SQS|Api|Schedule|CloudWatchEvent|EventBridgeRule|CloudWatchLogs|IoTRule|AlexaSkill|Cognito|HttpApi|MSK|MQ
EventSource
今回はHTTP APIタイプのAPI Gatewayを作成しますので、「HttpApi」を指定します。
Propertiesプロパティで作成するリソースの各種パラメータを指定します。
PathおよびMethodプロパティはAPI Gatewayのルートに関するパラメータです。今回は作成されるエンドポイントの「/hello」にGETでアクセスされた場合、本関数が実行されるように指定します。
なお実行する関数の内容ですが、以下の通りです。
import json
def lambda_handler(event, context):
return {
"statusCode": 200,
"body": json.dumps({
"message": "hello world from Awstut !"
}),
}
Code language: Python (python)
詳細につきましては、以下のページをご確認ください。
SAMアプリをローカルでテスト
冒頭でご紹介した通り、sam localコマンドを使用することで、作成したSAMアプリをローカルでテストすることができます。今回はローカルテスト方法を3つご紹介します。
sam local invoke
AWS公式では、sam local invokeコマンドは以下の通りに説明されています。
AWS Serverless Application Model コマンドラインインターフェイス (AWS SAM CLI) sam local invoke サブコマンドを使用して、AWS Lambda 関数の 1 回限りの呼び出しをローカルで開始します。
sam local invoke を使用する
sam local invokeコマンドを使用して、ローカルテストを行います。
$ sam local invoke HelloWorldFunction
Invoking app.lambda_handler (python3.8)
Local image is out of date and will be updated to the latest runtime. To skip this, pass in the parameter --skip-pull-image
Building image...........................................................................................................................................................................................................
Using local image: public.ecr.aws/lambda/python:3.8-rapid-x86_64.
Mounting /[app-dir]/fa-006/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside
runtime container
START RequestId: b6c66d3b-532d-4ae5-97ea-d265e04c4064 Version: $LATEST
END RequestId: b6c66d3b-532d-4ae5-97ea-d265e04c4064
REPORT RequestId: b6c66d3b-532d-4ae5-97ea-d265e04c4064 Init Duration: 0.16 ms Duration: 137.87 ms Billed Duration: 138 msMemory Size: 128 MB Max Memory Used: 128 MB
{"statusCode": 200, "body": "{\"message\": \"hello world from Awstut !\"}"}
SAM CLI update available (1.109.0); (1.101.0 installed)
To download: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html
Code language: Bash (bash)
sam local invokeコマンドをHelloWorldFunctionを指定したところ、正常に実行することができました。
実行結果を見ると、確かに「hello world from Awstut !」を含むJSON文字列が返されていますね。
このようにSAMを使用すると、Lambda関数単位でローカルテストを行うことができます。
sam local start-api
AWS公式では、sam local start-apiコマンドは以下の通りに説明されています。
AWS Serverless Application Model コマンドラインインターフェイス (AWS SAM CLI) sam local start-api サブコマンドを使用して、AWS Lambda 関数をローカルで実行し、ローカル HTTP サーバーホストを通じてテストします。このタイプのテストは、Amazon API Gateway エンドポイントによって呼び出される Lambda 関数に役立ちます。
sam local start-api を使用する
sam local start-apiコマンドを使用して、ローカルテストを行います。
まずローカルテスト用のサーバを起動します。
$ sam local start-api
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
2024-02-11 08:25:16 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-02-11 08:25:16 Press CTRL+C to quit
Code language: Bash (bash)
これでローカルにテスト用サーバが立ち上がりました。
続いてcurlコマンドを使用してこのテストサーバにアクセスします。
今回のSAMアプリで作成したLambda関数は、/helloというURLに紐づけているため、それに従いアクセスします。
$ curl http://127.0.0.1:3000/hello
{"message": "hello world from Awstut !"}
Code language: Bash (bash)
確かにレスポンスがありました。
実行結果を見ると、確かに「hello world from Awstut !」を含むJSON文字列が返されていますね。
このアクセス時にローカルサーバに出力されたログを確認します。
Invoking app.lambda_handler (python3.8)
Local image is up-to-date
Using local image: public.ecr.aws/lambda/python:3.8-rapid-x86_64.
Mounting /[app-dir]/fa-006/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside
runtime container
START RequestId: 7b672c4b-c7ec-44a3-aa6f-ce0e36d98a57 Version: $LATEST
END RequestId: 7b672c4b-c7ec-44a3-aa6f-ce0e36d98a57
REPORT RequestId: 7b672c4b-c7ec-44a3-aa6f-ce0e36d98a57 Init Duration: 0.46 ms Duration: 271.65 ms Billed Duration: 272 msMemory Size: 128 MB Max Memory Used: 128 MB
No Content-Type given. Defaulting to 'application/json'.
2024-02-11 08:26:55 127.0.0.1 - - [11/Feb/2024 08:26:55] "GET /hello HTTP/1.1" 200 -
Code language: plaintext (plaintext)
確かにHelloWorldFunctionが実行されていることが読み取れます。
このようにSAMを使用すると、API Gatewayレベルでもローカルテストを行うことができます。
sam local start-lambda
AWS公式では、sam local start-apiコマンドは以下の通りに説明されています。
AWS Serverless Application Model コマンドラインインターフェイス (AWS SAM CLI) sam local start-lambda サブコマンドを使用して、AWS Command Line Interface (AWS CLI) または SDK を通じて AWS Lambda 関数を呼び出します。
sam local start-lambda を使用する
sam local start-lambdaコマンドを使用して、ローカルテストを行います。
まずローカルテスト用のサーバを起動します。
$ sam local start-lambda
Starting the Local Lambda Service. You can now invoke your Lambda Functions defined in your template through the endpoint.
2024-02-11 08:29:43 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
2024-02-11 08:29:43 Press CTRL+C to quit
Code language: Bash (bash)
これでローカルにテスト用サーバが立ち上がりました。
続いてAWS CLIを使用してこのテストサーバにアクセスします。
$ aws lambda invoke --function-name HelloWorldFunction --endpoint-url http://127.0.0.1:3001 ./out.txt
{
"StatusCode": 200
}
Code language: Bash (bash)
成功しました。
テストには一般的なaws lambda invokeコマンドを使用しますが、endpoint-urlでローカルサーバを指定するところがポイントです。
以下がローカルテスト時の実行結果です。
$ cat ./out.txt
{"statusCode": 200, "body": "{\"message\": \"hello world from Awstut !\"}"}
Code language: Bash (bash)
実行結果を見ると、確かに「hello world from Awstut !」を含むJSON文字列が返されていますね。
Invoking app.lambda_handler (python3.8)
Local image is up-to-date
Using local image: public.ecr.aws/lambda/python:3.8-rapid-x86_64.
Mounting /[app-dir]/fa-006/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated, inside runtime container
START RequestId: 4bd88094-9223-4cc8-ade6-955dfae03361 Version: $LATEST
END RequestId: 4bd88094-9223-4cc8-ade6-955dfae03361
REPORT RequestId: 4bd88094-9223-4cc8-ade6-955dfae03361 Init Duration: 0.36 ms Duration: 177.11 ms Billed Duration: 178 ms Memory Size: 128 MB Max Memory Used: 128 MB
2024-02-11 08:39:50 127.0.0.1 - - [11/Feb/2024 08:39:50] "POST /2015-03-31/functions/HelloWorldFunction/invocations HTTP/1.1" 200 -
Code language: plaintext (plaintext)
確かにHelloWorldFunctionが実行されていることが読み取れます。
このようにSAMを使用すると、AWS CLIを使用してアクセスできる形式でもローカルテストを行うことができます。
SAMアプリケーションデプロイ
AWS SAM CLIでビルドおよびデプロイします。
まずビルドします。
$ sam build --use-container
Starting Build inside a container
Building codeuri: hello_world/ runtime: python3.8 metadata: {} functions: ['HelloWorldFunction']
Fetching amazon/aws-sam-cli-build-image-python3.8 Docker container image...................................................................................................................................................................................................................................................................
Mounting /your-directory/hello_world as /tmp/samcli/source:ro,delegated inside runtime container
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
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource
Code language: Bash (bash)
use-containerオプションを使用することで、アプリケーションのビルドをDockerの専用コンテナ内で実施することができます。
関数がネイティブにコンパイルされた依存関係を持つパッケージに依存する場合は、このオプションを使用して Lambda に似た Docker コンテナ内で関数を構築します。
sam build
今回で言いますと、ランタイム環境にPython3.8をしていますので、CLIを実行するローカルに同環境が用意されている必要があります。しかし本オプションを使用することで、ビルドはコンテナ内で完結しますので、ローカル環境を気にする必要はありません。
続いてアプリケーションのデプロイに進みます。
$ sam deploy --guided
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Not found
Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]: fa-006
AWS Region [us-east-1]: 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
HelloWorldFunction 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]:
....
Deploying with following values
===============================
Stack name : fa-006
Region : ap-northeast-1
Confirm changeset : True
Deployment s3 bucket : [bucket-name]
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {}
Signing Profiles : {}
Initiating deployment
=====================
HelloWorldFunction may not have authorization defined.
Waiting for changeset to be created..
CloudFormation stack changeset
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType Replacement
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add HelloWorldFunctionHelloWorldPermission AWS::Lambda::Permission N/A
+ Add HelloWorldFunctionRole AWS::IAM::Role N/A
+ Add HelloWorldFunction AWS::Lambda::Function N/A
+ Add ServerlessHttpApiApiGatewayDefaultStage AWS::ApiGatewayV2::Stage N/A
+ Add ServerlessHttpApi AWS::ApiGatewayV2::Api N/A
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:[accound-id]:changeSet/samcli-deploy1641285770/e5418e4b-792d-4e96-93ae-5d10a45ee858
Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y
...
CloudFormation outputs from deployed stack
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Key HelloWorldFunctionIamRole
Description Implicit IAM Role created for Hello World function
Value arn:aws:iam::[account-id]:role/fa-006-HelloWorldFunctionRole-VBT1AK5MRG5J
Key HelloWorldApi
Description API Gateway endpoint URL for Prod stage for Hello World function
Value https://25ypbm7one.execute-api.ap-northeast-1.amazonaws.com/hello
Key HelloWorldFunction
Description Hello World Lambda Function ARN
Value arn:aws:lambda:ap-northeast-1:[account-id]:function:fa-006-HelloWorldFunction-qajs4WIMSN60
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - fa-006 in ap-northeast-1
Code language: Bash (bash)
正常にデプロイが完了しました。
デプロイのログから、今回は5つのリソースが生成されたことが確認できます。
またOutputsセクションに記載した出力内容から、今回作成されたAPI Gatewayエンドポイントは「https://25ypbm7one.execute-api.ap-northeast-1.amazonaws.com/hello」であることがわかります。
SAMアプリにアクセス
それでは実際にアクセスします。
$ curl https://25ypbm7one.execute-api.ap-northeast-1.amazonaws.com/hello
{"message": "hello world from Awstut !"}
Code language: Bash (bash)
正常にLambda関数の実行結果を取得することができました。
このようにSAMを使用することによって、通常のCloudFormationの記法に比べ、より簡潔にサーバーレスアプリケーションを構築することができることがわかりました。
(参考)SAMで作成された各種リソースの詳細
SAMでデプロイしたアプリケーションの各種リソースの状況を確認します。
冒頭で確認した通り、SAMはCloudFormationの変形版とも言えるサービスですので、SAMアプリケーションをデプロイすると、CloudFormationスタックが作成されます。
AWS Management Consoleからスタックの作成状況を確認します。
テンプレートファイルでは直接定義していませんが、API GatewayがLambda関数を呼び出すためのPermissionと、Lambda関数に付与するIAMロールも、自動的に作成されていることがわかります。
Permissionの中身は以下の通りです。
Lambda関数の呼び出しに関する条件が定義されています。今回作成したAPI Gatewayで、かつURLが/helloのGETメソッドからの呼び出しの場合のみに許可されるという内容です。
以下は作成されたIAMロールです。
AWS管理ポリシーAWSLambdaBasicExecutionRoleがアタッチされていることがわかります。Lambda関数を実行する上での最低限の権限を付与しているということになります。
本スタックには含まれていませんが、API Gatewayのルート・統合リソースも作成されています。
まず統合です。
作成したLambda関数が関連づいていることがわかります。
次にルートです。
/helloのGETメソッドに先ほどの統合が関連づいています。これらの設定が合わさってAPI Gatewayにアクセスがあった場合に、Lambda関数が実行されるという動作を実現することができます。
まとめ
SAMを使用することによって、通常のCloudFormationの記法に比べ、より簡潔にサーバーレスアプリケーションを構築することができることがわかりました。
またSAMはローカルテストの方法も豊富に提供されていることも確認しました。
このように簡便な構文とローカルテストによって、開発者はアプリ開発のスピードを高速化できますし、アプリのインフラ周りではなくアプリのロジックに対して注力することができるでしょう。