AWS SAM + LocalStack によるサンプルアプリケーションの作成
Serverless Framework いいなあ
— arailly (@arailly_) 2020年4月15日
Serverless Framework,AWS SAM,AWS CDKなど, サーバーレスのローカル開発環境を行うツールが増えてきました.
使っている人が多いのはおそらくServerless Frameworkですが, AWS公式がメンテナンスしていて, ローカルでのLambda関数のテストができるAWS SAMは今なお有力な選択肢だと思います.
しかしサーバーレスは,クラウド環境では手軽に扱える反面, ローカル環境での開発はハマりどころや辛いところが多いです.
そこで本記事では,AWS SAM を使って,API Gateway を通してLambdaを呼び出し, DynamoDBとやりとりするアプリを作っていきます. LambdaのランタイムはNode.jsです.
また,DynamoDBはLocalStackのコンテナにホストします. DynamoDBだけであればAWS公式が提供している DynamoDBのDockerイメージを使うことができますが, SQSやS3などのサービスも使うことが多いため, LocalStackの使用をおすすめします.
バージョン情報
macOS Catalina 10.15.4 AWS CLI 1.16.310 AWS SAM CLI 0.44.0 Docker 19.03.8
構築手順
プロジェクト作成
sam initコマンドでプロジェクトを作成します. 今回はQuick Start Templatesから作っていきます.
> sam init --name sam-sample --runtime nodejs12.x Which template source would you like to use? 1 - AWS Quick Start Templates 2 - Custom Template Location Choice: 1 Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git ----------------------- Generating application: ----------------------- Name: sam-sample Runtime: nodejs12.x Dependency Manager: npm Application Template: hello-world Output Directory: . Next steps can be found in the README file at ./sam-sample/README.md
これで,簡単なAPIが作られました.
確認
サンプルの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. You only need to restart SAM CLI if you update your AWS SAM template 2020-04-29 10:23:23 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
ターミナルの別ウィンドウを開いてcurlします.
> curl http://localhost:3000/hello {"message":"hello world"}
hello worldが返ってきました.
LocalStackの設定
Docker networkの作成
> docker network create nw-sam-sample
docker-composeでLocalStackコンテナを作成・起動
今回はDynamoDBしか使いませんが, この記事をご覧の方の中には, DynamoDBとSQSを両方使いたい場合があるかもしれないので, 参考までに両方ホストするためのファイルを書いておきます.
docker-compose.yaml
version: "3.3" services: localstack: container_name: localstack-sam-sample image: localstack/localstack ports: - 8080:8080 - 4569:4569 # DynamoDB - 4576:4576 # SQS environment: - SERVICES=dynamodb,sqs networks: - nw-sam-sample networks: nw-sam-sample: external: true
LocalStackを起動します.
> docker-compose up -d
DynamoDB テーブルを作成
スキーマ情報のファイルを作成
Id
をプライマリキーとするテーブルを作っていきます.
schema.json
{ "AttributeDefinitions": [ { "AttributeName": "Id", "AttributeType": "S" } ], "TableName": "tb-sam-sample", "KeySchema": [ { "AttributeName": "Id", "KeyType": "HASH" } ], "BillingMode": "PAY_PER_REQUEST" }
LocalStackに作成
> aws dynamodb create-table --endpoint-url http://localhost:4569 --cli-input-json file://schema.json
コンテナをdownすると,テーブルが全て消えるので注意
確認
> aws dynamodb list-tables --endpoint-url http://localhost:4569 { "TableNames": [ "tb-sam-sample" ] }
Lambda関数の追加
サンプルプロジェクトに含まれているHelloWorldFunction
の下に,
DynamoDBとやり取りするLambda関数であるSamSampleFunction
を作成します.
今回は3つのパスを用意します.
POST /sample
:新たにリソースを作成GET /sample
:全てのリソースを表示GET /sample/{id}
:パスパラメータid
で指定したIdを持つリソースを表示
template.yaml
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > sam-sample Sample SAM Template for sam-sample # 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: nodejs12.x 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 # add SamSampleFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello-world/ Handler: sam-sample.handler Runtime: nodejs12.x Events: Create: Type: Api Properties: Path: /sample Method: post Index: Type: Api Properties: Path: /sample Method: get Show: Type: Api Properties: Path: /sample/{id} Method: get 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
次に,そのハンドラをhello-world/sam-sample.js
内に書いていきます.
注意点
Macの場合,
ハンドラ内のDynamoDBエンドポイントは
http://localhost:4569
ではなく
http://host.docker.internal:4569
としてください.
今回,Dockerコンテナ同士をDockerネットワークでつなぐので, localhostでは動きません.
ちなみに,Linuxではhttp://localstack:4569
になります.
hello-world/sam-sample.js
const AWS = require('aws-sdk'); AWS.config.update({ region: 'ap-northeast-1' }); const dynamoDBEndpoint = 'http://host.docker.internal:4569'; const dynamoDB = new AWS.DynamoDB({ endpoint: dynamoDBEndpoint }); const docClient = new AWS.DynamoDB.DocumentClient({ service: dynamoDB }); const tableName = 'tb-sam-sample'; exports.handler = async (event, context) => { try { switch (event.httpMethod) { case 'POST': { const payload = JSON.parse(event.body); await docClient.put({ Item: { Id: payload.id, Data: payload.data }, TableName: tableName }).promise(); return { statusCode: 200, body: {} } } case 'GET': { if (event.pathParameters && event.pathParameters.id) { const id = event.pathParameters.id; const res = await docClient.get({ Key: { Id: id }, TableName: tableName }).promise(); return { statusCode: 200, body: JSON.stringify(res.Item) } } else { const res = await docClient.scan({ TableName: tableName }).promise(); return { statusCode: 200, body: JSON.stringify(res.Items) } } } default: { return { statusCode: 400, body: JSON.stringify({ message: 'invalid input' }) } } } } catch (err) { console.log(err); return { statusCode: 500, body: JSON.stringify({ message: 'internal server error' }) } } };
起動
> sam local start-api --docker-network nw-sam-sample
確認
> curl http://localhost:3000/sample -X POST -d '{"id": "1", "data": "hello"}'
> curl http://localhost:3000/sample [{"Data":"hello","Id":"1"}]
> curl http://localhost:3000/sample/1 {"Data":"hello","Id":"1"}
完成です!
最後に
今回はAWS SAMとLocalStackでLambda関数のローカル開発環境を構築し, API Gatewayを通してDynamoDBのCRUDを行うサンプルアプリケーションを作成しました.
今回作成したアプリケーションは, LocalStackにアクセスするためのものなので, これを参考にしてデプロイする際は, 適宜環境変数を用いてAWSサービスに対するエンドポイントをスイッチしてください.
この記事が少しでも参考になれば幸いです.
今回作成したリポジトリです.