はじめに
アプリケーションサービス部の鎌田(義)です。
最近CDKを使った構築に関わる機会があり、
プログラミング言語で記述できる点が気に入っていて、個人的にもIaCにはCDKを使う機会が増えています。
その中でPython3.12ランタイムのLambdaをデプロイしようとして
躓いたことがあった為、その内容を共有します。
前提
CDKのコンストラクタには、@aws-cdk/aws-lambda-python-alphaを使用していました。
上記機能は、現在も開発中のExperimentalな機能です。
Dockerコンテナ上でPipfile/poetry.lock/requiements.txtなどのファイルをもとに
依存関係を解決した上で、Lambdaをデプロイすることが可能になります。
以下の条件下で使用している際にエラーに遭遇しました。
今回の記事に関係のある部分のみ抜粋して記載しています。
- Lambdaランタイムには、Python3.12を使用
- コンストラクタには、@aws-cdk/aws-lambda-python-alphaのPythonFunctionを使用
- パッケージングには、Pipenvを使用
起こったこと
以下サンプルのようなスタックをデプロイしようとしました。
CDKコードにはTypescriptを使用しています。
import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { aws_lambda as lambda } from 'aws-cdk-lib'; import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; export class TestCdkStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const entry = 'src' new PythonFunction(this, 'TestFunction', { functionName: "testFunction", runtime: lambda.Runtime.PYTHON_3_12, entry, index: 'index.py', handler: 'handler', bundling: { assetExcludes: ['.venv'], }, }); } }
cdk synth
すると以下のようなエラーが出力されました。
Traceback (most recent call last): File "/usr/app/venv/bin/pipenv", line 5, in <module> from pipenv import cli File "/usr/app/venv/lib/python3.12/site-packages/pipenv/__init__.py", line 57, in <module> from .cli import cli File "/usr/app/venv/lib/python3.12/site-packages/pipenv/cli/__init__.py", line 1, in <module> from .command import cli # noqa ^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/app/venv/lib/python3.12/site-packages/pipenv/cli/command.py", line 7, in <module> from pipenv.cli.options import ( File "/usr/app/venv/lib/python3.12/site-packages/pipenv/cli/options.py", line 3, in <module> from pipenv.project import Project File "/usr/app/venv/lib/python3.12/site-packages/pipenv/project.py", line 21, in <module> from pipenv.core import system_which File "/usr/app/venv/lib/python3.12/site-packages/pipenv/core.py", line 30, in <module> from pipenv.utils.resolver import venv_resolve_deps File "/usr/app/venv/lib/python3.12/site-packages/pipenv/utils/resolver.py", line 14, in <module> from pipenv.vendor.requirementslib import Pipfile, Requirement File "/usr/app/venv/lib/python3.12/site-packages/pipenv/vendor/requirementslib/__init__.py", line 7, in <module> from .models.lockfile import Lockfile File "/usr/app/venv/lib/python3.12/site-packages/pipenv/vendor/requirementslib/models/lockfile.py", line 14, in <module> from ..utils import is_editable, is_vcs, merge_items File "/usr/app/venv/lib/python3.12/site-packages/pipenv/vendor/requirementslib/utils.py", line 11, in <module> import pip_shims.shims File "/usr/app/venv/lib/python3.12/site-packages/pipenv/vendor/pip_shims/__init__.py", line 26, in <module> from . import shims File "/usr/app/venv/lib/python3.12/site-packages/pipenv/vendor/pip_shims/shims.py", line 12, in <module> from .models import ( File "/usr/app/venv/lib/python3.12/site-packages/pipenv/vendor/pip_shims/models.py", line 775, in <module> Command.add_mixin(SessionCommandMixin) File "/usr/app/venv/lib/python3.12/site-packages/pipenv/vendor/pip_shims/models.py", line 689, in add_mixin mixin = mixin.shim() ^^^^^^^^^^^^ File "/usr/app/venv/lib/python3.12/site-packages/pipenv/vendor/pip_shims/models.py", line 737, in shim result = self.traverse(top_path) ^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/app/venv/lib/python3.12/site-packages/pipenv/vendor/pip_shims/models.py", line 729, in traverse result = shim.shim() ^^^^^^^^^^^ File "/usr/app/venv/lib/python3.12/site-packages/pipenv/vendor/pip_shims/models.py", line 575, in shim imported = self._import() ^^^^^^^^^^^^^^ File "/usr/app/venv/lib/python3.12/site-packages/pipenv/vendor/pip_shims/models.py", line 600, in _import result = self._import_module(self.calculated_module_path) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/app/venv/lib/python3.12/site-packages/pipenv/vendor/pip_shims/models.py", line 352, in _import_module imported = importlib.import_module(module) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/var/lang/lib/python3.12/importlib/__init__.py", line 90, in import_module return _bootstrap._gcd_import(name[level:], package, level) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/app/venv/lib/python3.12/site-packages/pipenv/patched/notpip/_internal/cli/req_command.py", line 15, in <module> from pipenv.patched.notpip._internal.cache import WheelCache File "/usr/app/venv/lib/python3.12/site-packages/pipenv/patched/notpip/_internal/cache.py", line 13, in <module> from pipenv.patched.notpip._internal.exceptions import InvalidWheelFilename File "/usr/app/venv/lib/python3.12/site-packages/pipenv/patched/notpip/_internal/exceptions.py", line 7, in <module> from pipenv.patched.notpip._vendor.pkg_resources import Distribution File "/usr/app/venv/lib/python3.12/site-packages/pipenv/patched/notpip/_vendor/pkg_resources/__init__.py", line 2164, in <module> register_finder(pkgutil.ImpImporter, find_on_path) ^^^^^^^^^^^^^^^^^^^ AttributeError: module 'pkgutil' has no attribute 'ImpImporter'. Did you mean: 'zipimporter'?
pkgutilには、ImpImporterという属性がないと出力されています。
pkgutilパッケージのページを見るとPython3.12のページからはImpImporterが削除されていました。
原因
エラーログにも出力されていたのですが、パッケージのバンドルには以下のコマンドが実行されているようです。
上記で記載したエラーログと合わせて見ると、どうやらpipenvの中でpkgutil.ImpImporterを使用していそうでした。
--> Command: docker run --rm -u "1000:1000" -v "<リポジトリ>/src:/asset-input:delegated" -v "<リポジトリ>/cdk.out/asset.322e9618894cee11d5bc6725b0f29ca137612e076f6c77f43e7b9cbb2014d332:/asset-output:delegated" -w "/asset-input" cdk-2cd508e07a6318940da85c0effe19a529949446120216189336894a12f3a56f1 bash -c "rsync -rLv --exclude='.venv' /asset-input/ /asset-output && cd /asset-output && PIPENV_VENV_IN_PROJECT=1 pipenv requirements > requirements.txt && rm -rf .venv && python -m pip install -r requirements.txt -t /asset-output"
自身の開発環境では以下を使用しており、pipenvも正常に使用できています。
- Python 3.12.2
- pipenv==2023.12.1
では、なぜ??と思いましたが、
バンドルに使用するDockerコンテナの中では別バージョンのpipenvが使用されていると思い調べてみました。
実行ログを見るとDockerイメージには、sam/build-python3.12が使われていますが、
イメージに使用されているDockerfileを見ても、
pipenvがインストールされている様子もなくイメージをダウンロードしてpip list
で確認してみても、pipenvは存在していませんでした。
次に@aws-cdk/aws-lambda-python-alpha を調べてみると、Dockerfileがありました。
Dockerfileの中で以下記述を見つけました。
# pipenv 2022.4.8 is the last version with Python 3.6 support pip install pipenv==2022.4.8 poetry==$POETRY_VERSION && \
pipenv==2022.4.8
が使用されているようです。
試しに、自身の環境を以下に変更しました。
- Python 3.12.2
- pipenv==2022.4.8
pipenv requirements
を実行してみると、同様のエラーが発生することを確認できました。
回避策
現時点(2024/5/15)では、前提に記載した条件下ではエラーとなってしまうようですが
回避可能ないくつかの案を記載します。
- poetryを使用する
- requirements.txtを使用する
- カスタムDockerイメージを使用する
- @aws-cdk/aws-lambda-python-alphaを使用せず、自身でパッケージングする
1. poetryを使用する
私はこちらの案を採用しましたが、
特段記載することがない為、本記事では案2, 3についてのみサンプルを記載します。
2. requirements.txtを使用する
スタックを以下に書き換えました。
bundlingの記述を削除し、entry配下にrequirements.txtを配置します。
import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { aws_lambda as lambda } from 'aws-cdk-lib'; import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; export class TestStackOk2 extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const entry = 'src' new PythonFunction(this, 'TestFunction', { functionName: "testFunction", runtime: lambda.Runtime.PYTHON_3_12, entry, index: 'index.py', handler: 'handler', }); } }
開発環境ではPipfileを使用しつつ、パッケージングにはrequirements.txtを使うパターン
entry配下にPipfileも置いている場合は、以下のようにbundling時のコマンドを上書きする必要があります。
cdk synth
する前にpipenv requirements.txt > requirements.txt
でrequirements.txtを生成しておきます。
import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { aws_lambda as lambda } from 'aws-cdk-lib'; import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; export class TestStackOk2 extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const entry = 'src' new PythonFunction(this, 'TestFunction', { functionName: "testFunction", runtime: lambda.Runtime.PYTHON_3_12, entry, index: 'index.py', handler: 'handler', bundling: { // Pipfileがentryと同じ階層に存在する場合、コンテナ内で自動的にPipfileからrequierements.txtが生成される為commandを上書きする必要がある // 既存コマンド: bash -c "rsync -rLv /asset-input/ /asset-output && cd /asset-output && PIPENV_VENV_IN_PROJECT=1 pipenv requirements > requirements.txt && rm -rf .venv && python -m pip install -r requirements.txt -t /asset-output" command: [ 'bash', '-c', 'rsync -rLv --exclude=".venv" --exclude="Pipfile*" /asset-input/ /asset-output && cd /asset-output && rm -rf .venv && python -m pip install -r requirements.txt -t /asset-output', ], }, }); } }
3. カスタムDockerイメージを使用する
Dockerfile内で、pipenv==2022.4.8
が指定されている点が原因の為
Dockerfileを別途用意しカスタムしたDockerイメージを使用してみます。
import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { aws_lambda as lambda } from 'aws-cdk-lib'; import { DockerImage } from 'aws-cdk-lib'; import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha'; export class TestStackOk3 extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const entry = 'src' const image = DockerImage.fromBuild(entry) new PythonFunction(this, 'TestFunction', { functionName: "testFunction", runtime: lambda.Runtime.PYTHON_3_12, entry, index: 'index.py', handler: 'handler', bundling: { image, assetExcludes: ['.venv', 'Dockerfile'], }, }); } }
entryと同じ階層に配置したDockerfileを使用するように書き換えました。
Dockerfileは以下を用意しましたが、もとのDockerfileをほぼコピペで
1行目のFROMとpipenv==2023.12.1
の部分のみ書き換えました。
# The correct AWS SAM build image based on the runtime of the function will be # passed as build arg. The default allows to do `docker build .` when testing. FROM public.ecr.aws/sam/build-python3.12 ARG PIP_INDEX_URL ARG PIP_EXTRA_INDEX_URL ARG HTTPS_PROXY ARG POETRY_VERSION=1.5.1 # Add virtualenv path ENV PATH="/usr/app/venv/bin:$PATH" # set the pip cache location ENV PIP_CACHE_DIR=/tmp/pip-cache # set the poetry cache ENV POETRY_CACHE_DIR=/tmp/poetry-cache RUN \ # create a new virtualenv for python to use # so that it isn't using root python -m venv /usr/app/venv && \ # Create a new location for the pip cache mkdir /tmp/pip-cache && \ # Ensure all users can write to pip cache chmod -R 777 /tmp/pip-cache && \ # Upgrade pip (required by cryptography v3.4 and above, which is a dependency of poetry) pip install --upgrade pip && \ # Create a new location for the poetry cache mkdir /tmp/poetry-cache && \ # Ensure all users can write to poetry cache chmod -R 777 /tmp/poetry-cache && \ # pipenv 2022.4.8 is the last version with Python 3.6 support pip install pipenv==2023.12.1 poetry==$POETRY_VERSION && \ # Ensure no temporary files remain in the caches rm -rf /tmp/pip-cache/* /tmp/poetry-cache/* CMD [ "python" ]
最後に
最後までご覧頂きありがとうございます。
繰り返しにはなりますが@aws-cdk/aws-lambda-python-alphaはExperimentalな機能です。
今回の件については、Issueで報告している為その内解消されるかもしれません。
前提にも記載したように特定の条件下のみで発生する為、
遭遇するケースも少ないかもしれませんがどなたかの参考になれば幸いです。
鎌田 義章 (執筆記事一覧)
2023年4月入社 AS部DS3課