AWS SAMで手軽にAPIを用意する
はじめに
Rooxim株式会社ではAWS SAMを利用してアプリケーションの一部を定義・構築することで構成管理を行なっています。
今回はSAMを初めて利用する方向けに、SAM CLIのセットアップとテンプレートの記述、Dynamo DBに用意したデータを取得するAPIを作る例を書きたいと思います。
本記事で利用する環境は下記の通りです。
macOS Big Sur 11.3
AWS CLI: 2.0.26
SAM CLI: 1.23.0
Docker: 20.10.5
SAMとは
Serverless Application Modelの略でAWSが提供するサーバーレスアプリケーション構築用のフレームワークです。
SAM構文でアプリケーションに必要なリソースをSAMテンプレートとして記述することで、Infrastructure as Codeを実現できます。似たものとしてAWS CloudFormationがありますが、SAM構文で記述されたテンプレートはCloudFormation構文に変換されデプロイがされます。
CloudFormationでは定形的なリソース定義(例えばLambdaのためのIAM Roleなど)を何度も書かなければなりませんでしたが、SAMではよしなに用意してくれる部分も多く比較的記述量を減らすことができます。
SAM CLIはSAMテンプレートを解釈してデプロイやローカルでのテストを実行するためのコマンドラインツールです。
インストール
先にAWS CLIをインストールし、アクセスキーIDやシークレットアクセスキーを設定しておきます。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-install.html
SAM CLIはmacOSの場合はHomebrewを利用してインストール可能です。
$ brew tap aws/tap
$ brew install aws-sam-cli
また、Dockerもインストールしておきます。
https://docs.docker.jp/docker-for-mac/install.html
アプリケーションの生成
initでアプリケーションを作成します。
今回はすでに用意されているAWS Quick Start Templatesから選択し、必要なリソースを追加することにします。
$ 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: 2
Which base image would you like to use?
1 - amazon/nodejs14.x-base
2 - amazon/nodejs12.x-base
3 - amazon/nodejs10.x-base
4 - amazon/python3.8-base
5 - amazon/python3.7-base
6 - amazon/python3.6-base
7 - amazon/python2.7-base
8 - amazon/ruby2.7-base
9 - amazon/ruby2.5-base
10 - amazon/go1.x-base
11 - amazon/java11-base
12 - amazon/java8.al2-base
13 - amazon/java8-base
14 - amazon/dotnet5.0-base
15 - amazon/dotnetcore3.1-base
16 - amazon/dotnetcore2.1-base
Base image: 4
Project name [sam-app]:
Cloning app templates from https://github.com/aws/aws-sam-cli-app-templates
AWS quick start application templates:
1 - Hello World Lambda Image Example
2 - PyTorch Machine Learning Inference API
3 - Scikit-learn Machine Learning Inference API
4 - Tensorflow Machine Learning Inference API
5 - XGBoost Machine Learning Inference API
Template selection: 1
-----------------------
Generating application:
-----------------------
Name: sam-app
Base Image: amazon/python3.8-base
Dependency Manager: pip
Output Directory: .
Next steps can be found in the README file at ./sam-app/README.md
SAMテンプレートの記述とビルド
Hello World Lambda Image Exampleを選択したため既にtemplate.yamlに動作するものが記述されています。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
python3.8
Sample SAM Template for sam-app
Globals:
Function:
Timeout: 3
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
PackageType: Image
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
Metadata:
Dockerfile: Dockerfile
DockerContext: ./hello_world
DockerTag: python3.8-v1
Outputs:
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
この記述だけでデプロイすると裏ではAPI Gateway, Lambda, 必要なIAM Roleなどが自動で生成されます。
処理の本体は ./hello_world/app.py にあります。
import json
def lambda_handler(event, context):
"""Sample pure Lambda function
Parameters
----------
event: dict, required
API Gateway Lambda Proxy Input Format
Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
context: object, required
Lambda Context runtime methods and attributes
Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html
Returns
------
API Gateway Lambda Proxy Output Format: dict
Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
"""
return {
"statusCode": 200,
"body": json.dumps(
{
"message": "hello world",
}
),
}
SAMテンプレートをbuildでビルドしてみましょう。
$ sam build
Building codeuri: /Users/hoge/sam-app runtime: None metadata: {'Dockerfile': 'Dockerfile', 'DockerContext': '/Users/hoge/sam-app/hello_world', 'DockerTag': 'python3.8-v1'} functions: ['HelloWorldFunction']
Building image for HelloWorldFunction function
Setting DockerBuildArgs: {} for HelloWorldFunction function
Step 1/4 : FROM public.ecr.aws/lambda/python:3.8
---> f8db25a51ee4
Step 2/4 : COPY app.py requirements.txt ./
---> Using cache
---> dcf23342cb23
Step 3/4 : RUN python3.8 -m pip install -r requirements.txt -t .
---> Using cache
---> 8fc1b07df65f
Step 4/4 : CMD ["app.lambda_handler"]
---> Using cache
---> 000eb06f755e
Successfully built 000eb06f755e
Successfully tagged helloworldfunction:python3.8-v1
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
ビルド時に下記のように失敗してしまった場合は、エラーメッセージにある通りDockerが動いていません。事前に動かしておきましょう。
Build Failed
Error: Building image for HelloWorldFunction requires Docker. is Docker running?
DynamoDBを追加する
アプリケーションで利用するDynamoDBのテーブルを定義します。
簡単にLambdaからDynamoDBへのアクセスができるようPoliciesにはAmazonDynamoDBFullAccessを設定していますが、本番環境では適切な権限設定をするようにしてください。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
python3.8
Sample SAM Template for sam-app
Globals:
Function:
Timeout: 3
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function Properties:
PackageType: Image
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
Policies: AmazonDynamoDBFullAccess
Environment:
Variables:
DYNAMO_DB_TABLE_NAME: !Ref DynamoDBTable
Metadata:
Dockerfile: Dockerfile
DockerContext: ./hello_world
DockerTag: python3.8-v1
DynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
Outputs:
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
app.pyはリクエストパラメータで指定されたidを利用してDynamoDBに問い合わせするよう書き換えます。
import json
import os
import boto3
from boto3.dynamodb.conditions import Key
table_name = os.environ['DYNAMO_DB_TABLE_NAME']
def lambda_handler(event, context):
query_param_id = event['queryStringParameters']['id']
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(table_name)
query_result = table.query(
KeyConditionExpression=Key('id').eq(query_param_id)
)
return {
"statusCode": 200,
"body": json.dumps(query_result['Items'], ensure_ascii=False),
}
デプロイ
deploy —guidedでデプロイをします。
2回目以降はsamconfig.tomlに設定が保存されるため、—guidedは不要です。
$ sam deploy --guided
デプロイ時に保存先のImage Repositoryを聞かれるため、適当なものがない場合はあらかじめ作っておきます。今回はsam-appという名前でプライベートリポジトリを作成しました。
また、途中でAPI Keyが設定されていないけど良いか、と聞かれますが一旦今はyesとしておきます。
HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
そうすると何やらCloudFormationのchangesetが作成され、リソースが用意されていきます。
最後に https://xxxxx.execute-api.<Region>.amazonaws.com/Prod/hello/ のようなAPIのエンドポイントが表示されます。あとで利用するため控えておきます。
CloudFormation outputs from deployed stack
----------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
----------------------------------------------------------------------------------------------------------------------------------------------------------------
Key HelloWorldFunctionIamRole
Description Implicit IAM Role created for Hello World function
Value arn:aws:iam::<Account ID>:role/sam-app-HelloWorldFunctionRole-1NVQH310VQW79
Key HelloWorldApi
Description API Gateway endpoint URL for Prod stage for Hello World function
Value https://xxxxxx.execute-api.<Region>.amazonaws.com/Prod/hello/
Key HelloWorldFunction
Description Hello World Lambda Function ARN
Value arn:aws:lambda:<Region>:<Account ID>:function:sam-app-HelloWorldFunction-19AJID2IPWNXN
----------------------------------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - sam-app in <Region>
ここまでくるとWebのコンソールからAPI GatewayやLambda, DynamoDBのテーブルが作成されていることが確認できるかと思います。
DynamoDBにデータを用意して取得する
このままではDBにデータがないためAPIは何も返すことができません。適当なデータを用意しておきます。
SAMテンプレートではテーブル名を明示的に指定しなかったため、sam-app-DynamoDBTable-xxxのような名前で作成されています。
手動で適当なidとvalueを追加しました。
これでデータの用意ができました。APIを呼び出して値を取得してみましょう。
$ curl "https://xxxxxxxx.execute-api.<Region>.amazonaws.com/Prod/hello/?id=0"
[{"id": "0", "value": "test value"}]
終わりに
SAMを利用して手軽にリソースを用意し、APIを構築することができました。
今回詳しくは紹介しませんでしたがSAM CLIを利用してローカルでの実行やテストを行うことも可能です。SAMやCloudFormationを利用すると作って壊すことが簡単なため試行錯誤のスピードも上がりますし、テンプレートで構成を記述できるためバージョン管理も容易となります。
この記事がこれからSAMを利用する方の参考になれば幸いです。
参考
この記事が気に入ったらサポートをしてみませんか?