AWS SAMでローカル環境でS3とDynamoDBを扱うLambdaを実行する
AWS SAMでローカル環境でS3とDynamoDBを扱うLambdaを実行する:
Serverlessconf Tokyo 2018で色々と刺激を受け、Lambdaに取り組んでみようと思い、色々と試す上でLambdaをローカル環境で開発や動作確認をするのに色々迷う部分が多かったので、メモとして残したものです。
以下のものを使用しています。
無事に成功すると以下のようなパッケージ構成が作成されます。
まずは自動的に作成された、hello_world関数を実行してみます。
無事に動作確認ができました。
今回はS3とDynamoDBをエミュレートするために、LocalStackを使用しました。
https://github.com/localstack/localstack
LocalStack自体はdockerで立ち上げます。
以下のように
s3とdynamodbがそれぞれ使用するポートはこちら
※初回はイメージのダウンロードがあるため、時間がかかります。
LocalStack用にcredential情報を追加します。
AWSのドキュメントを参考にテーブルを作成します。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Tools.CLI.html
LocakStackのS3にバケットの作成と、実ファイルの配置を行います。
テストデータは以下の内容で作成しました。
まずは依存関係に
https://github.com/boto/boto3
関数の作成
今回はs3_dynamoという関数にしました。
SAMのテンプレートの
ローカル実行用のプロファイルとして、以下のファイルも合わせて作成しました。
SAM CLIの機能で各Lambda関数のイベントを生成できるようになっているので、S3をputするイベント定義を作成します。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/test-sam-cli.html
わかりにくいですが、
出力されたイベントファイルがこちら。
まずはS3からファイルを読み取れることを確認するために、以下のようにS3からファイルを読んで標準出力に表示する部分だけを実装します。
sam localで実行する場合もdocker内部から実行されるようで、ホスト名は
実際に実行してみます。
無事にbuket、keyが渡され、S3ファイルの中身を表示することができています。
以下が最終型です。
わかりにくいですが、以下のコードを追加しています。
デフォルトではLambdaの実行時間上限は3秒になっているので、適当に伸ばしておきます。
再度実行してみます。
AWS CLIでMusicテーブルの中身を確認
無事にDynamoDBのテーブルにデータがputされています。
今までLambdaのコンソール上で短いコードを書いて実行するといったことはやったことがありましたが、実際にローカルに開発環境を用意して実行してみるという部分は初めてだったので、ハマりどころや分からない部分も多くありました。
実際にSAMを使用してAWS上にデプロイするにはCloudFormationを使うなど、もう少し学ぶところがありそうだという印象でした。
Serverlessのメリットを享受するために、これから吸収していければと思います。
この投稿について
Serverlessconf Tokyo 2018で色々と刺激を受け、Lambdaに取り組んでみようと思い、色々と試す上でLambdaをローカル環境で開発や動作確認をするのに色々迷う部分が多かったので、メモとして残したものです。
動作環境
以下のものを使用しています。- AWS SAM
- AWS CLI
- docker
- docker-compose
# Pythonはvenv $ python --version Python 3.6.6 $ aws --version aws-cli/1.16.24 Python/3.7.0 Darwin/18.0.0 botocore/1.12.14 $ sam --version SAM CLI, version 0.6.0
SAMプロジェクトの作成
sam initコマンドを使用して空のプロジェクトを作成します。- -r はruntime
- -n はプロジェクト名
$ sam init -r python3.6 -n sam-s3-lambda-app
$ cd sam-s3-lambda-app $ tree . . ├── README.md ├── hello_world │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-37.pyc │ │ └── app.cpython-37.pyc │ └── app.py ├── requirements.txt ├── template.yaml ├── tests │ └── unit │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-37.pyc │ │ └── test_handler.cpython-37.pyc │ └── test_handler.py
hello_worldの動作確認
まずは自動的に作成された、hello_world関数を実行してみます。# ライブラリのインストール
$ pip install -r requirements.txt -t hello_world/build/
$ cp hello_world/*.py hello_world/build/
# イベントの生成
$ sam local generate-event apigateway aws-proxy > api_event.json
# テスト実行
$ sam local invoke HelloWorldFunction --event api_event.json
2018-10-25 18:31:13 Invoking app.lambda_handler (python3.6)
Fetching lambci/lambda:python3.6 Docker container image......
.
.
.
{"statusCode": 200, "body": "{\"message\": \"hello world\", \"location\": \"IP Address\"}"}
実際にS3とDynamoDBを扱う部分を実装
LocalStackコンポーネントの作成
今回はS3とDynamoDBをエミュレートするために、LocalStackを使用しました。https://github.com/localstack/localstack
LocalStack自体はdockerで立ち上げます。
以下のように
docker-compose.ymlファイルを作成します。docker-compose.yml
version: "3.3"
services:
localstack:
container_name: localstack
image: localstack/localstack
ports:
- "4569:4569"
- "4572:4572"
environment:
- SERVICES=dynamodb,s3
- DEFAULT_REGION=ap-northeast-1
- DOCKER_HOST=unix:///var/run/docker.sock
- s3:4572
- dynamodb:4569
※初回はイメージのダウンロードがあるため、時間がかかります。
$ docker-compose up
LocalStack用credentialの作成
LocalStack用にcredential情報を追加します。~/.aws/credentials
[localstack] aws_access_key_id = dummy aws_secret_access_key = dummy
~/.aws/config
[profile localstack] region = ap-northeast-1 output = json
LocalStack上にDynamoDBのテーブルを作成
AWSのドキュメントを参考にテーブルを作成します。https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Tools.CLI.html
$ aws dynamodb create-table \ > --table-name Music \ > --attribute-definitions \ > AttributeName=Artist,AttributeType=S \ > AttributeName=SongTitle,AttributeType=S \ > --key-schema AttributeName=Artist,KeyType=HASH AttributeName=SongTitle,KeyType=RANGE \ > --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1 \ > --endpoint-url http://localhost:4569 --profile localstack $ aws dynamodb list-tables --endpoint-url http://localhost:4569 --profile localstack TABLENAMES Music
LocalStack上のS3にテストデータを配置
LocakStackのS3にバケットの作成と、実ファイルの配置を行います。# bucketの作成 $ aws s3 mb s3://music/ --endpoint-url=http://localhost:4572 --profile localstack make_bucket: music # テストデータのput $ aws s3 cp ./testdata.json s3://music/rawdata/testdata.json --endpoint-url=http://localhost:4572 --profile localstack upload: ./testdata.json to s3://music/rawdata/testdata.json
testdata.json
{
"Artist": "Acme Band",
"SongTitle": "Happy Day",
"AlbumTitle": "Songs About Life"
}
関数のベースを作成
まずは依存関係にboto3を追加します。boto3はPythonでS3やDynamoDBなどのリソースを扱うためのライブラリです。https://github.com/boto/boto3
$ pip install boto3 $ pip freeze > requirements.txt
今回はs3_dynamoという関数にしました。
$ mkdir s3_dynamo $ touch s3_dynamo/__init__.py s3_dynamo/app.py
Resourcesセクションに、作成した関数を追記します。template.yml
Resources:
S3DynamoFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: s3_dynamo/build/
Handler: app.lambda_handler
Runtime: python3.6
env-local.json
{
"S3DynamoFunction": {
"AWS_SAM_LOCAL": true
}
}
S3イベントの作成
SAM CLIの機能で各Lambda関数のイベントを生成できるようになっているので、S3をputするイベント定義を作成します。https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/test-sam-cli.html
わかりにくいですが、
--keyにはS3のバケット配下のパスを渡します。$ sam local generate-event s3 put --bucket music --key rawdata/testdata.json --region ap-northeast-1 > s3_event.json
awsRegion,bucket.name,object.key が合っていれば正しくLambda側で判定できそうです。s3_event.json
{
"Records": [
{
"eventVersion": "2.0",
"eventSource": "aws:s3",
"awsRegion": "ap-northeast-1",
.
.省略
.
"s3": {
"s3SchemaVersion": "1.0",
"configurationId": "testConfigRule",
"bucket": {
"name": "music",
"ownerIdentity": {
"principalId": "EXAMPLE"
},
"arn": "arn:aws:s3:::music"
},
"object": {
"key": "rawdata/testdata.json",
"size": 1024,
"eTag": "0123456789abcdef0123456789abcdef",
"sequencer": "0A1B2C3D4E5F678901"
}
}
}
]
}
S3からの読み込み処理を実装
まずはS3からファイルを読み取れることを確認するために、以下のようにS3からファイルを読んで標準出力に表示する部分だけを実装します。s3_dynamo/app.py
import os
import json
import boto3
import pprint
import urllib.parse
if os.getenv("AWS_SAM_LOCAL"):
dynamodb = boto3.resource(
'dynamodb',
endpoint_url='http://host.docker.internal:4569/'
)
s3 = boto3.client(
's3',
endpoint_url='http://host.docker.internal:4572/'
)
else:
dynamodb = boto3.resource('dynamodb')
s3 = boto3.client('s3')
def lambda_handler(event, context):
bucket = event['Records'][0]['s3']['bucket']['name']
key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
print("[bucket]: " + bucket + " [key]: " + key)
try:
response = s3.get_object(Bucket=bucket, Key=key)
d = json.loads(response['Body'].read())
pprint.pprint(d)
except Exception as e:
print(e)
raise e
env-local.jsonを使用して実行するため、今回はS3 / DynamoDBとしては以下の定義が使われます。sam localで実行する場合もdocker内部から実行されるようで、ホスト名は
host.docker.internalを使っています。dynamodb = boto3.resource(
'dynamodb',
endpoint_url='http://host.docker.internal:4569/'
)
s3 = boto3.client(
's3',
endpoint_url='http://host.docker.internal:4572/'
)
$ pip install -r requirements.txt -t s3_dynamo/build
$ cp s3_dynamo/*.py s3_dynamo/build/
$ sam local invoke S3DynamoFunction --event s3_event.json --profile localstack
# result
[bucket]: music [key]: rawdata/testdata.json
{'AlbumTitle': 'Songs About Life',
'Artist': 'Acme Band',
'SongTitle': 'Happy Day'}
DynamoDBへputするコードの実装
以下が最終型です。s3_dynamo/app.py
import os
import json
import boto3
import pprint
import urllib.parse
if os.getenv("AWS_SAM_LOCAL"):
dynamodb = boto3.resource(
'dynamodb',
endpoint_url='http://host.docker.internal:4569/'
)
s3 = boto3.client(
's3',
endpoint_url='http://host.docker.internal:4572/'
)
else:
dynamodb = boto3.resource('dynamodb')
s3 = boto3.client('s3')
table = dynamodb.Table('Music')
def lambda_handler(event, context):
bucket = event['Records'][0]['s3']['bucket']['name']
key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
print("[bucket]: " + bucket + " [key]: " + key)
try:
response = s3.get_object(Bucket=bucket, Key=key)
d = json.loads(response['Body'].read())
pprint.pprint(d)
table.put_item(
Item=d
)
except Exception as e:
print(e)
raise e
table = dynamodb.Table('Music')
.
.
.
table.put_item(
Item=d
)
LambdaのTimeoutを伸ばす
デフォルトではLambdaの実行時間上限は3秒になっているので、適当に伸ばしておきます。template.yml
Globals:
Function:
Timeout: 100
結果確認
再度実行してみます。$ cp s3_dynamo/*.py s3_dynamo/build/ $ sam local invoke S3DynamoFunction --event s3_event.json --profile localstack
$ aws dynamodb scan --table-name Music --endpoint-url http://localhost:4569 --profile localstack 1 1 ALBUMTITLE Songs About Life ARTIST Acme Band SONGTITLE Happy Day
おわりに
今までLambdaのコンソール上で短いコードを書いて実行するといったことはやったことがありましたが、実際にローカルに開発環境を用意して実行してみるという部分は初めてだったので、ハマりどころや分からない部分も多くありました。実際にSAMを使用してAWS上にデプロイするにはCloudFormationを使うなど、もう少し学ぶところがありそうだという印象でした。
Serverlessのメリットを享受するために、これから吸収していければと思います。
コメント
コメントを投稿