概要
10月にDVAを取得し、11月にSAPを取得したのですが、知識を詰め込んだだけでサービス自体にはあまり触れていなかったので、この機会に試験で出題されたAWS SAMを触ってみました。この記事では、AWS SAM を使って簡単なサーバレスアプリケーションを構築する方法を解説します。このアプリケーションでは DynamoDB を利用してタスクを管理し、Lambda 関数を通じてタスクの作成、取得、完了といった操作を行います。構築からデプロイ、ローカルテスト、リソース削除までの手順をカバーします。
1. 開発環境の準備
AWS SAM を使ったサーバレスアプリケーションを構築するには、以下が必要です。
必要なツール
- AWS CLI: AWS サービスとやり取りするために必要です。
- SAM CLI: サーバレスアプリケーションの管理ツール。
- Docker: SAM CLI のローカルテストに必要。
-
Postman または
curl
などの API テストツール。
インストール確認
以下のコマンドを実行してインストールされているか確認してください。
aws --version
sam --version
docker --version
Docker 起動
Docker をインストールした後、サービスを起動しておきます。
2. プロジェクトの作成
SAM CLI を使ったプロジェクト作成
以下のコマンドでプロジェクトを作成します。
sam init
プロンプトに従って以下の設定を選択します。
今回はAWS Quick Start Templates
から進めていきます。また、本格的な開発というわけではないのでX-rayやCloudWatch Application Insightsは無効で設定をしています。
-
テンプレートの選択:
Hello World Example
-
ランタイム:
Python 3.11
(存在しなければ後述するtemplate.yamlで指定できるのであまり気にしないでください。) -
プロジェクト名:
task-app
作成後のディレクトリ構造は以下のようになります。
task-app/
│ .gitignore
│ README.md
│ samconfig.toml
│ template.yaml
│ __init__.py
│
├─events
│ event.json
│
├─hello_world
│ app.py
│ requirements.txt
│ __init__.py
│
└─tests
│ requirements.txt
│ __init__.py
│
├─integration
│ test_api_gateway.py
│ __init__.py
│
└─unit
test_handler.py
__init__.py
3. DynamoDB テーブルと Lambda 関数の定義 (解説)
template.yaml
は AWS SAM において、アプリケーションの全体構成を記述するファイルです。リソース(DynamoDB テーブル、Lambda 関数など)の作成や API Gateway の設定を定義します。
全体構成の概要
-
DynamoDB テーブル (
TaskTable
)- タスクを管理するためのデータベース。
-
Lambda 関数
- タスク作成、取得、完了の 3 つの関数を定義。
-
API Gateway
- Lambda 関数をトリガーするエンドポイントを提供。
-
Outputs セクション
- デプロイ後に生成されるリソースの情報を出力。
DynamoDB テーブルの定義
以下の部分が DynamoDB テーブルの定義です。
TaskTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: TaskTable
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
BillingMode: PAY_PER_REQUEST
解説
-
Type: AWS::DynamoDB::Table
- このリソースは DynamoDB のテーブルを作成します。
-
TableName
- テーブル名を
TaskTable
に指定しています。この名前は DynamoDB 内で一意である必要があります。
- テーブル名を
-
AttributeDefinitions
-
id
という属性を定義し、その型を文字列 (S
) としています。
-
-
KeySchema
- 主キーとして
id
属性を指定しています。 -
KeyType: HASH
は DynamoDB のパーティションキー (プライマリキー) です。
- 主キーとして
-
BillingMode: PAY_PER_REQUEST
- 使用量に応じた料金を課金する設定です。リソースを効率的に利用できます。
Lambda 関数の定義
この YAML ファイルでは 3 つの Lambda 関数を定義しています。それぞれの役割を解説します。
1. タスク作成関数 (CreateTaskFunction
)
CreateTaskFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.create_task
Runtime: python3.11
Architectures:
- x86_64
Environment:
Variables:
TABLE_NAME: TaskTable
Policies:
- DynamoDBCrudPolicy:
TableName: TaskTable
Events:
CreateTaskApi:
Type: Api
Properties:
Path: /tasks
Method: post
-
CodeUri
:- 関数コードが格納されているフォルダを指定します (
hello_world/
)。
- 関数コードが格納されているフォルダを指定します (
-
Handler
:-
app.py
内のcreate_task
関数がエントリポイントとして実行されます。
-
-
Runtime
:- 使用する言語のランタイムを指定します (
python3.11
)。
- 使用する言語のランタイムを指定します (
-
Environment
:- Lambda 関数で使用する環境変数を定義します。
-
TABLE_NAME
変数に DynamoDB テーブル名TaskTable
を指定しています。
-
Policies
:- DynamoDB の操作権限を指定します。
-
DynamoDBCrudPolicy
により、TaskTable
に対して読み取り、書き込み、更新、削除の操作が可能になります。
-
Events
:- API Gateway をトリガーとして設定。
-
/tasks
に対する POST リクエストを処理します。
2. タスク取得関数 (GetTasksFunction
)
GetTasksFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.get_tasks
Runtime: python3.11
Architectures:
- x86_64
Environment:
Variables:
TABLE_NAME: TaskTable
Policies:
- DynamoDBReadPolicy:
TableName: TaskTable
Events:
GetTasksApi:
Type: Api
Properties:
Path: /tasks
Method: get
-
Policies
:-
DynamoDBReadPolicy
により、DynamoDB テーブルからのデータ読み取り操作が可能です。
-
-
Events
:-
/tasks
に対する GET リクエストを処理します。
-
3. タスク完了関数 (CompleteTaskFunction
)
CompleteTaskFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.complete_task
Runtime: python3.11
Architectures:
- x86_64
Environment:
Variables:
TABLE_NAME: TaskTable
Policies:
- DynamoDBCrudPolicy:
TableName: TaskTable
Events:
CompleteTaskApi:
Type: Api
Properties:
Path: /tasks/{id}/complete
Method: put
-
Events
:-
/tasks/{id}/complete
に対する PUT リクエストを処理します。 -
{id}
は URL パスパラメータとして指定されます。
-
Outputs セクション
Outputs セクションでは、デプロイ後に生成されるリソースの情報を出力します。これにより、API Gateway エンドポイント URL や Lambda 関数 ARN などが簡単に参照できます。
Outputs:
CreateTaskApi:
Description: "API Gateway endpoint URL for creating tasks"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/tasks"
GetTasksApi:
Description: "API Gateway endpoint URL for retrieving tasks"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/tasks"
CompleteTaskApi:
Description: "API Gateway endpoint URL for completing tasks"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/tasks/{id}/complete"
解説
-
CreateTaskApi
:- タスク作成用 API のエンドポイントを出力します。
-
GetTasksApi
:- タスク取得用 API のエンドポイントを出力します。
-
CompleteTaskApi
:- タスク完了用 API のエンドポイントを出力します。
-
!Sub
:- 文字列中の
${}
部分を CloudFormation が解釈して動的に値を埋め込みます。
- 文字列中の
4. Lambda 関数の実装
以下は、app.py
ファイルに記述された 3 つの Lambda 関数についての解説です。
全体構成
import json
import boto3
import uuid
import os
# DynamoDB リソースの設定
# 本番環境とローカル環境で切り替え可能
# dynamodb = boto3.resource('dynamodb', endpoint_url="http://host.docker.internal:8000")
dynamodb = boto3.resource('dynamodb')
# 環境変数から DynamoDB テーブル名を取得
table_name = os.environ['TABLE_NAME']
table = dynamodb.Table(table_name)
ポイント
-
boto3.resource('dynamodb')
:- AWS DynamoDB にアクセスするための設定。
- 本番環境では
endpoint_url
を設定せずにデフォルトで動作します。
-
環境変数
TABLE_NAME
:- DynamoDB テーブル名を環境変数から取得し、動的に設定します。
1. タスク作成関数 (create_task
)
def create_task(event, context):
"""
タスクを作成するLambda関数
"""
body = event.get('body')
if not body:
return {
"statusCode": 400,
"body": json.dumps({"message": "Invalid request, 'body' is missing"})
}
try:
data = json.loads(body)
except json.JSONDecodeError:
return {
"statusCode": 400,
"body": json.dumps({"message": "Invalid JSON in request body"})
}
# タスクを作成
task_id = str(uuid.uuid4())
task = {
"id": task_id,
"title": data.get("title", "Untitled Task"),
"description": data.get("description", ""),
"status": "pending" # デフォルトで未完了状態
}
table.put_item(Item=task)
return {
"statusCode": 201,
"body": json.dumps({"message": "Task created", "task": task})
}
動作の流れ
-
リクエストボディの検証
-
event['body']
が存在するか確認。 - JSON 形式で解析し、不正なリクエストはエラーを返します。
-
-
タスクデータの作成
- UUID で一意の ID を生成。
- タスクの
title
,description
,status
を設定。
-
DynamoDB に保存
-
table.put_item(Item=task)
を使用してデータを挿入。
-
-
レスポンス
- 作成されたタスクのデータを含むレスポンスを返します。
2. タスク取得関数 (get_tasks
)
def get_tasks(event, context):
"""
すべてのタスクを取得するLambda関数
"""
response = table.scan()
tasks = response.get("Items", [])
return {
"statusCode": 200,
"body": json.dumps({"tasks": tasks})
}
動作の流れ
-
データ取得
-
table.scan()
で DynamoDB テーブル内のすべてのタスクを取得します。
-
-
レスポンス
- 取得したタスクデータを JSON 形式で返します。
3. タスク完了関数 (complete_task
)
def complete_task(event, context):
"""
指定されたタスクを完了にするLambda関数
"""
# タスクIDを取得
path_parameters = event.get("pathParameters", {})
task_id = path_parameters.get("id")
if not task_id:
return {
"statusCode": 400,
"body": json.dumps({"message": "Task ID is required in the path"})
}
# タスクの状態を更新
try:
response = table.update_item(
Key={"id": task_id},
UpdateExpression="SET #status = :status",
ExpressionAttributeNames={
"#status": "status"
},
ExpressionAttributeValues={
":status": "completed"
},
ReturnValues="UPDATED_NEW"
)
return {
"statusCode": 200,
"body": json.dumps({"message": "Task completed", "updatedAttributes": response.get("Attributes", {})})
}
except Exception as e:
return {
"statusCode": 500,
"body": json.dumps({"message": "Failed to complete task", "error": str(e)})
}
動作の流れ
-
タスク ID の取得
-
event['pathParameters']['id']
から ID を取得。 - ID が指定されていない場合はエラーを返します。
-
-
DynamoDB 更新
- 指定された ID のタスクを検索し、
status
をcompleted
に変更。 - 更新された内容をレスポンスとして返します。
- 指定された ID のタスクを検索し、
-
エラーハンドリング
- 更新処理中の例外はキャッチし、エラーメッセージを含むレスポンスを返します。
ローカル環境での注意点
- ローカル環境で動作確認する場合、
endpoint_url
を設定して DynamoDB Local を使用します。 - 本番環境ではコメントアウトして、標準の AWS DynamoDB に接続します。
# dynamodb = boto3.resource('dynamodb', endpoint_url="http://host.docker.internal:8000")
dynamodb = boto3.resource('dynamodb')
このように、ローカルテスト用と本番環境用を簡単に切り替えることができます。
5. ローカルテスト
ローカルテストでは、AWSリソースを直接触らずに環境を模擬して、Lambda関数やDynamoDBテーブルの動作確認を行います。このセクションでは、DynamoDB Local を使用したローカルテストの手順を詳しく説明します。
前提条件
- AWS SAM CLI がインストールされていること。
- Docker がインストールされ、起動していること。
- DynamoDB Local が起動していること(この記事を参考)。
手順
1. DynamoDB Local の起動
以下のコマンドで DynamoDB Local を Docker コンテナとして起動します。
docker run -d -p 8000:8000 amazon/dynamodb-local
-
オプションの確認
-
-d
: バックグラウンドで実行。 -
-p 8000:8000
: ホストとコンテナのポートを 8000 にバインド。
-
2. DynamoDB テーブルの作成
以下のコマンドで DynamoDB Local にテーブルを作成します。
aws dynamodb create-table \
--table-name TaskTable \
--attribute-definitions AttributeName=id,AttributeType=S \
--key-schema AttributeName=id,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--endpoint-url http://localhost:8000
-
各パラメータの意味
-
--table-name TaskTable
: 作成するテーブル名。 -
--attribute-definitions
: テーブルの属性定義。ここではid
を文字列型で定義。 -
--key-schema
: プライマリキーを指定。 -
--billing-mode PAY_PER_REQUEST
: 従量課金モードを指定。 -
--endpoint-url http://localhost:8000
: ローカル DynamoDB に接続。
-
3. DynamoDB の状態確認
テーブルが作成されたかを確認します。
aws dynamodb list-tables --endpoint-url http://localhost:8000
出力例:
{
"TableNames": [
"TaskTable"
]
}
次に、テーブルの詳細情報を確認します。
aws dynamodb describe-table --table-name TaskTable --endpoint-url http://localhost:8000
4. イベントファイルの準備
Lambda 関数のテストに使用するイベントファイルを用意します。
① タスク作成 (create_task.json
):
{
"resource": "/tasks",
"path": "/tasks",
"httpMethod": "POST",
"isBase64Encoded": false,
"headers": {
"Content-Type": "application/json"
},
"body": "{\"title\": \"Test Task\", \"description\": \"This is a test task.\"}"
}
② タスク取得 (get_tasks.json
):
{
"resource": "/tasks",
"path": "/tasks",
"httpMethod": "GET",
"isBase64Encoded": false,
"headers": {
"Content-Type": "application/json"
}
}
③ タスク完了 (complete_task.json
):
path
とpathParameters.id
は作成したタスクを確認して適宜変えてください。
{
"resource": "/tasks/{id}/complete",
"path": "/tasks/a044c959-0139-401d-8131-9781a991ffe0/complete",
"httpMethod": "PUT",
"isBase64Encoded": false,
"headers": {
"Content-Type": "application/json"
},
"pathParameters": {
"id": "a044c959-0139-401d-8131-9781a991ffe0"
}
}
5. Lambda 関数のローカルテスト
AWS SAM CLI を使用して Lambda 関数をローカルでテストします。
- SAM ビルド
以下のコマンドで Lambda 関数をビルドします。
sam build
成功例:
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
- Lambda 関数のテスト
-
タスク作成
sam local invoke CreateTaskFunction --event events/create_task.json
-
タスク取得
sam local invoke GetTasksFunction --event events/get_tasks.json
-
タスク完了
sam local invoke CompleteTaskFunction --event events/complete_task.json
出力例:
-
タスク作成の成功例
{ "statusCode": 201, "body": "{\"message\": \"Task created\", \"task\": {\"id\": \"01e86c41-a58b-46b0-9495-c8390a0bb420\", \"title\": \"Test Task\", \"description\": \"This is a test task.\", \"status\": \"pending\"}}" }
6. DynamoDB Local でデータ確認
DynamoDB Local に保存されたデータを確認します。
aws dynamodb scan --table-name TaskTable --endpoint-url http://localhost:8000
出力例:
{
"Items": [
{
"title": {
"S": "Test Task"
},
"description": {
"S": "This is a test task."
},
"id": {
"S": "01e86c41-a58b-46b0-9495-c8390a0bb420"
},
"status": {
"S": "pending"
}
}
],
"Count": 1,
"ScannedCount": 1,
"ConsumedCapacity": null
}
補足: DynamoDB Local GUI (dynamodb-admin)
より視覚的に DynamoDB Local を操作したい場合は、dynamodb-admin
を使用します。
https://github.com/aaronshaf/dynamodb-admin
-
インストール
npm install -g dynamodb-admin
-
起動
dynamodb-admin --dynamo-endpoint=http://localhost:8000 --skip-default-credentials
-
アクセス
- ブラウザで http://localhost:8001 にアクセスします。
- テーブルデータを GUI で確認できます。
6. 本番環境へのデプロイ
ローカルでの動作確認が終わったら、次は本番環境へのデプロイを行います。AWS SAM CLI を使用して、作成したリソースを AWS にデプロイする方法を以下に詳しく説明します。
デプロイ手順
1. sam deploy --guided
コマンドの実行
以下のコマンドを実行して、AWS にデプロイします。
sam deploy --guided
2. 対話形式の設定
コマンド実行後に対話形式で設定を進めます。以下は設定の例です。
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Not found
Setting default arguments for 'sam deploy'
Stack Name [task-app]: # デプロイする CloudFormation スタック名を入力します (例: task-app)
AWS Region [us-east-1]: ap-northeast-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 # デプロイ前にリソースの変更を確認したい場合は "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 # 必要な IAM ロールを作成する場合は "Y"
# Preserves the state of previously provisioned resources when an operation fails
Disable rollback [y/N]: N # エラー時にロールバックを無効にしたい場合は "Y" を選択
# 今回は簡単な検証目的なので認証不要
CreateTaskFunction has no authentication. Is this okay? [y/N]: Y
GetTasksFunction has no authentication. Is this okay? [y/N]: Y
CompleteTaskFunction has no authentication. Is this okay? [y/N]: Y
Save arguments to configuration file [Y/n]: Y # 設定を `samconfig.toml` に保存する場合は "Y"
SAM configuration file [samconfig.toml]: # デフォルトで "samconfig.toml" を指定
SAM configuration environment [default]: # 環境名を指定 (通常は "default")
3. デプロイの進行
設定を入力すると、以下のような進捗が表示されます。
Deploying with following values
===============================
Stack name : task-app
Region : ap-northeast-1
Confirm changeset : True
Disable rollback : False
Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-xxxxx
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {}
Initiating deployment
=====================
Waiting for changeset to be created..
CloudFormation stack changeset
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType Replacement
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add CompleteTaskFunctionCompleteTaskApiPermissionProd AWS::Lambda::Permission N/A
+ Add CompleteTaskFunctionRole AWS::IAM::Role N/A
+ Add CompleteTaskFunction AWS::Lambda::Function N/A
+ Add CreateTaskFunctionCreateTaskApiPermissionProd AWS::Lambda::Permission N/A
+ Add CreateTaskFunctionRole AWS::IAM::Role N/A
+ Add CreateTaskFunction AWS::Lambda::Function N/A
+ Add GetTasksFunctionGetTasksApiPermissionProd AWS::Lambda::Permission N/A
+ Add GetTasksFunctionRole AWS::IAM::Role N/A
+ Add GetTasksFunction AWS::Lambda::Function N/A
+ Add ServerlessRestApiDeployment26a16bfa26 AWS::ApiGateway::Deployment N/A
+ Add ServerlessRestApiProdStage AWS::ApiGateway::Stage N/A
+ Add ServerlessRestApi AWS::ApiGateway::RestApi N/A
+ Add TaskTable AWS::DynamoDB::Table N/A
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Deploy this changeset? [y/N]: y
y
を入力してデプロイを進めます。
4. デプロイ完了後の確認
デプロイが成功すると以下のような結果が表示されます。
Successfully created/updated stack - task-app in ap-northeast-1
Outputs
----------------------------------------------------------------------------------------------------------------------------------
Key TaskTableName
Description DynamoDB table name
Value TaskTable
Key CompleteTaskApi
Description API Gateway endpoint URL for completing tasks
Value https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/tasks/{id}/complete
Key GetTasksApi
Description API Gateway endpoint URL for retrieving tasks
Value https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/tasks
Key CreateTaskApi
Description API Gateway endpoint URL for creating tasks
Value https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/tasks
----------------------------------------------------------------------------------------------------------------------------------
API エンドポイントの確認
-
タスク作成 API
-
URL:
https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/tasks
- HTTP メソッド: POST
-
リクエストボディ例:
{ "title": "New Task", "description": "This is a new task." }
-
URL:
-
タスク取得 API
-
URL:
https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/tasks
- HTTP メソッド: GET
-
URL:
-
タスク完了 API
-
URL:
https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/tasks/{id}/complete
- HTTP メソッド: PUT
-
URL:
7. 本番環境での動作確認
1. デプロイ後の API Gateway 確認
AWS マネジメントコンソールから API Gateway の設定を確認します。
API Gateway コンソールに移動:
- AWS マネジメントコンソールで「API Gateway」に移動します。
- デプロイされた API (例:
task-app
) がリストに表示されていることを確認します。
2. Postman を使った API テスト
Postman を使用して、本番環境の API をテストしました。以下の順序で API を実行しています。
① タスクの作成 (POST)
② タスクの取得 (GET)
③ タスクの完了 (PUT)
④ 完了後のタスクの取得 (GET)
結果のまとめ
- 全ての操作 (タスクの作成、取得、完了) が正常に動作することを確認しました。
- DynamoDB に保存されたデータがリアルタイムで更新されていることも確認済みです。
8. 最後に
これで、簡単にですがSAMを試すことができました。結構簡単にいろいろできそうなので、まだまだ触れていない部分も引き続き勉強を続けていきます!