AWS CDK Python × uv × Docker で実現する複数の Lambda Function の Monorepo 管理
はじめに
色々なツールを AWS Lambda で作り、各ツールのコードを管理するリポジトリの数がどんどん増えてきた時に思いました。「そうだ Monorepo しよう。」
そんな中、Python のパッケージ管理ツールで「uv がアツい」という噂を聞いた時に思いました。「そうだ uv 使おう。」
AWS CDK も Python を使えば、インフラからアプリケーションコードまで全て Python で統一できると気づいた時に思いました。「そうだ インフラからアプリコードまで全部 Monorepo にして uv で管理しよう。」
このブログでは、実際にやってみて実現できた方法をシェアします。
※ GitHub リポジトリはこちら。
ソフトウェアバージョン
- Python: 3.11.8
- Docker: 27.3.1
- uv: 0.6.14
- AWS CLI: 2.13.32
- AWS CDK CLI: 2.1010.0
- Node.js: v22.13.1
- npm: 10.9.2
プロジェクト構成
今回実現した Monorepo 構成のベースは以下の通りです。
lambda-monorepo/
├── functions/
│ ├── function_a/
│ │ ├── Dockerfile
│ │ └── lambda_function.py
│ └── function_b/
│ ├── Dockerfile
│ └── lambda_function.py
├── infra/
│ └── lambda_monorepo_stack.py
├── tests/
├── app.py
├── cdk.json
├── pyproject.toml
└── uv.lock
functions/: 各 Lambda Function のソースコード、Dockerfile を格納。
infra/: インフラを定義する CDK のコードを格納。
tests/: 各種テストコードを格納。
app.py: CDK プロジェクトのエントリーポイントとなるファイル。
cdk.json: CDK プロジェクト全体の設定ファイル。
pyproject.toml: プロジェクト全体の依存関係を管理するための設定ファイル。
uv.lock: pyproject.toml に基づいて解決された依存関係のロックファイル。
ベース構成構築手順
- プロジェクトディレクトリの作成
mkdir lambda-monorepo
- CDK プロジェクトの初期化
cd lambda-monorepo && cdk init app --language python
- uv の初期化
uv init
- 仮想環境の起動
source .venv/bin/activate
- 既存の依存関係の移行
uv add --group infra -r requirements.txt && \ uv add --dev -r requirements-dev.txt
- ディレクトリの整理
mv lambda_monorepo infra && \ mkdir functions && \ rm main.py requirements.txt requirements-dev.txt
Lambda functions の作成
今回はサンプルとして 2 つの Lambda Function を作成します。
- S3 Bucket 一覧を取得する Function [依存関係: Boto3]
import json
import boto3
client = boto3.client('s3')
def lambda_handler(event, context):
'''S3 Bucket の一覧を取得する'''
try:
response = client.list_buckets()
return {
'statusCode': 200,
'body': json.dumps(
{
'message': 'Successfully retrieved buckets.',
'buckets': [bucket['Name'] for bucket in response['Buckets']],
}
)
}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps(
{
'message': 'Unexpected error occurred',
'error': str(e),
}
)
}
- hello world! を返却する Function [依存関係: FastAPI, Mangum]
from fastapi import FastAPI
from mangum import Mangum
app = FastAPI()
@app.get('/')
async def root():
'''hello world! を返却する'''
return 'hello world!'
lambda_handler = Mangum(app)
AWS CDK Python によるインフラ定義
続いて Lambda Function のリソースを作成します。
#!/usr/bin/env python3
import os
import aws_cdk as cdk
from infra.lambda_monorepo_stack import LambdaMonorepoStack
app = cdk.App()
LambdaMonorepoStack(
app,
'LambdaMonorepo',
env=cdk.Environment(account=os.getenv('CDK_DEFAULT_ACCOUNT'), region=os.getenv('CDK_DEFAULT_REGION')),
)
app.synth()
from aws_cdk import Stack
from aws_cdk import aws_iam as iam
from aws_cdk import aws_lambda as _lambda
from constructs import Construct
class LambdaMonorepoStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
function_a = _lambda.Function(
self,
'FunctionA',
code=_lambda.Code.from_asset_image(
directory='.',
file='functions/function_a/Dockerfile',
),
handler=_lambda.Handler.FROM_IMAGE,
runtime=_lambda.Runtime.FROM_IMAGE,
)
function_a.add_to_role_policy(
iam.PolicyStatement(
actions=['s3:ListAllMyBuckets'],
resources=['*'],
)
)
_lambda.Function(
self,
'FunctionB',
code=_lambda.Code.from_asset_image(
directory='.',
file='functions/function_b/Dockerfile',
),
handler=_lambda.Handler.FROM_IMAGE,
runtime=_lambda.Runtime.FROM_IMAGE,
)
uv による依存関係の一元管理
各 Lambda Function で必要な依存関係は異なっており、また、それぞれのコンテナイメージに不要なパッケージは含めたくありません。(コンテナイメージ軽量化のため)
そのため、それぞれの依存関係は個別に管理できるよう、uv の group
機能を活用します。
uv add --group function-a boto3 && \
uv add --group function-b fastapi mangum
ここまでで、pyproject.toml の内容は以下のようになっているはずです。
各 Lambda Function の依存関係がグループ毎に分かれていて、一目瞭然でわかりやすいと思います。
[project]
name = "lambda-monorepo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = []
[dependency-groups]
dev = [
"pytest==6.2.5",
]
function-a = [
"boto3>=1.37.38",
]
function-b = [
"fastapi>=0.115.12",
"mangum>=0.19.0",
]
infra = [
"aws-cdk-lib==2.189.0",
"constructs>=10.0.0,<11.0.0",
]
各 Lambda Function 用の Dockerfile を作成する際も、uv の group
機能を活用します。
ARG PYTHON_VERSION=3.11
FROM public.ecr.aws/lambda/python:${PYTHON_VERSION} AS builder
WORKDIR ${LAMBDA_TASK_ROOT}
COPY /uv /bin/
ENV UV_COMPILE_BYTECODE=1 \
UV_NO_INSTALLER_METADATA=1 \
UV_LINK_MODE=copy
RUN \
uv sync --group function-a --frozen
FROM public.ecr.aws/lambda/python:${PYTHON_VERSION}
COPY ${LAMBDA_TASK_ROOT} ${LAMBDA_TASK_ROOT}
COPY ./functions/function_a/lambda_function.py ${LAMBDA_TASK_ROOT}
CMD ["lambda_function.lambda_handler"]
ARG PYTHON_VERSION=3.11
FROM public.ecr.aws/lambda/python:${PYTHON_VERSION} AS builder
COPY /uv /bin/
ENV UV_COMPILE_BYTECODE=1 \
UV_NO_INSTALLER_METADATA=1 \
UV_LINK_MODE=copy
RUN \
uv export --group function-b --frozen --no-emit-workspace --no-dev --no-editable -o requirements.txt && \
uv pip install -r requirements.txt --target ${LAMBDA_TASK_ROOT}
FROM public.ecr.aws/lambda/python:${PYTHON_VERSION}
COPY ${LAMBDA_TASK_ROOT} ${LAMBDA_TASK_ROOT}
COPY ./functions/function_b/lambda_function.py ${LAMBDA_TASK_ROOT}
CMD ["lambda_function.lambda_handler"]
デプロイ & テスト
作成した Lambda Functions をデプロイします。
cdk deploy
デプロイが成功しました。
✅ LambdaMonorepo
✨ Deployment time: 38.33s
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:xxxxxxxxxxxx:stack/LambdaMonorepo/xxxxxxxx
✨ Total time: 62.57s
テストも成功しました。
function_a
function_b
まとめ
本記事では、AWS CDK Python, uv, Docker を組み合わせることで、複数の Lambda Function を Monorepo で効率的に管理・デプロイする方法を紹介しました。Monorepo での複数プロジェクト管理に悩んでいる方のヒントになれば嬉しいです。もっと良い方法があれば、ぜひコメント等で教えてください!
最後に
株式会社 Penetrator はシリーズ A ラウンドにおいて総額 5.5 億円の資金調達を実施し、不動産テック業界における更なる成長を目指して、採用活動を一層強化しています。エンジニア、デザイナー、カスタマーサクセス、BizDev、営業、マーケティングなど、事業拡大を支える多様なポジションで共に挑戦していただける方を待っています!
▽ 会社のカルチャーを知りたい方はこちら
▽ 募集職種を知りたい方はこちら
Discussion