【Python】 FastAPI + Lambda Web Adapter コンテナをマルチステージビルドで作成する
はじめに
こんにちは、がんがんです。
生成AI・MCPは凄まじい速度で進化しており気づいたら知らないものが登場している印象です。Codex、MCP on Windowsが登場してびっくりしています。
個人でも活用できる点を増やしたいなと考え、Lambda MCP Server、AWS Chatbot + Bedrockについて調査を進めています。
前段としてFastAPI + Lambda Web Adapter on Dockerコンテナを作成したところ、思ったよりも時間がかかってしまい結構つまずきました。
そこで備忘録を残しておきます。
まとめ
# Base image
FROM python:3.12.10-slim-bookworm AS base
WORKDIR /app
# Build stage
FROM base AS builder
COPY requirements.txt .
RUN pip install -r requirements.txt
# Note: Docker Buildx利用の場合. GHAを考慮して一旦コメントアウト
# RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \
# --mount=type=bind,source=requirements.txt,target=requirements.txt \
# pip install -r requirements.txt
# Production stage
FROM base AS prod
ENV TZ=Asia/Tokyo
COPY /usr/local/bin/uvicorn /usr/local/bin/uvicorn
COPY /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY /lambda-adapter /opt/extensions/lambda-adapter
COPY . .
ENTRYPOINT ["uvicorn"]
CMD ["main:app", "--host", "0.0.0.0", "--port", "8080"]
FastAPI + Lambda Web Adapterコンテナを作成する
今回は実装差異を最小限にするために Lambda Web Adapter を利用します。Lambda Web Adapterについては以下の記事で事例とメリットを紹介されています。以下で詳しく解説されいますので合わせてご覧ください。
Lambda実装における辛さとして「Lambda固有な実装をしないといけない」点が挙げられると思います。このLambda固有な実装の影響で別インフラへの移植性の低さがしんどいなといつも思っていました。
Lambda Web Adapterを用いることで「プロダクトの実装をEKSに寄せていきたい」「重めの処理を実装したいからECS taskに移行したい」となっても移植が楽になります。
Dockerfile作成
Dockerfileを書いていきます。コンテナサイズは可能な限り小さくしたいのでマルチステージビルドで実装しています。
本記事ではuv、poetryを用いていない構成で構築しています。最終的にはuvを利用して楽します(projects.tomlのチューニングが便利で快適...)
# Base image
FROM python:3.12.10-slim-bookworm AS base
WORKDIR /app
# Build stage
FROM base AS builder
COPY requirements.txt .
RUN pip install -r requirements.txt
# Production stage
FROM base AS prod
ENV TZ=Asia/Tokyo
COPY /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY /lambda-adapter /opt/extensions/lambda-adapter # <-- Lambda Web Adapterの設定はこれだけ
COPY . .
ENTRYPOINT ["uvicorn"]
CMD ["main:app", "--host", "0.0.0.0", "--port", "8080"]
Lambda Web Adapterの設定はCOPYの一文のみです。非常に便利です。
COPY /lambda-adapter /opt/extensions/lambda-adapter
uvicorn
コマンドが動かない
docker run すると... いざビルドして動かしてみます。ビルド自体は正常に終了します。
docker build --no-cache -t exp-fastapi-lambda:0.0.1 .
しかし実行しようとしたらエラーで怒られます。uvicorn
コマンドが動かない?どうしてでしょう🤔
> docker run -it --rm -p 8080:8080 exp-fastapi-lambda:0.0.1
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "uvicorn": executable file not found in $PATH: unknown.
uvicorn
コマンドはpython/site-pacakges
に存在していないっぽい
検索でヒットする記事はマルチステージビルドではないものが大半でした。非常に頭を抱えました。
1~2時間ほど時間を溶かし、以下の記事に出会えました。
私と同じ問題に引っかかっており無事に解決されていました。こちらの記事を参考にCOPY文を追加します。
# Base image
FROM python:3.12.10-slim-bookworm AS base
WORKDIR /app
# Build stage
FROM base AS builder
COPY requirements.txt .
RUN pip install -r requirements.txt
# Production stage
FROM base AS prod
ENV TZ=Asia/Tokyo
+ COPY /usr/local/bin/uvicorn /usr/local/bin/uvicorn
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter # <-- Lambda Web Adapterの設定はこれだけ
COPY . .
ENTRYPOINT ["uvicorn"]
CMD ["main:app", "--host", "0.0.0.0", "--port", "8080"]
これで無事に動きました。無事にデプロイ検証が進められます。
❯ docker run --rm -it -p 8080:8080 exp-fastapi-lambda:0.0.1
INFO: Started server process [1]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)
余談: もう少しチューニングするなら...
動作検証用のDockerfileはこれで問題ありませんがもう少しチューニングしてみます。
Lambda Web Adapter用コンテナの作成差分はCOPY文の追加です。このCOPY文は毎ビルドごとに差異がなくバージョン情報に起因しています。
そのため、ベースコンテナを事前に作成し、ベースコンテナを利用してビルドする方向にすることでビルド時間の削減が期待できます。
# Base image
FROM python:3.12.10-slim-bookworm AS base
WORKDIR /app
ENV TZ=Asia/Tokyo
+ COPY /lambda-adapter /opt/extensions/lambda-adapter
# Build stage
FROM base AS builder
RUN --mount=type=cache,target=/root/.cache/pip,sharing=locked \
--mount=type=bind,source=requirements.txt,target=requirements.txt \
pip install -r requirements.txt
# Production stage
- FROM base AS prod
+ FROM exp-fastapi-base:0.0.1 AS prod
COPY --from=builder /usr/local/bin/uvicorn /usr/local/bin/uvicorn
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY . .
ENTRYPOINT ["uvicorn"]
CMD ["main:app", "--host", "0.0.0.0", "--port", "8080"]
今回はライブラリをほぼ追加してないので0.4sec
の短縮に成功しました。今回はチューニング効果が薄い過度なチューニングです。
> docker build --no-cache -t exp-fastapi-base:0.0.1 --target base .
> docker build --no-cache -t exp-fastapi-lambda:0.0.1 .
しかし、事前に必要なライブラリが多い場合はビルド時間の短縮に期待できます。
ビルド時間の短縮はGitHub Actions、AWS Code Build、Cloud Buildのコスト削減に起因してきます。
おわりに
今回はFastAPI + Lambda Web Adapter on Dockerにおけるマルチステージビルドの実装について調べました。
FastAPI on DockerやFastAPI + Mangum on Docker事例は見かけたものの、マルチステージビルドで実装してるケースはほとんどありませんでした。
Dockerのマルチステージビルドは非常に便利でおすすめです。Docker buildxと合わせて多くの人に利用してみて欲しいです。
Dockerfileのチューニングは以下記事で丁寧にまとめられています。チューニングしてみたくなった人は是非こちらも覗いてみてください。
Discussion