🧰

FastAPI (mangum) を AWS Lambda で動かす

2023/09/10に公開

対象

  • PythonのFastAPIをAWS Lambdaで動かしたい人
  • カスタムイメージを使ってビルドしたい人
  • ローカルでの動作確認方法を知りたい人
  • AWSのリソース作成の概略を知りたい人

利用するライブラリのバージョン

以下のライブラリを利用します。

fastapiは執筆時点での最新版、mangumは0.11.0より新しいバージョンだとエラーが発生するため、このバージョンとしてます。
pydanticやstarlette, typing-extensionsはfastapiやmangumの依存ライブラリです。

fastapi==0.103.1
pydantic==2.3.0
starlette==0.27.0
mangum==0.11.0
typing-extensions==4.7.1

#カスタムイメージを利用する場合
#awslambdaric==2.0.7

概要

FastAPIはAPIのエンドポイントを複数公開できますが、AWS Lambdaは1つだけしか公開できません。そこで、mangumというライブラリを使うことで、複数のエンドポイントを1つにまとめることができます。

mangumを利用する際はエンドポイントが1つになるため、リクエストBodyにパスやメソッドなどを設定することで、FastAPIの処理を分岐させます。

パラメーター名 概要
path エンドポイントのパス
httpMethod エンドポイントを叩く際のメソッド (GET, POST, DELETE)
requestContext API Gateway利用時に値が入る (AWSのアカウントIDなど)
multiValueQueryStringParameters POSTメソッドの利用時などで送りたいパラメーターを設定する

サンプルコード

こちらで確認できます。

https://github.com/kusakabe-t/aws-lambda-fastapi-mangum-container

アプリ側のコード

使い方はすごく簡単です。以下のように、FastAPIのインスタンスを作成し、mangumの引数に渡すだけです。
ちなみに、ドキュメントを読む限りだと、mangumはデフォルトで8080ポートを使うようで、ポートの変更はできなさそうでした。

app.py
from fastapi import FastAPI
from mangum import Mangum

app = FastAPI()

@app.get('/test1')
async def test1():
  return {"message": "test"}

@app.post('/test2')
async def test():
  return {'message': "test2"}

@app.post('/test3')
async def test1(message: str):
  return {'message': message}

# export port 8080
handler = Mangum(app)

Dockerfile (AWSのイメージ利用する場合)

AWSが提供しているイメージを使う場合は以下のようになります。
requirements.txtに使用するPythonのライブラリのバージョンを記述してます。

特に引っかかる点はないと思うので、詳細はAWSのドキュメントやリポジトリを確認してみてください。

Dockerfile
FROM public.ecr.aws/lambda/python:3.8

COPY app.py requirements.txt ./

RUN pip install -r ./requirements.txt

CMD ["app.handler"]

Dockerfile (カスタムイメージを利用する場合)

AWSが提供するイメージ (public.ecr.aws/lambda/python:3.8) が利用できない場合もあると思います。その場合は自身でカスタムイメージ (Pythonを入れた任意のDockerイメージ) にLambdaのランタイムを追加することになります。

まず、requirements.txtにLambdaのランタイムであるawslambdaricを追加します。

requirements.txt
fastapi==0.103.1
pydantic==2.3.0
starlette==0.27.0
mangum==0.11.0
typing-extensions==4.7.1
awslambdaric==2.0.7

DockerfileではDocker起動時のデフォルトのディレクトリを/functionとし、アプリのコードを追加します。
最後にENTRYPOINTを設定し、awslambdaric経由でapp.handlerを動かします。

Dockerfile
FROM python:3.11

WORKDIR /function

COPY app.py requirements.txt ./

RUN pip install -r ./requirements.txt

ENTRYPOINT [ "/usr/local/bin/python", "-m", "awslambdaric" ]

CMD ["app.handler"]

ビルド方法

AWS公式イメージ

AWSの公式イメージを利用する場合は、以下のようにDockerコンテナを起動します。

docker run -p 9000:8080 <docker image name>

カスタムイメージ

カスタムイメージを利用する場合はaws-lambda-rieをローカルにインストールします。
インストール方法はGitHubの方を参考にしてください。

https://github.com/aws/aws-lambda-python-runtime-interface-client#local-testing

Dockerの起動コマンドは以下のようになります。

docker run -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 --entrypoint /aws-lambda/aws-lambda-rie <docker image name> /usr/local/bin/python -m awslambdaric app.handler

ローカルでの動作確認

エンドポイントは http://localhost:9000/2015-03-31/functions/function/invocations になります。
リクエストBodyには、path, httpMethod, requestContext, multiValueQueryStringParametersを設定します。

@app.get('/test1') にアクセスしたい場合はpathを /test1, httpMethodをGETに設定し、リクエストします。

curl -X "POST" "http://localhost:9000/2015-03-31/functions/function/invocations" \
-H 'Content-Type: application/json; charset=utf-8' \
-d $'{
  "path": "/test1",
  "requestContext": {},
  "httpMethod": "GET",
  "multiValueQueryStringParameters": {}
}'

@app.post('/test2')にアクセスしたい場合は、httpMethodをPOSTにします。

curl -X "POST" "http://localhost:9000/2015-03-31/functions/function/invocations" \
-H 'Content-Type: application/json; charset=utf-8' \
-d $'{
  "path": "/test2",
  "requestContext": {},
  "httpMethod": "POST",
  "multiValueQueryStringParameters": {}
}'

@app.post('/test3') にアクセスしたい場合は、multiValueQueryStringParametersにmessageを追加します。

curl -X "POST" "http://localhost:9000/2015-03-31/functions/function/invocations" \
-H 'Content-Type: application/json; charset=utf-8' \
-d $'{
  "path": "/test3",
  "requestContext": {},
  "httpMethod": "POST",
  "multiValueQueryStringParameters": {
    "message": "hoge"
  }
}'

ローカルでのテスト結果

AWSへのデプロイ

詳細は省きます。

まずローカルでイメージをビルドし、ECRにpushします。
ECRのサンプル

次に、ECRにpushしたイメージを使ってLambdaにデプロイします。
ローカルではENTRYPOINTやCMDを指定していましたが、Lambdaでは指定する必要がないので、空欄にしておきます。
Lambdaのサンプル

AWS Lambdaから直接URLを発行することもできますが、mangumが対応してないため、API Gatewayを経由させます。
POSTだけで良いので、リクエストをLambdaに送るように設定します。APIのタイプはREST APIです。
API Gatewayのサンプル

デプロイ後の動作確認はエンドポイントを変えるだけで、ローカルと同じです。

以上。

GitHubで編集を提案

Discussion