FastAPIをLambda/API Gatewayで動かそう
概要
FastAPIでREST APIを作成しました。
本番はAWSのLambda/API Gatewayで運用します。
今後、新しいアプリを作成した時に設定を使いまわしたいので、CDKで書きます。
実装
CDKアプリケーションの作成
FastAPIのルート直下にcdk
ディレクトリを作成し管理することにしました。
サンプルコードが多いため、CDKの言語はTypeScriptを採用します。
mkdir cdk
cd cdk
cdk init app --language typescript
Lambda・API Gatewayの設定
公式ドキュメントを参考にして実装してみました。
@aws-cdk/aws-apigatewayv2
を使用すると、「Argument type Function is not assignable to parameter type IFunction」型エラーを解決できなかったので、@aws-cdk/aws-apigatewayv2-alpha
を使用しました。
import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import { Construct } from "constructs";
import { HttpLambdaIntegration } from "@aws-cdk/aws-apigatewayv2-integrations-alpha";
import { HttpMethod, HttpApi } from "@aws-cdk/aws-apigatewayv2-alpha";
export class CdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const fastApiLambda = new lambda.Function(this, "FastApiLambda", {
runtime: lambda.Runtime.PYTHON_3_12,
handler: "main.app",
code: lambda.Code.fromAsset("../app"),
});
const fastApiIntegration = new HttpLambdaIntegration(
"FastApiIntegration",
fastApiLambda
);
const httpApi = new HttpApi(this, "HttpApi");
httpApi.addRoutes({
path: "/{proxy+}",
methods: [HttpMethod.ANY],
integration: fastApiIntegration,
});
}
}
Mangum
FastAPIのエンドポイントとLambda関数を紐づけていきます。
Lambdaは1関数:1エンドポイントですが、1関数:複数エンドポイントを持つようにしてくれるmangum
をインストールします。
pip install mangum
app
をmangum
に取り付けるだけです
app = FastAPI()
@app.get("/test1", status_code=200)
async def root():
return {"message": "Successfully API test1"}
@app.get("/test2", status_code=200)
async def root():
return {"message": "Successfully API test2"}
handler = Mangum(app)
依存関係用意
Lambdaにデプロイしても、fastapi, mangumがないと怒られます。
パッケージ化してLambdaのレイヤーに設定する必要があります。
パッケージ化
毎回決まったコマンドなので、シェルスクリプトを用意しました。
mkdir python
pip install \
--platform manylinux2014_aarch64 \
--target=python/python/lib/python3.12/site-packages/ \
--implementation cp \
--python-version 3.12 \
--only-binary=:all: --upgrade \
-r requirements.txt
cd python
zip -r ../fastapi-layer.zip .
cd ..
rm -rf python
// ディレクトリ構成
app-root/
├──app/
├──cdk/
└──layer.sh
LayerをLambdaに設定してあげます。
const layer = new lambda.LayerVersion(this, "CustomLayer", {
code: lambda.Code.fromAsset("../app/fastapi-layer.zip"), // 作成したパッケージ名
compatibleRuntimes: [lambda.Runtime.PYTHON_3_12],
description: "FastAPI and Mangum dependencies",
compatibleArchitectures: [Architecture.ARM_64],
});
const fastApiLambda = new lambda.Function(this, "FastApiLambda", {
runtime: lambda.Runtime.PYTHON_3_12,
handler: "main.handler", // エントリーポイントを変更
code: lambda.Code.fromAsset("../app"),
layers: [layer], // 追加
architecture: Architecture.ARM_64,
});
テスト用に作成したエンドポイントに向かってGETリクエストを投げてみると成功!
備考
Lambdaのテスト方法
- GETであればブラウザから実行
- Lambdaのテストイベントでテンプレート「apigateway-aws-proxy」を選択して
path
,httpMethod
を変更して実行
FastAPIディレクトリ構成
ディレクトリ構成に悩んだため、備忘録として残しておきます。
app-root/
├──app/
│ ├──routers/
│ ├──main.py
├──cdk/
└──venv/
routersからimportしたい。
from app.routers import user
from fastapi.responses import JSONResponse
app = FastAPI()
app.include_router(user.router)
Lambda実行時に発生したエラー
Unable to import module 'main': No module named 'app’
事象:appディレクトリがLambdaにアップロードされず見つからない。routers
やmain.py
がルートディレクトリに置かれる。
from .routers
と相対パスにしても「Unable to import module 'main': attempted relative import with no known parent package」でダメでした。
最終的にapp-root
から必要ないディレクトリを除外してアップロードすることで解決しました。
もっと効率がいい方法ありそう。
code: lambda.Code.fromAsset("../../app-root", {
exclude: ["cdk", "venv", "fastapi-layer.zip"],
}),
Discussion