🐳

cdk でデプロイ可能な uv 管理の AWS Lambda コンテナイメージのスケルトン

2024/10/22に公開

普段、pythonで書いたLambdaをStepfunctionsなどにして、cdkでIaCすることで楽に色々な仕組みを作っています。特にLambdaは、コンテナイメージとして開発することで、関数1つあたりのパッケージサイズや、アカウント全体のアーカイブやレイヤーサイズの総計に関するクォータの低さに縛られない開発ができますし、ECSなどへの転用もしやすくなります。

また、python言語は使える人が多い一方で、パッケージやプロジェクト管理ツールがいくつもあるので色々なスタイルがあってメンバー間でツールの統一に少し困ります。あまり古いものをずっと使っていると、他のメンバーの足を引っ張ってしまいます。

などなど諸般の理由から、新し目で流行りそうなuvとコンテナ技術を使ったリソースをCDKでIaCしながらデプロイすることができれば、しばらくは使えそうなので試してみました。

前提

uv, docker engine, npm をインストール済の状態とします。OS は Ubuntu 24 LTS の想定です。

ランタイムやエンジンまわりを準備します。cdk の python のサンプルコードをベースにします。

bash ~/${workspaceFolder}
$ uv python install 3.12
$ npm install -g aws-cdk
$ cdk init app --language python
$ uv init
$ uv add -r requirements.txt && rm -f ./requirements.txt
$ uv add -r requirements-dev.txt --dev && rm -f ./requirements-dev.txt
$ mkdir -p ./src
$ touch ./src/sample_lambda.py ./Dockerfile

コードとIaCを書く

Dockerfile を書きます。

${workspaceFolder}/Dockerfile
# AWS Lambda 関数の公式コンテナイメージ https://gallery.ecr.aws/lambda/python で
# uv https://docs.astral.sh/uv/ を使って python パッケージ管理と実行を可能にする最小限の例
FROM public.ecr.aws/lambda/python:3.12

# uv を公式イメージからCOPYする方法でインストールする
# https://docs.astral.sh/uv/guides/integration/docker/#installing-uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
ENV PATH="/app/.venv/bin:$PATH"

# AWS Lambda は書込可能なディレクトリが `/tmp` 配下だけなので uv の cache も同ディレクトリ配下を使うように変更する
# https://docs.astral.sh/uv/configuration/environment/
ENV UV_CACHE_DIR=/tmp/.uv_cache

# uv の lockfile からプロジェクトの python パッケージ をインストールする
COPY pyproject.toml uv.lock ${LAMBDA_TASK_ROOT}/
RUN uv sync

# Lambda関数のコード `./src/` をイメージに含める
COPY src/ ${LAMBDA_TASK_ROOT}/

# AWS Lambda イメージのデフォルトのエントリーポイント `lambda-entrypoint.sh` を
# uv の仮想環境で実行させることで sync したパッケージが利用可能なコンテキストでLambdaを実行させる
ENTRYPOINT ["uv", "run", "/lambda-entrypoint.sh"]

# `lambda-entrypoint.sh` に `./src/lambda_handler.py` の `handler(event, context)` を
# Lambda関数のエントリーポイントとして指定する例。プロジェクトに合わせてお好みで
# CMD ["lambda_handler.handler"]

Lambda のコードを書きます。

${workspaceFolder}/src/sample_lambda.py
def handler(event, context):
    return {'statusCode': 200, 'body': 'Hello from Lambda!'}

cdk スタックを書きます。コンテナイメージとしてビルドされる Lambda 関数で、ハンドラは先のコードを使う構成です。イメージは ECR に push されます。

${workspaceFolder}/temp/temp_stack.py
from aws_cdk import aws_lambda
from aws_cdk import Duration, Stack
from constructs import Construct

class TempStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        code = aws_lambda.DockerImageCode.from_image_asset(directory=".", cmd=["sample_lambda.handler"])
        aws_lambda.DockerImageFunction(scope=self, id=f"sample-lambda" code=code)

デプロイする

bash ~/${workspaceFolder}
$ npx cdk bootstrap
$ npx cdk deploy

Lambda関数が1つデプロイできたら成功です。テスト実行すると HTTP 200 Hello from Lambda! を返してくれるはずです。

Discussion