API をローカル環境と AWS Lambda 環境で同一コードベースで運用する方法(FastAPI)
はじめに
FastAPI で開発したバックエンドアプリケーションを、ローカルでは Docker Compose を使い、本番環境では AWS Lambda にデプロイしますが、環境ごとにコードを変更せずに運用する方法を紹介します。
環境情報
- Python: 3.10
- FastAPI: 最新版
- Docker: 最新版
- AWS Lambda: Python 3.10 ランタイム
- AWS API Gateway: REST API
- OS: macOS 24.3.0
アーキテクチャ概要
ローカル環境
- Docker Compose を使用して、フロントエンド、バックエンド、テスト環境を構築
- FastAPI アプリケーションを Uvicorn で直接実行
- ホットリロード機能を活用した迅速な開発
本番環境(AWS Lambda)
- AWS Lambda を使用してサーバーレスアーキテクチャを実現
- API Gateway を通じて HTTP リクエストを処理
- Mangum アダプターを使用して FastAPI アプリケーションを Lambda ハンドラーとして実行
同一コードベースでの環境切り替えの仕組み
1. マルチステージ Docker ビルド
バックエンドの Dockerfile では、マルチステージビルドを使用して、ローカル環境と本番環境の両方に対応しています。
# ローカル開発環境用ステージ
FROM python:3.10 as local
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
ENV ENVIRONMENT=local
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
# 本番環境(AWS Lambda)用ステージ
FROM public.ecr.aws/lambda/python:3.10 as production
# 依存関係のインストール
COPY requirements.txt ${LAMBDA_TASK_ROOT}/
RUN pip install -r requirements.txt --target "${LAMBDA_TASK_ROOT}"
# アプリケーションコードのコピー
COPY app ${LAMBDA_TASK_ROOT}/app
COPY app/main.py ${LAMBDA_TASK_ROOT}/
# 環境変数の設定
ENV ENVIRONMENT=production \
AWS_LAMBDA_FUNCTION_NAME=sample-api
CMD ["app.main.handler"]
# デフォルトは本番環境用のステージを使用
FROM production
最後の FROM production
命令は、明示的に環境を指定しない場合のデフォルトステージを設定しています。この行により、単に docker build
を実行した場合は本番環境用のイメージが生成されます。一方、docker build --target local
のように特定のターゲットを指定すると、そのステージのイメージが生成されます。
ローカル環境では、Docker Compose で明示的に target: local
を指定することで、ローカル開発用のステージを使用します。
api:
build:
context: ./api
target: local
# 他の設定...
2. 環境検出と Mangum アダプター
FastAPI アプリケーションのエントリーポイント(main.py)では、環境変数を使用して実行環境を検出し、AWS Lambda 環境の場合のみ Mangum ハンドラーを作成します。
import os
from fastapi import FastAPI
from mangum import Mangum
# 環境変数の取得
ENVIRONMENT = os.getenv("ENVIRONMENT", "local")
app = FastAPI(title="Sample API")
# エンドポイントの定義...
# AWS Lambda環境の場合のみMangumハンドラーを作成
if os.getenv("AWS_LAMBDA_FUNCTION_NAME"):
handler = Mangum(app, lifespan="off", api_gateway_base_path=None)
ここで重要なのは、AWS_LAMBDA_FUNCTION_NAME
環境変数はユーザーが明示的に設定する必要がないという点です。この環境変数は AWS Lambda ランタイムによって自動的に設定されるため、Lambda 環境では常に利用可能です。そのため、この変数の存在を確認するだけで、コードが Lambda 環境で実行されているかどうかを簡単に判断できます。
これにより、同じコードベースでありながら、ローカル環境では Uvicorn による直接実行、AWS Lambda 環境では Mangum ハンドラーを通じた実行が可能になります。
環境変数の管理
ローカル環境(Docker Compose)
ローカル環境では、Docker Compose のenvironment
セクションで環境変数を設定します。
api:
# 他の設定...
environment:
- PYTHONPATH=/app
- ENVIRONMENT=local
- API_KEY=sample_api_key_123
- DEBUG=True
- LOG_LEVEL=DEBUG
本番環境(AWS Lambda)
AWS Lambda 環境では、以下の環境変数を設定する必要があります:
-
必須環境変数
-
ENVIRONMENT
:production
に設定 -
API_KEY
: API キー(セキュアな値を設定)
-
-
オプション環境変数
-
DEBUG
: デバッグモード(本番ではFalse
に設定) -
LOG_LEVEL
: ログレベル(本番ではINFO
またはERROR
に設定)
-
AWS Lambda コンソールまたはインフラストラクチャコード(Terraform、CloudFormation など)を通じて、これらの環境変数を設定します。
環境に応じた動的な設定
コード内では、環境変数に基づいて動的に設定を変更することができます。例えば、CORS 設定は環境によって異なる値を使用します:
# CORS設定
ALLOWED_ORIGINS = (
["https://sample-app.example.com"] if ENVIRONMENT == "production" else ["http://localhost:3000"]
)
デプロイフロー
ローカル環境での開発
- Docker Compose を使用して環境を起動:
docker compose up
- ローカル環境でのテスト:
# APIエンドポイントのテスト
curl http://localhost:8000/health
# サンプル機能のテスト
curl -X POST http://localhost:8000/process -H "Content-Type: application/json" -d '{"data": "test"}'
AWS Lambda 環境へのデプロイ
- 本番用の Docker イメージをビルド:
docker build -t sample-api:production ./api
-
コンテナイメージを AWS ECR にプッシュ(または直接 Lambda 関数としてデプロイ)
-
AWS Lambda の環境変数を設定:
- AWS Lambda コンソールで環境変数を設定
- または、インフラストラクチャコードを通じて設定
-
デプロイ後のテスト:
- API Gateway エンドポイントを使用して API をテスト
まとめ
この記事では、FastAPI アプリケーションをローカル環境と AWS Lambda 環境の両方で同じコードベースを使用して運用する方法について説明しました。主なポイントは以下の通りです:
- マルチステージ Docker ビルドを使用して環境ごとの違いを吸収
- 環境変数を使用して実行環境を検出
- Mangum アダプターを使用して FastAPI アプリケーションを Lambda ハンドラーとして実行
- 環境変数を通じて各環境に適した設定を動的に適用
この方法を使用することで、開発からテスト、本番環境までのシームレスなデプロイが可能になり、環境間の一貫性を保ちながら効率的な開発を行うことができます。
実装例
上記の解決策を実装した実際のサンプルコードは以下のリポジトリで公開しています:
Discussion