💭

LocalStack上にAPI Gateway、Lambda、DynamoDB環境を構築してみる

2021/12/16に公開

はじめに

LocalStack上にAPI Gateway、Lambda、DynamoDBを環境を構築する。
CurlでApiを叩き。LambdaでDynamoDBからデータを取得しレスポンスを返す処理を実装する。

動作確認環境

WSL2上のUbuntu 20.04でLocalStackを使ってみる」で構築した環境を使い作成する。

準備

作業フォルダー作成

各種ファイルを置く作業フォルダーを作成する

hoge@AA:~$ mkdir aws-console
hoge@AA:~$ cd aws-console

docker-compose.yml

docker-compose.ymlファイルを作成する。
必要なSERVICESを指定する。

docker-compose.yml 編集後の状態
version: "3.8"

services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME-localstack_main}"
    image: localstack/localstack:0.13.1
    network_mode: bridge
    ports:
      - "4566:4566"
      - "4571:4571"
    environment:
      - SERVICES=apigateway,lambda,dynamodb
      - DEBUG=${DEBUG- }
      - DATA_DIR=${DATA_DIR- }
      - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR- }
      - LOCALSTACK_API_KEY=${LOCALSTACK_API_KEY- }  # only required for Pro
      - HOST_TMP_FOLDER=${TMPDIR:-/tmp/}localstack
      - DOCKER_HOST=unix:///var/run/docker.sock
      - LAMBDA_EXECUTOR=docker-reuse
    volumes:
      - "${TMPDIR:-/tmp}/localstack:/tmp/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"

DynamoDB

テーブル作成する。

hoge@AA:~/aws-console$ awslocal dynamodb create-table --table-name myUserTable \
--attribute-definitions \
    AttributeName=Name,AttributeType=S \
    AttributeName=Index,AttributeType=N \
--key-schema \
    AttributeName=Name,KeyType=HASH \
    AttributeName=Index,KeyType=RANGE \
--provisioned-throughput \
    ReadCapacityUnits=10,WriteCapacityUnits=5 \
--profile=localstack

テーブル追加を確認する。

hoge@AA:~/aws-console$ awslocal dynamodb list-tables \
--profile=localstack
{
    "TableNames": [
        "myUserTable",
    ]
}

[ex] テーブル削除する。

hoge@AA:~/aws-console$ awslocal dynamodb delete-table \
--table-name myUserTable --profile=localstack

アイテム追加する

hoge@AA:~/aws-console$ awslocal dynamodb put-item --table-name myUserTable \
--item '{"Name":{"S": "Ishida Mio"}, "Index":{"N": "1000"}, "Gender":{"S": "Female"}, "Tel":{"S": "0769625106"}, "Mail":{"S": "mio1094@ccfrevk.ll"}, "Birthday":{"S": "1982/02/26"}}' \
--profile=localstack
hoge@AA:~/aws-console$ awslocal dynamodb put-item --table-name myUserTable \
--item '{"Name":{"S": "Shimura Kokoro"}, "Index":{"N": "1001"}, "Gender":{"S": "Female"}, "Tel":{"S": "0829490871"}, "Mail":{"S": "kokoro414@bevhlk.xju"}, "Birthday":{"S": "1990/02/17"}}' \
--profile=localstack
hoge@AA:~/aws-console$ awslocal dynamodb put-item --table-name myUserTable \
--item '{"Name":{"S": "Ishimura Masaki"}, "Index":{"N": "1002"}, "Gender":{"S": "Male"}, "Tel":{"S": "0848254480"}, "Mail":{"S": "masaki_ishimura@kokn.qgj"}, "Birthday":{"S": "1992/06/20"}}' \
--profile=localstack
hoge@AA:~/aws-console$ awslocal dynamodb put-item --table-name myUserTable \
--item '{"Name":{"S": "Shirai Tomoko"}, "Index":{"N": "1003"}, "Gender":{"S": "Female"}, "Tel":{"S": "0742216492"}, "Mail":{"S": "tomoko225@vlyqs.pxt"}, "Birthday":{"S": "1982/09/29"}}' \
--profile=localstack
hoge@AA:~/aws-console$ awslocal dynamodb put-item --table-name myUserTable \
--item '{"Name":{"S": "Yasui Tomoyuki"}, "Index":{"N": "1004"}, "Gender":{"S": "Male"}, "Tel":{"S": "095953680"}, "Mail":{"S": "tomoyuki772@jheauwpl.lyo"}, "Birthday":{"S": "1996/04/23"}}' \
--profile=localstack

アイテム追加されているかを確認する。

hoge@AA:~/aws-console$ awslocal dynamodb scan --table-name myUserTable \
--profile=localstack
{
    "Items": [
        {
            "Index": {
                "N": "1000"
            },
            "Tel": {
                "S": "0769625106"
            },
            "Birthday": {
                "S": "1982/02/26"
            },
            "Gender": {
                "S": "Female"
            },
            "Mail": {
                "S": "mio1094@ccfrevk.ll"
            },
            "Name": {
                "S": "Ishida Mio"
            }
        },
〜略〜

[ex] アイテムを個別で取得する。

hoge@AA:~/aws-console$ awslocal dynamodb get-item --consistent-read \
--table-name myUserTable \
--key '{ "Name": {"S": "Ishida Mio"}, "Index": {"N": "1000"}}' \
--profile=localstack

Lambda

Functionを作成する。[1]

Users.py
import os
import boto3
from boto3.session import Session
from datetime import datetime

session = Session(
    aws_access_key_id='dummy',
    aws_secret_access_key='dummy',
    region_name='us-east-1'
)

if os.getenv('LOCALSTACK_HOSTNAME') is None:
    endpoint = 'http://localhost:4566'
else:
    endpoint=f"http://{os.environ['LOCALSTACK_HOSTNAME']}:4566"

dynamodb = session.resource(
    service_name='dynamodb', 
    endpoint_url=endpoint
)

def getUsers(event, context):
    table = dynamodb.Table('myUserTable')
    data = []
    response = table.scan()
    while True:
        data.extend(response["Items"])
        if "LastEvaluatedKey" not in response:
            break
        response = table.scan(ExclusiveStartKey=response["LastEvaluatedKey"])

    return {
        'statusCode': 200,
        'body':{ 
            'users':data
        }
    }

zip形式で圧縮をする。

hoge@AA:~/aws-console$ zip lambda.zip Users.py

Functionを登録する。

hoge@AA:~/aws-console$ awslocal lambda create-function \
--runtime python3.8 \
--function-name users \
--handler Users.getUsers \
--zip-file fileb://lambda.zip \
--role r1 \
--profile=localstack

Functionを実行する。[2]

hoge@AA:~/aws-console$ awslocal lambda invoke \
--cli-binary-format raw-in-base64-out \
--function-name users \
--profile=localstack result.log
hoge@AA:~/aws-console$ cat result.log | jq

[ex] Functionを再登録する。

hoge@AA:~/aws-console$ awslocal lambda update-function-code --function-name users --zip-file fileb://lambda.zip --profile=localstack

[ex] Functionを削除する。(再登録で更新されない場合)

hoge@AA:~/aws-console$ awslocal lambda delete-function --function-name users --profile=localstack

API Gateway

作成する。

hoge@AA:~/aws-console$ awslocal apigateway create-rest-api --name 'UserApi' --profile=localstack
{
    "id": "XXXXXXXX",
    "name": "UserApi",
    "createdDate": "2020-10-02T22:03:27+09:00",
    "version": "V1",
    "binaryMediaTypes": [],
    "apiKeySource": "HEADER",
    "endpointConfiguration": {
        "types": [
            "EDGE"
        ]
    },
    "tags": {},
    "disableExecuteApiEndpoint": false
}

create-rest-apiのレスポンスにある、idの値(XXXXXXXXの部分)をメモしておく。

ルートリソースを確認する。(XXXXXXXXはメモしたidと置き換える)

hoge@AA:~/aws-console$ awslocal apigateway get-resources --rest-api-id XXXXXXXX --profile=localstack
{
    "items": [
        {
            "id": "YYYYYYYY",
            "path": "/"
        }
    ]
}

get-resourcesのレスポンスにある、idの値(YYYYYYYYの部分)をメモしておく。

ルートリソースを親として、新しくリソースを作成する。(XXXXXXXX、YYYYYYYYはメモしたidと置き換える)

hoge@AA:~/aws-console$ awslocal apigateway create-resource \
--rest-api-id XXXXXXXX \
--parent-id YYYYYYYY \
--path-part {proxy+} \
--profile=localstack
{
    "id": "ZZZZZZZZ",
    "parentId": "YYYYYYYY",
    "pathPart": "{proxy+}",
    "path": "/{proxy+}"
}

create-resourceのレスポンスにある、idの値(ZZZZZZZZの部分)をメモしておく。

作成したリソースに対してメソッドを追加する。(XXXXXXXX、ZZZZZZZZはメモしたidと置き換える)

hoge@AA:~/aws-console$ awslocal apigateway put-method \
--rest-api-id XXXXXXXX \
--resource-id ZZZZZZZZ \
--http-method ANY \
--authorization-type "NONE"  \
--profile=localstack
{
    "httpMethod": "ANY",
    "authorizationType": "NONE",
    "apiKeyRequired": false
}

インテグレーションの設定に必要な、AWS Lambda関数のARNを取得する。

hoge@AA:~/aws-console$ awslocal lambda get-function --function-name users \
--query 'Configuration.FunctionArn' \
--profile=localstack
"arn:aws:lambda:us-east-1:000000000000:function:users"

get-functionで取得したARN「arn:aws:lambda:us-east-1:000000000000:function:users」をメモしておく。

インテグレーションの設定をする。[3]
(XXXXXXXX、ZZZZZZZZはメモしたidと置き換える、[ANR]はメモしたARNに置き換える。)

hoge@AA:~/aws-console$ awslocal apigateway put-integration \
--rest-api-id XXXXXXXX \
--resource-id ZZZZZZZZ \
--http-method ANY \
--type AWS_PROXY \
--integration-http-method POST \
--uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/[ANR]/invocations \
--credentials arn:aws:iam::000000000000:role/apigAwsProxyRole
{
    "type": "AWS_PROXY",
    "httpMethod": "POST",
    "uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:000000000000:function:users/invocations",
    "requestParameters": {},
    "passthroughBehavior": "WHEN_NO_MATCH",
    "cacheNamespace": "ad27c65a",
    "cacheKeyParameters": []
}

デプロイする。

hoge@AA:~/aws-console$ awslocal apigateway create-deployment \
--rest-api-id XXXXXXXX \
--stage-name test \
--profile=localstack
{
    "id": "AAAAAAAA",
    "description": "",
    "createdDate": "2020-10-02T22:23:27+09:00"
}

動作確認

API呼び出し方法
API Gateway(LocalStack)のローカルのアクセスは下記のようになる。

http://localhost:4566/restapis/<apiId>/<stageId>/_user_request_/<path>
  • <apiId>はデプロイ設定のrest-api-id「XXXXXXXX」
  • <stageId>はデプロイ設定のstage-name「test」
  • <path>はなんでも通す設定になっている

実行例

hoge@AA:~/aws-console$ curl -X GET -s http://localhost:4566/restapis/XXXXXXXX/test/_user_request_/users | jq
{
  "users": [
    {
      "Index": 1000,
      "Tel": "0769625106",
      "Birthday": "1982/02/26",
      "Gender": "Female",
      "Mail": "mio1094@ccfrevk.ll",
      "Name": "Ishida Mio"
    },
    {
      "Index": 1002,
      "Tel": "0848254480",
      "Birthday": "1992/06/20",
      "Gender": "Male",
      "Mail": "masaki_ishimura@kokn.qgj",
      "Name": "Ishimura Masaki"
    },
〜略〜

参考にしたサイト

脚注
  1. return部分の構造が要件を満たしていないとまずい ↩︎

  2. データに日本語が含まれていると\uxxxx文字列になる。また、デコードできない文字列になる。 ↩︎

  3. どこかの設定によっては、integration-http-method「POST」に設定しないとダメ? ↩︎

Discussion