はじめに
本記事はサーバーワークス Advent Calendar 2024の9日目の記事です。
uv をコンテナ開発で利用したところ、Dockerfileの記述量が少なくコンテナイメージのビルドが高速でしたので、CDKでコンテナイメージを使用した Lambda 関数を作成する小さなサンプルプロジェクトを書いてみました。
特にこの記事では、CDKの公式ドキュメントである AWS Cloud Development Kit (AWS CDK) v2 開発者ガイド
の cdk init sample-app... コマンドで立ち上げた状態のプロジェクトから、極力小さい変更手数で構築したサンプルプロジェクトの形式で紹介します。以下5つの要素を含むプロジェクトです。
- AWS CDK を用いた IaC ができる
- コンテナイメージを使用した Lambda 関数
- Amazon ECR Public Gallery の Python Lambda イメージ を使っている
- uv を用いた python ランタイムやパッケージの管理ができる
- ruff を用いた linting, formatting ができる
構成図
とてもシンプルですね。
結論
以下がコードです。
Quick Start
$ git clone git@github.com:hiromitsu-sai/cdk-uv-python-example-forblog.git $ cd cdk-uv-python-example-forblog $ uv sync $ uv run npx cdk deploy
デプロイされると Cloudformation スタックができていることが確認できます。
SQSにメッセージを与えてみます。
Lambdaのログを確認する
以上になります。以降は結論にどのように至ったのかを補足していきます。 過程はコードを見れば分かるよーという方はスキップして頂いて構いません。
構築手順
uv, docker engine, npm をインストール済の状態とします。OS は Ubuntu 24 LTS とします。
AWS CLI をインストールしプロファイルを設定しておく
$ aws configure AWS Access Key ID [None]: AKIAEXAMPLE AWS Secret Access Key [None]: EXAMPLEKEY Default region name [ap-northeast-1]: Default output format [None]: json
$ uv python install 3.11
たたき台とするCDKサンプルプロジェクトを新規作成する
$ mkdir cdk-uv-python-example-forblog $ cd cdk-uv-python-example-forblog $ npx cdk init sample-app --language=python cdk_uv_python_example
venv で仮想環境を作成する
$ uv venv --python 3.11 Using Python 3.11.9 Creating virtualenv at: .venv Activate with: source .venv/bin/activate $ uv run python -V Python 3.11.9 $
$ uv init Initialized project `cdk-uv-python-example-forblog` $
src/
, tests/
ディレクトリにコードを配置する
$ mkdir -p src $ rm hello.py $ curl -o src/hello.py https://raw.githubusercontent.com/hiromitsu-sai/cdk-uv-python-example-forblog/refs/heads/main/src/hello.py $ curl -o tests/test_hello.py https://raw.githubusercontent.com/hiromitsu-sai/cdk-uv-python-example-forblog/refs/heads/main/tests/test_hello.py
Dockerfileを配置する
$ curl -o Dockerfile https://raw.githubusercontent.com/hiromitsu-sai/cdk-uv-python-example-forblog/refs/heads/main/Dockerfile $ curl -o .dockerignore https://raw.githubusercontent.com/hiromitsu-sai/cdk-uv-python-example-forblog/refs/heads/main/.dockerignore
uv で python パッケージを管理する
## requirements.txt からパッケージを追加 $ uv add -r requirements.txt $ uv add -r requirements.txt --dev $ rm requirements.txt requirements-dev.txt source.bat $ uv add ruff --dev
ユニットテストとコードスタイルチェックをかける
$ uv run ruff check $ uv run python -m pytest tests -v
デプロイする
$ uv run npx cdk bootstrap # 初回だけ実行すればよい $ uv run npx cdk deploy
メリット
コンテナイメージを使用した Lambda 関数を採用するメリット
Lambda関数のデプロイパッケージの方式には、大別すると次の2通りがあります。
- Lambda 関数の .zip ファイルアーカイブ としてデプロイする方法
- コンテナイメージ としてデプロイする方法
コンテナイメージとしてデプロイする場合のメリットは、イメージデプロイが可能になった時の builders.flash 記事 が分かりやすいです。特に、たくさんデプロイできしっかりした版管理がされるECR (コンテナリポジトリ) に依存している点が良いです。
項目 | Zip | コンテナ |
---|---|---|
ストレージ場所 | S3 | ECR |
ストレージサイズ上限 (リージョン単位) | 75GB (上限緩和可能) | ECRのクォータに準拠。10万リポジトリまで |
アーティファクトサイズ上限 | 250 MB (展開後) | 10 GB |
Layer 対応 | あり | なし |
なお、この表は2024-12-7 時点の情報です。アップデートされる可能性がありますので最新情報は 公式ドキュメント をご覧ください。
Amazon ECR Public Gallery の Python Lambda イメージを使うメリット
- AWS Lambdaとしてで稼働するための構成がされており動作保証範囲が広い
- Amazon Linux Base オペレーティングシステムで構成される
- AWS 自体がLambdaメンテナンスポリシーをもって保守しており、定期的にセキュリティパッチや更新、その他最適化が適用される
- Lambda コンテナイメージとしてデプロイ・実行するために必要なコンポーネントや構成 (RICなど) が適用済
AWS CDK を用いた IaC をするメリット
AWS CDK を使うメリットは IaC ができることですが、特に効率よく安全に行いやすい点がメリットです。
uv を用いた python ランタイムやパッケージの管理をするメリット
uvとは、Python ランタイムのバージョン管理と、Pythonパッケージの管理を効率的に行えるツールです。この2種類の管理ツールの世界には先行の著名ツール(pyenv, anyenv, pip, pipenv, poetry, etc...)が複数あり、長短様々で必ずしも優劣はつけがたいと思います。
今回特に uv に見出すメリットとしては、コンテナイメージとして実行されるPythonプログラムを開発する場合に、環境の構築速度が速く構築ステップの少なさがあります。
コンテナ開発における辛みは、イメージのビルド時間が長く、Python実行環境としての構築オペレーション (Dockerfile) の内容が長大になりがちな点なのですが、uvを使うとpython環境構築の部分は他のツールとは比べ物にならないほど縮小されます。
試しにコンテナイメージをビルドして所要時間を確認してみました。
FROM ghcr.io/astral-sh/uv:latest...
に着目していただくと1.9秒でとても早いです。
なお、実行環境は ap-northeast-1 リージョンに配置した t2.xlarge
タイプのオンデマンドEC2インスタンスです。
$ DOCKER_BUILDKIT=1 docker build . --no-cache [+] Building 7.7s (12/12) FINISHED docker:default => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 2.12kB 0.0s => [internal] load metadata for ghcr.io/astral-sh/uv:latest 1.2s => [internal] load metadata for public.ecr.aws/lambda/python:3.11 0.9s => [internal] load .dockerignore 0.0s => => transferring context: 324B 0.0s => FROM ghcr.io/astral-sh/uv:latest@sha256:23272999edd22e78195509ea3fe380e7632ab39a4c69a340bedaba7555abe20a 1.9s => => resolve ghcr.io/astral-sh/uv:latest@sha256:23272999edd22e78195509ea3fe380e7632ab39a4c69a340bedaba7555abe20a 0.0s => => sha256:dca2e425d1cd38e6c4f4dbe1125694d7e3f52efedc7d8fc516a823a2fde512e9 669B / 669B 0.0s => => sha256:b8ee93cf5ac57f844743147b3376659348dced3855a97492350f0d2cd16f51af 1.30kB / 1.30kB 0.0s => => sha256:334074b91934d845cad75878f0896e6e7ecfb59b10f678493db5badc64a02d23 14.66MB / 14.66MB 1.5s => => sha256:5cd11d181efec426e34eb3c524911557fc9b9d36111c1750075f36670a6aa971 94B / 94B 0.4s => => sha256:23272999edd22e78195509ea3fe380e7632ab39a4c69a340bedaba7555abe20a 2.19kB / 2.19kB 0.0s => => extracting sha256:334074b91934d845cad75878f0896e6e7ecfb59b10f678493db5badc64a02d23 0.3s => => extracting sha256:5cd11d181efec426e34eb3c524911557fc9b9d36111c1750075f36670a6aa971 0.0s => [internal] load build context 0.0s => => transferring context: 21.59kB 0.0s => CACHED [stage-0 1/5] FROM public.ecr.aws/lambda/python:3.11@sha256:885e7899bbfb56dbc34401eb07b5bee738f0909032d5342479acf229f196a4a2 0.0s => [stage-0 2/5] COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv 0.1s => [stage-0 3/5] COPY pyproject.toml uv.lock /var/task/ 0.0s => [stage-0 4/5] RUN uv sync --frozen 2.6s => [stage-0 5/5] COPY src/ /var/task/ 0.0s => exporting to image 1.5s => => exporting layers 1.5s => => writing image sha256:2ee5f4b6ca4ac0bf7c9da074a925b8e5b2c626c00c49f5963d6dce7210512fdb 0.0s 2 warnings found (use docker --debug to expand): - UndefinedVar: Usage of undefined variable '$LOGLEVEL' (line 9) - UndefinedVar: Usage of undefined variable '$IS_DEBUG' (line 10) $
ruff を用いた linting, formatting ができるメリット
ruff は flake8 や black のような Linter, Formatter で、動作が高速です。
さらに、uv も ruff もAstral 社によって開発されたツールで、両者には親和性があります。例えば、今回書いたサンプルプロジェクトでは ruff パッケージをわざわざインストールしていますが、パッケージとしてインストールせずにuv toolとして利用することができます。
$ uv remove ruff --dev Resolved 21 packages in 125ms Uninstalled 1 package in 0.72ms - ruff==0.8.2 $ uvx ruff check Installed 1 package in 1ms All checks passed! $