Lambda Web AdapterでFastAPIをAWS Lambdaにデプロイする
Lambda Web Adapterを使うと、FastAPIで書いたAPIバックエンドをコンテナのまま単一のLambda関数にデプロイできます。
API Gatewayと複数のLambda関数を使った従来のAWS設計は、多数のAPIを管理する際に扱いにくくなりがちです。Lambda Web Adapterを使えば、FastAPIで書いたAPIバックエンドをそのままコンテナ化してLambdaにデプロイできます。
Lambda Web Adapterはこのアーキテクチャをシンプルにし、必要なのは単一のAPI Gatewayルートと1つのLambda関数だけになります。
はじめに
AWS CDK環境のブートストラップ
AWS CDK環境がまだブートストラップされていなければ、次のコマンドでローカルにCDKをインストールし、環境をブートストラップします。すでに済んでいる場合はこの手順は不要です。
npm i -D aws-cdknpx cdk bootstrap aws://<AWS_ACCOUNT_ID>/<AWS_REGION>CDKプロジェクトの初期化
CDKプロジェクト用のディレクトリを作成し、初期化します。
mkdir cdk && cd cdknpx cdk init app --language typescriptFastAPIのインストール
python -m venv .venvsource .venv/bin/activatepip install "fastapi[standard]"mkdir srcpip freeze > ./src/requirements.txtバックエンドの構築
APIの定義
FastAPIアプリケーションの例は次の通りです。
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}FastAPIサーバーを起動します。
fastapi dev ./src/main.pyお好みのツールで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"}コンテナ化
FastAPIバックエンドをコンテナ化するため、./docker/Dockerfileを作成します。このDockerfileは、開発環境と本番環境の両方を効果的に扱えるように設計されています。
- ベースイメージ: 軽量かつセキュアなベースイメージとして
public.ecr.aws/docker/library/python:3.12-alpineを使用します(2行目)。 - Lambda Web Adapter: AWS Lambdaとのシームレスな統合のためLambda Web Adapterを追加します(22行目)。
- ポート設定: Lambda Web Adapterの想定設定に合わせ、本番環境ではデフォルトでバックエンドがポート8080でリッスンします(25行目)。
Lambda Web Adapterの使い方の詳細については、GitHubリポジトリを参照してください。
# Base image: Python 3.12 AlpineFROM public.ecr.aws/docker/library/python:3.12-alpine AS baseENV APP_ROOT=/code
# Copy requirements and install dependenciesCOPY ./src/requirements.txt $APP_ROOT/RUN pip install --no-cache-dir --upgrade -r $APP_ROOT/requirements.txt
# Development stageFROM base AS devENV ENV=devEXPOSE 8000CMD ["sh", "-c", "fastapi run $APP_ROOT/main.py --port 8000"]
# Production stageFROM baseENV ENV=prodEXPOSE 8080COPY ./src $APP_ROOT
# Copy Lambda Web AdapterCOPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.8.4 /lambda-adapter /opt/extensions/lambda-adapter
# Run FastAPI backend on port 8080 for Lambda Web AdapterCMD ["sh", "-c", "fastapi run $APP_ROOT/main.py --port 8080"](オプション)Docker Compose
この記事ではデータベースの利用は扱いませんが、実際のシナリオではDynamoDBやMySQLなどのデータベースを扱うことがよくあります。ローカルでの開発・テストを容易にするため、Docker Composeをセットアップできます。
- ビルドコンテキスト: プロジェクトルート(
../)を指します(4行目)。 - ビルドターゲット: Dockerfileの開発ステージ(
target: dev)を使用します(6行目)。 - ポートマッピング: コンテナのポート
8000をホストのポート8000にマッピングし、ローカルからアクセスできるようにします(8行目)。 - ボリューム: ローカルの
srcディレクトリをコンテナの/codeディレクトリにマウントし、開発中のコードのホットリロードを可能にします(10行目)。
services: api: build: context: ../ dockerfile: ./docker/Dockerfile-web target: dev ports: - "8000:8000" volumes: - ../src:/codeDocker Composeを使ってバックエンドサービスをローカルで起動するには、次のコマンドを実行します。
cd dockerdocker 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)これでhttp://127.0.0.1:8000でAPIにアクセスでき、curlやPostman、ブラウザなど普段使っているツールでテストできます。
AWSへのデプロイ
AWSリソースの定義
AWS CDKを使って、FastAPIバックエンドのデプロイに必要なリソースを定義します。
#!/usr/bin/env nodeimport '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');- メモリサイズ:
memorySizeを512MB以上に設定します(20行目)。これにより、メモリ不足によるタイムアウトの可能性を防ぎます。 - プラットフォーム設定: Apple Siliconを使用している場合は
Platform.LINUX_AMD64を使用します(23行目)。これがないと、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);
// Dockerized Lambda Function const lambda = new DockerImageFunction(this, 'function', { functionName: 'fast-api-app-function', loggingFormat: LoggingFormat.JSON, memorySize: 512, // Larger memory to avoid timeout code: DockerImageCode.fromImageAsset(path.join(__dirname, '..', '..'), { file: path.join('docker', 'Dockerfile-web'), platform: Platform.LINUX_AMD64, // Required for Apple Silicon users exclude: ['*', '!src', '!docker'], }), });
// API Gateway REST API new LambdaRestApi(this, 'api', { handler: lambda, deploy: true, }); }}スタックのデプロイ
CDKプロジェクトのディレクトリに移動し、次のコマンドを実行してデプロイします。
cd cdknpx cdk deployデプロイ中、リソース作成の確認を求められる場合があります。yと答えて続行します。
Do you wish to deploy these changes (y/n)? yApp: 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デプロイが完了するとAPI Gatewayのエンドポイントが出力され、これを使ってAPIをテストできます。
APIのテスト
デプロイされたAPIをテストします。
curl "https://<API_GATEWAY_ENDPOINT>/prod/"{"Hello":"World"}
curl "https://<API_GATEWAY_ENDPOINT>/prod/items/1?q=keyword"{"item_id":1,"q":"keyword"}まとめ
Lambda Web AdapterでFastAPIアプリをコンテナ化し、CDK経由でデプロイしたことで、API全体が単一のLambda関数と1つのAPI Gatewayルートの背後に収まりました。Lambda Web Adapterの本当の意義は、本来であれば個別のLambda関数とAPI Gatewayのルートマッピングが積み重なっていくところを、FastAPI自身のルーティングに置き換えられる点にあります。アプリケーション全体が単一のプロキシルートの背後にある1つのコンテナとして出荷されるため、エンドポイントの追加はインフラの変更ではなくコードの変更で済みます。CDKスタックで取り上げた2つの設定は見落としがちですが、実際には省略できません。memorySize: 512は、より小さいメモリ割り当てでは発生してしまうコールドスタートのタイムアウトを回避します。そしてPlatform.LINUX_AMD64がApple Siliconで必要なのは、ここで使われているLambda Web Adapterレイヤーがコンパイル済みバイナリであり、どのホストアーキテクチャでも同じように動くインタプリタ型のコードではないためです。
Related posts
Cognito User PoolsとOIDCでSlackサインインを実装する
Cognito user poolをOIDC経由でSlackと連携させ、"Sign in with Slack"をAmplifyでNext.jsアプリに組み込みます。

AWS Amplify と Eclipse Mosquitto を連携させた MQTT メッセージング
AWS Amplify の PubSub モジュールを AWS IoT Core に接続する前に、ローカルの Eclipse Mosquitto ブローカーでテストする方法。
API Gateway WebSocket:モック統合の実装
バックエンドのLambdaを一切使わず、モック統合のみでAPI Gateway WebSocket APIを構築し、あらかじめ用意されたレスポンスを返します。
CloudFront署名付きURL経由でS3にアップロードする
CloudFrontの署名付きURLを使えば、独自ドメイン経由でS3にアップロードできます。S3の直接の署名付きURLが使えない場合に有用です。
AWS EventBridge Scheduler:スケジュールに沿ってEC2を起動・停止する
Lambdaを介さずEventBridge SchedulerがEC2 APIを直接呼び出すことで、cronスケジュールに従ってEC2インスタンスを起動・停止します。
