経緯
副業でちょいとお手伝いをしているところがありまして、フロントだけの開発〜って感じで聞いていたのですが、フロントエンドだけじゃどうにもならなくなってきてしまいまして。
本職ぺちぱーなので普通にLaravelとかで組んでもいいんですけど、先方のやってみたいことやっていいよ〜という温かい言葉に甘えて、こりゃいっちょ新しいことやってみっかな、ってなったのです。
で、やっぱ時代はサーバーレスじゃないですか。
サーバーレスでサーバーサイドは案件でAWS SAM
使ったNode.js
のはやったのですけど、せっかくなのでServerless Framework
でpython
使ってやってみようかなーって思い至ったわけです。
やりたい構成
難しい事やる気はサラサラなくて、API Gateway
からLambda
通ってDynamoDB
の読み書きするってやつですね。
んで、ここのLambda
とDynamoDB
のところをDockerコンテナとしてそれぞれ置こう!ってしたいわけですね。
docker-compose.yml
はこんな感じになりました。
version: '3'
services:
# フロントエンド開発用
web:
build: .
ports:
- 9080:9080
volumes:
- .:/app
stdin_open: true
tty: true
# serverless開発用
serverless:
build: ./serverless
environment:
- AWS_PROFILE=default
volumes:
- ~/.aws/:/root/.aws:ro
- ./serverless:/app
ports:
- 50000:5000
stdin_open: true
tty: true
dynamodb-local:
image: 'amazon/dynamodb-local'
container_name: dynamodb-local
user: root
ports:
- 8000:8000
volumes:
- ./dynamodb_data:/data
command: ["-jar", "DynamoDBLocal.jar", "-sharedDb", "-dbPath", "/data"]
dynamodb-admin:
image: aaronshaf/dynamodb-admin:latest
container_name: dynamodb-admin
environment:
- DYNAMO_ENDPOINT=dynamodb-local:8000
ports:
- 8001:8001
depends_on:
- dynamodb-local
で、serverless
コンテナのDockerfile
はこんな感じです。
FROM python:3.9.13-alpine
ARG AWS_PROFILE
ENV NODE_PATH /usr/lib/node_modules/
# install nodejs
RUN apk update \
&& apk add --no-cache nodejs npm curl
# install aws-cli
RUN pip install --upgrade pip
RUN pip install awscli werkzeug boto3
# install serverless framework
RUN npm install -g serverless serverless-plugin-existing-s3
# change work directory
WORKDIR /app/flask-dynamodb-api
それから、serverless.yml
はこんな感じになりました。
これはserverless
コマンドのflask-dynamodb-api
のテンプレートから自動生成されるやつにちょこっと書き加えたやつです。
service: ando-san-flask-dynamodb-api
frameworkVersion: '3'
custom:
tableName: 'users-table-${self:provider.stage}'
wsgi:
app: app.app
dynamodb:
start:
host: dynamodb-local
port: 8000
noStart: true
migrate: true
stages:
- dev
provider:
name: aws
region: ap-northeast-1
runtime: python3.9
stage: dev
iam:
role:
statements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource:
- Fn::GetAtt: [ UsersTable, Arn ]
environment:
USERS_TABLE: ${self:custom.tableName}
functions:
api:
handler: wsgi_handler.handler
events:
- httpApi: '*'
plugins:
- serverless-wsgi
- serverless-python-requirements
- serverless-dynamodb-local
resources:
Resources:
UsersTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: userId
AttributeType: S
KeySchema:
- AttributeName: userId
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
TableName: ${self:custom.tableName}
ポイントを解説していきましょう。
開発の起点どうすんべ
serverless
をpython
でチャレンジすると決めたので、Qiita漁って参考になりそうなのを探してきました。
こちらの記事を参考にさせていただきました。
とはいえ、4年前の記事なので、pythonのバージョン新しくしたいぜ!と思いイキってdockerhub
にあるほぼ最新の3.10.5
にしたら無事にserverless deploy
で死んだので一つ落として3.9
にしました。
aws cli
用のプロファイルは普通にホスト側にあるクレデンシャルを流用したかったので~/.aws/
をコンテナ側にそのままマウントして、環境変数AWS_PROFILE
を設定するようにdocker-compose.yml
に記述しています。
dynamodb-local って何?
dockerhubをウロウロしてたらdynamodb-local
というDockerイメージが有るのを見つけました。
Serverless Flameworkではdynamodbはserverless-dynamodb-local
というプラグインを使って、serverless dynamodb install
とやってserverless dynamodb start
するのが常套手段のようですが、以下の記事のとおり、別のプラグインによって道連れになって死ぬということがあるようです。
serverless-dynamodb-local
プラグインは使わないとどうにもならないのですが、serverless dynamodb install
を省きたいという意図です。
なので、この記事を参考にdynamodb-localも無事Dockerコンテナ化しました……というわけにもいかず、これだとmigrateが走らないのでテーブルが作られません。ふべん!ということで、serverless.yml
に一工夫を入れます。
custom:
tableName: 'users-table-${self:provider.stage}'
wsgi:
app: app.app
dynamodb:
start:
host: dynamodb-local
port: 8000
noStart: true
migrate: true
stages:
- dev
ここのhost
とnoStart
とmigrate
のところですね。
host
はdocker-compose.yml
にあるdynamodb-local
のコンテナ名ですね。
こうすることによってserverless dynamodb start
のコマンドを叩いたときにmigrateが動いてDockerコンテナのdynamodb-localの方にテーブルが作られます。
あ、もちろんapp.py
も書き換えておかないとDockerコンテナのdynamodb-localを見に行かないので注意が必要です。
dynamodb_client = boto3.client('dynamodb')
if os.environ.get('IS_OFFLINE'):
dynamodb_client = boto3.client(
'dynamodb', region_name='dynamodb-local', endpoint_url='http://dynamodb-local:8000'
)
ローカルでAPIのテストをしたいだけなのに
最後、いちばん大事なポイントです。
README.mdには
serverless wsgi serve
ってやるとローカルで動きまっせ
みたいなこと書いてあって、鵜呑みにしてやってみるのですが、一向にコンテナの外から叩けません。
docker-compose.yml
でちゃんとportのバインディングもしているのにねえ、と悩んでいたんですよ。
Running "serverless" from node_modules
Using Python specified in "runtime": python3.9
* Running on http://localhost:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 170-470-797
# serverless開発用
serverless:
build: ./serverless
environment:
- AWS_PROFILE=default
volumes:
- ~/.aws/:/root/.aws:ro
- ./serverless:/app
ports:
- 50000:5000
stdin_open: true
tty: true
Connection was forcibly closed by a peer
ってなんやねん。
コンテナ側でhttp://localhost:5000
ならホスト側でhttp://localhost:50000
にしたらつながるんと違うんかい!と思うじゃないですか。違うんですよこれが。
Running "serverless" from node_modules
Using Python specified in "runtime": python3.9
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 170-470-797
はい! この通り! http://0.0.0.0:5000
じゃないとつながらないのです!
こうするためにはこうしないといけないわけですね。
$ serverless wsgi server --host 0.0.0.0
これで無事、コンテナの外からAPIが叩けるようになって平和が訪れました。
さいごに
いかがでしたでしょうか。(言ってみたかった
これを参考にDockerでたのしいServerless開発ライフを送ってみてください。