Lambda Web Adapter を利用して FastAPI を AWS Lambda で実行する方法
Lambda Web Adapter は、 AWS Lambda を利用したアプリケーション開発を劇的に加速させました。 大抵の開発者が慣れている Docker を利用して、 AWS Lambda でバックエンドのサービスを開発できます。
この記事では、 Lambda Web Adapter を利用して FastAPI で API バックエンドを開発する方法を紹介します。 私の GitHub リポジトリから、サンプルをクローンできます。
概要
利点
Lambda Web Adapter の利点は、以下が挙げられます。
比較項目 | Lambda Web Adapter | Traditional Lambda |
---|---|---|
ローカルでのテスト | Easy | Hard |
Fargate のような他サービスへのマイグレーション | Easy | Hard |
AWS リソース数 | Less 1 | More |
コスト | Low (because of overhead) | Very low |
- 1つの Lambda 関数と1つの API Gateway ルートが存在するだけです。
AWS 設計
旧来型
API Gateway と Lambda 関数を利用するこれまでの AWS 設計だと、以下のようになります。
多くの API を追加する場合、カオスになるかもしれません。
Lambda Web Adapter 利用
Lambda Web Adapter は、以下のように AWS 設計をシンプルにします。
多数の API Gateway ルートと Lambda 関数を管理する必要がありません。
プロジェクト構造
この記事では、以下のプロジェクト構造を生成します。
/
|-- .venv/
|-- cdk/
| |-- bin/
| | `-- cdk.ts
| |-- lib/
| | `-- cdk-stack.ts
| |-- node_modules/
| |-- test/
| | `-- cdk.test.ts
| |-- .gitignore
| |-- .npmignore
| |-- biome.json
| |-- cdk.json
| |-- jest.config.js
| |-- package-lock.json
| |-- package.json
| |-- README.md
| `-- tsconfig.json
|-- docker/
| |-- Dockerfile-web
| `-- docker-compose.yml
|-- node_modules/
|-- src/
| |-- main.py
| `-- requirements.txt
|-- .gitignore
|-- package-lock.json
`-- package.json
はじめに
AWS CDK 環境の Bootstrapping
最初に、 AWS CDK 環境を Bootstrap してください。
この記事では、 Global の npm 環境を汚さないよう、ローカルで AWS CDK をインストールします。
npm i -D aws-cdk
npx cdk bootstrap aws://<AWS_ACCOUNT_ID>/<AWS_REGION>
CDK プロジェクト初期化
CDK プロジェクトを初期化するために、以下のコマンドを実行してください。
mkdir cdk && cd cdk
npx cdk init app --language typescript
FastAPI セットアップ
FastAPI をインストールするために、以下のコマンドを実行してください。
python -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]"
mkdir src
pip freeze > ./src/requirements.txt
FastAPI を利用した Backend API
API 実装
この記事では、 FastAPI の例をそのまま利用します。
以下のコードを記述して、 ./src/main.py
に保存してください。
from typing import Union
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Union[str, None] = None):
return {"item_id": item_id, "q": q}
API テスト
以下のコマンドでサーバーを起動できます。
fastapi dev ./src/main.py
サーバーが起動していることが確認できます。
...
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [73992] using WatchFiles
INFO: Started server process [73997]
INFO: Waiting for application startup.
INFO: Application startup complete.
お好みのツールを利用して API を実行してください。レスポンスが返ってくるはずです。
curl "http://127.0.0.1:8000/"
{"Hello":"World"}
curl "http://127.0.0.1:8000/items/1?q=keyword"
{"item_id":1,"q":"keyword"}
Docker でコンテナ化
Dockerfile
FastAPI バックエンドをコンテナ化するために、以下のコードを記述して、 ./docker/Dockerfile-web
に保存してください。
重要なポイントは、次のとおりです。
- AWS が提供する
public.ecr.aws/docker/library/python:3.12-alpine
をベースイメージとして利用 (行1) - Lambda Web Adapter をコピー (行15)
- Lambda Web Adapter がデフォルトで利用する 8080 ポートをリスン (行16)
By default, Lambda Web Adapter assumes the web app is listening on port 8080. If not, you can specify the port via configuration.
FROM public.ecr.aws/docker/library/python:3.12-alpine AS base
ENV APP_ROOT=/code
COPY ./src/requirements.txt $APP_ROOT/
RUN pip install --no-cache-dir --upgrade -r $APP_ROOT/requirements.txt
FROM base AS dev
ENV ENV=dev
EXPOSE 8000
CMD ["sh", "-c", "fastapi run $APP_ROOT/main.py --port 8000"]
FROM base
ENV ENV=prod
EXPOSE 8080
COPY ./src $APP_ROOT
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter
CMD ["sh", "-c", "fastapi run $APP_ROOT/main.py --port 8080"]
ローカル開発用 Docker Compose
この記事ではデータベースを利用しませんが、ほとんどの場合、 DynamoDB, MySQL のようなデータベースを利用します。
そのため、 ./docker/docker-compose.yml
を用意しておくと、ローカル開発に役立つと思います。
services:
api:
build:
context: ../
dockerfile: ./docker/Dockerfile-web
target: dev
ports:
- "8000:8000"
volumes:
- ../src:/code
以下のコマンドでバックエンドのサービスを実行できます。
cd docker
docker compose up
...
api-1 | INFO: Started server process [1]
api-1 | INFO: Waiting for application startup.
api-1 | INFO: Application startup complete.
api-1 | INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
AWS へのデプロイ
AWS CDK を利用した AWS リソースの定義
残りのやるべきことは、 AWS CDK を利用して AWS リソースを定義することだけです。
以下を cdk/bin/cdk.ts
にペーストしてください。
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { CdkStack } from '../lib/cdk-stack';
const app = new cdk.App();
new CdkStack(app, 'App');
以下を cdk/lib/cdk-stack.ts
にペーストしてください。コードの少なさに驚くかもしれません。
以下にご注意ください。
- タイムアウトを回避するため、
memorySize
(行19) は 128 より大きいほうが望ましいです。 platform: Platform.LINUX_AMD64
(行22) は、 Apple Silicon を利用している場合に必要です。そうしないと、Error: fork/exec /opt/extensions/lambda-adapter: exec format error Extension.LaunchError
というエラーが発生します。
import * as cdk from 'aws-cdk-lib';
import type { Construct } from 'constructs';
import { LambdaRestApi } from 'aws-cdk-lib/aws-apigateway';
import {
DockerImageCode,
DockerImageFunction,
LoggingFormat,
} from 'aws-cdk-lib/aws-lambda';
import * as path from 'node:path';
import { Platform } from 'aws-cdk-lib/aws-ecr-assets';
export class CdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const lambda = new DockerImageFunction(this, 'function', {
functionName: 'fast-api-app-function',
loggingFormat: LoggingFormat.JSON,
memorySize: 512, // To avoid timeout
code: DockerImageCode.fromImageAsset(path.join(__dirname, '..', '..'), {
file: path.join('docker', 'Dockerfile-web'),
platform: Platform.LINUX_AMD64, // If you are using Apple Silicon
exclude: ['*', '!src', '!docker'],
}),
});
new LambdaRestApi(this, 'api', {
handler: lambda,
deploy: true,
});
}
}
App デプロイ
最後に、以下のコマンドを実行してください。数分かかるかもしれません。
cd cdk
npx cdk deploy
...
Do you wish to deploy these changes (y/n)? y
App: deploying... [1/1]
App: creating CloudFormation changeset...
✅ App
✨ Deployment time: 52.67s
Outputs:
App.apiEndpoint9349E63C = https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/
Stack ARN:
arn:aws:cloudformation:<AWS_REGION>:<AWS_ACCOUNT_ID>:stack/App/<UUID>
✨ Total time: 55.42s
App テスト
お好みのツールを利用して、 API をテストできます。
curl "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/"
{"Hello":"World"}
curl "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/items/1?q=keyword"
{"item_id":1,"q":"keyword"}
まとめ
AWS Lambda で API を開発する場合、 Lambda Web Adapter が最初の選択肢になるかもしれません。
これが導入される以前は、ローカルでのテストや、多数の API Gateway ルートと Lambda 関数の管理に苦労しました。 現在では、いつも通りバックエンドの API をテストでき、1つの API Gateway ルートと1つの Lambda 関数を管理するだけでよくなりました。
この投稿が、お役に立てば幸いです。