Lambda Web AdapterでFastAPIをAWS Lambdaにデプロイする

Lambda Web AdapterでFastAPIをAWS Lambdaにデプロイする

Lambda Web Adapterを使うと、FastAPIで書いたAPIバックエンドをコンテナのまま単一のLambda関数にデプロイできます。

Takahiro Iwasa
8 min read

API Gatewayと複数のLambda関数を使った従来のAWS設計は、多数のAPIを管理する際に扱いにくくなりがちです。Lambda Web Adapterを使えば、FastAPIで書いたAPIバックエンドをそのままコンテナ化してLambdaにデプロイできます。

Lambda Web Adapterはこのアーキテクチャをシンプルにし、必要なのは単一のAPI Gatewayルートと1つのLambda関数だけになります。

はじめに

AWS CDK環境のブートストラップ

AWS CDK環境がまだブートストラップされていなければ、次のコマンドでローカルにCDKをインストールし、環境をブートストラップします。すでに済んでいる場合はこの手順は不要です。

Terminal window
npm i -D aws-cdk
npx cdk bootstrap aws://<AWS_ACCOUNT_ID>/<AWS_REGION>

CDKプロジェクトの初期化

CDKプロジェクト用のディレクトリを作成し、初期化します。

Terminal window
mkdir cdk && cd cdk
npx cdk init app --language typescript

FastAPIのインストール

Terminal window
python -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]"
mkdir src
pip freeze > ./src/requirements.txt

バックエンドの構築

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}

FastAPIサーバーを起動します。

Terminal window
fastapi dev ./src/main.py

お好みのツールでAPIをテストします。

Terminal window
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リポジトリを参照してください。

docker/Dockerfile
# Base image: Python 3.12 Alpine
FROM public.ecr.aws/docker/library/python:3.12-alpine AS base
ENV APP_ROOT=/code
# Copy requirements and install dependencies
COPY ./src/requirements.txt $APP_ROOT/
RUN pip install --no-cache-dir --upgrade -r $APP_ROOT/requirements.txt
# Development stage
FROM base AS dev
ENV ENV=dev
EXPOSE 8000
CMD ["sh", "-c", "fastapi run $APP_ROOT/main.py --port 8000"]
# Production stage
FROM base
ENV ENV=prod
EXPOSE 8080
COPY ./src $APP_ROOT
# Copy Lambda Web Adapter
COPY --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 Adapter
CMD ["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行目)。
docker/compose.yaml
services:
api:
build:
context: ../
dockerfile: ./docker/Dockerfile-web
target: dev
ports:
- "8000:8000"
volumes:
- ../src:/code

Docker Composeを使ってバックエンドサービスをローカルで起動するには、次のコマンドを実行します。

Terminal window
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)

これでhttp://127.0.0.1:8000でAPIにアクセスでき、curlやPostman、ブラウザなど普段使っているツールでテストできます。

AWSへのデプロイ

AWSリソースの定義

AWS CDKを使って、FastAPIバックエンドのデプロイに必要なリソースを定義します。

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');
  • メモリサイズ: memorySizeを512MB以上に設定します(20行目)。これにより、メモリ不足によるタイムアウトの可能性を防ぎます。
  • プラットフォーム設定: Apple Siliconを使用している場合はPlatform.LINUX_AMD64を使用します(23行目)。これがないと、Error: fork/exec /opt/extensions/lambda-adapter: exec format error Extension.LaunchErrorというエラーが発生する可能性があります。
cdk/lib/cdk-stack.ts
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プロジェクトのディレクトリに移動し、次のコマンドを実行してデプロイします。

Terminal window
cd cdk
npx cdk deploy

デプロイ中、リソース作成の確認を求められる場合があります。yと答えて続行します。

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

デプロイが完了するとAPI Gatewayのエンドポイントが出力され、これを使ってAPIをテストできます。

APIのテスト

デプロイされたAPIをテストします。

Terminal window
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レイヤーがコンパイル済みバイナリであり、どのホストアーキテクチャでも同じように動くインタプリタ型のコードではないためです。

About the author

Takahiro Iwasa

Takahiro Iwasa

Software Developer

This blog shares technical notes from hands-on projects—architecture, implementation, and AWS service integrations.