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

https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html

また、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という名前でプライベートリポジトリを作成しました。

スクリーンショット 2021-05-09 21.34.38

また、途中で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のような名前で作成されています。

画像2

手動で適当なidとvalueを追加しました。

画像3

これでデータの用意ができました。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を利用する方の参考になれば幸いです。

参考

AWS Serverless Application Model (AWS SAM) Documentation

この記事が気に入ったらサポートをしてみませんか?