Lambda Web Adapter を利用して FastAPI を AWS Lambda で実行する方法

Lambda Web Adapter を利用して FastAPI を AWS Lambda で実行する方法

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

Lambda Web Adapter は、 AWS Lambda を利用したアプリケーション開発を劇的に加速させました。 大抵の開発者が慣れている Docker を利用して、 AWS Lambda でバックエンドのサービスを開発できます。

この記事では、 Lambda Web Adapter を利用して FastAPI で API バックエンドを開発する方法を紹介します。 私の GitHub リポジトリから、サンプルをクローンできます。

概要

利点

Lambda Web Adapter の利点は、以下が挙げられます。

比較項目Lambda Web AdapterTraditional Lambda
ローカルでのテストEasyHard
Fargate のような他サービスへのマイグレーションEasyHard
AWS リソース数Less 1More
コストLow (because of overhead)Very low
  1. 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 関数を管理するだけでよくなりました。

この投稿が、お役に立てば幸いです。

岩佐 孝浩

岩佐 孝浩

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