AWS Lambda と Lambda Web Adapter を活用した FastAPI 開発

AWS Lambda と Lambda Web Adapter を活用した FastAPI 開発

岩佐 孝浩
岩佐 孝浩
11 min read
Lambda Lambda Web Adapter

はじめに

Lambda Web Adapter は、AWS Lambda 上でのアプリケーション開発を一変させるツールです。多くの開発者が馴染みのある Docker を活用してバックエンドサービスを構築できるようになります。

この記事では、FastAPI を用いて Lambda Web Adapter を利用した API バックエンドの開発手順を解説します。サンプルコードは私の GitHub リポジトリ からクローンできます。

概要

Lambda Web Adapter のメリット

Lambda Web Adapter は従来の AWS Lambda に比べて以下のような利点を提供します。

比較項目Lambda Web Adapter従来の Lambda
ローカルテストの容易さ簡単難しい
他サービス (例: Fargate) への移行の容易さ簡単難しい
AWS リソースの数少ない1多い
コスト低い非常に低い

1 Lambda Web Adapter を使用する場合、必要なのは 1 つの Lambda 関数と 1 つの API Gateway ルートのみです。

AWS の設計

従来の設計

API Gateway と複数の Lambda 関数を利用した従来の AWS 設計では、多くの API を管理する際に煩雑になります。

Lambda Web Adapter を利用した設計

Lambda Web Adapter を利用することで、シンプルなアーキテクチャを実現できます。必要なのは 1 つの API Gateway ルートと 1 つの 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.json
|   |-- package-lock.json
|   |-- README.md
|   `-- tsconfig.json
|-- docker/
|   |-- Dockerfile-web
|   `-- compose.yaml
|-- node_modules/
|-- src/
|   |-- main.py
|   `-- requirements.txt
|-- .gitignore
|-- package.json
`-- package-lock.json

はじめに

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

まず、AWS CDK 環境をブートストラップします。

AWS CDK ガイド に従って、ローカルに 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 を用いたバックエンド API の構築

API の作成

以下は ./src/main.py に保存する 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}

API のテスト

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"}

Docker を利用したコンテナ化

Dockerfile の作成

FastAPI バックエンドをコンテナ化するために、以下のコードを作成し ./docker/Dockerfile-web に保存してください。この Dockerfile は開発環境と本番環境の両方に対応できるように設計されています。

主なポイント:

  • ベースイメージ: 軽量かつ安全なベースイメージとして public.ecr.aws/docker/library/python:3.12-alpine を使用します (2 行目)。
  • Lambda Web Adapter: AWS Lambda とのシームレスな統合を実現するために Lambda Web Adapter を追加します (22 行目)。
  • ポート設定: 本番環境ではデフォルトで ポート 8080 をリッスンし、Lambda Web Adapter の設定と一致させます (25 行目)。

Lambda Web Adapter の使用方法については、公式ドキュメント をご参照ください。

# 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 を設定します。

Docker Compose を利用したローカル開発

この記事ではデータベースの使用を含んでいませんが、実際のプロジェクトでは DynamoDBMySQL などのデータベースを扱うことが一般的です。ローカル開発とテストを効率化するために Docker Compose を設定します。この設定により、API バックエンドを分離された環境でスムーズに動作させることができます。

./docker/compose.yaml ファイルを作成し、以下の内容を記述してください。

services:
  api:
    build:
      context: ../
      dockerfile: ./docker/Dockerfile-web
      target: dev
    ports:
      - "8000:8000"
    volumes:
      - ../src:/code

主な設定ポイント

  • ビルドコンテキスト: プロジェクトのルート (../) を指定し、Dockerfile の開発環境用ステージ (target: dev) を使用します。
  • ポートのマッピング: コンテナのポート 8000 をホストのポート 8000 にマッピングし、ローカルでアクセスできるようにします。
  • ボリューム: ローカルの src ディレクトリをコンテナの /code ディレクトリにマウントし、開発中にコードのホットリロードを有効にします。

バックエンドサービスの起動

以下のコマンドで Docker Compose を利用してローカル環境でバックエンドサービスを起動します。

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)

API は http://127.0.0.1:8000 でアクセス可能になります。curl や Postman、またはブラウザを使用して通常通りテストできます。

次は AWS CDK を使用して、FastAPI アプリケーションを AWS Lambda にデプロイする方法について説明します。

AWS へのデプロイ

AWS CDK を使用したリソースの定義

FastAPI バックエンドを AWS にデプロイするために、AWS CDK を使用して必要なリソースを定義します。CDK を使用することで、インフラをコードで定義し、リソース作成が簡素化されます。

ステップ 1: エントリーポイントの設定

以下のコードを cdk/bin/cdk.ts に貼り付けてください。このファイルは AWS CDK アプリケーションのエントリーポイントです。

#!/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');

ステップ 2: インフラスタックの定義

cdk/lib/cdk-stack.ts に AWS Lambda 関数と API Gateway を定義します。以下のコードは、Docker 化された Lambda 関数を作成し、それを API Gateway REST API に統合します。

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, // タイムアウト回避のためにメモリを増加
      code: DockerImageCode.fromImageAsset(path.join(__dirname, '..', '..'), {
        file: path.join('docker', 'Dockerfile-web'),
        platform: Platform.LINUX_AMD64, // Apple Silicon ユーザーは必須
        exclude: ['*', '!src', '!docker'],
      }),
    });

    // API Gateway REST API
    new LambdaRestApi(this, 'api', {
      handler: lambda,
      deploy: true,
    });
  }
}
主なポイント
  • メモリサイズ: memorySize を 512 MB またはそれ以上に設定します (20 行目)。これにより、メモリ不足によるタイムアウトを防ぎます。
  • プラットフォーム設定: Apple Silicon を使用している場合は Platform.LINUX_AMD64 を指定します (23 行目)。これを指定しないと、Error: fork/exec /opt/extensions/lambda-adapter: exec format error Extension.LaunchError のエラーが発生する可能性があります。

これらのファイルが揃ったら、リソースのデプロイ準備が整います。

ステップ 3: リソースのデプロイ

CDK プロジェクトディレクトリに移動し、以下のコマンドを実行してデプロイを開始します。

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 をテストします。

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 は、AWS Lambda 上での API 開発を大幅に簡素化し、複雑さを削減しつつローカルテストを容易にします。AWS アーキテクチャの効率化を実現するこのツールは、モダンな Web 開発において欠かせない存在です。

Happy Coding! 🚀

岩佐 孝浩

岩佐 孝浩

Software Developer at KAKEHASHI Inc.
AWS を活用したクラウドネイティブ・アプリケーションの要件定義・設計・開発に従事。 株式会社カケハシで、処方箋データ収集の新たな基盤の構築に携わっています。 Japan AWS Top Engineers 2020-2023