pyminizip を AWS Lambdaで使いたい
サーモン大好き横山です。
今回、Macから serverless framework + poetryを用いて、AWS Lambdaへdeployを行いパスワード付zipを作りたい!とおもってやってみましたところ、パッケージがimport出来ないと言われました。今回はその解決策を書きます。
実行環境
以下のMacの環境からやります。
$ sw_vers ProductName: macOS ProductVersion: 12.4 BuildVersion: 21F79 $ uname -mprsv Darwin 21.5.0 Darwin Kernel Version 21.5.0: Tue Apr 26 21:08:37 PDT 2022; root:xnu-8020.121.3~4/RELEASE_ARM64_T6000 arm64 arm $ serverless --version Running "serverless" from node_modules Framework Core: 3.22.0 (local) 3.22.0 (global) Plugin: 6.2.2 SDK: 4.3.2 $ python -V Python 3.9.13
デプロイ準備
serverless create -t aws-python3 -n pyminizip
で作成したプロジェクト配下に以下のファイルを編集・追加する。
handler.py
import logging from typing import Any import pyminizip logger = logging.getLogger() logger.setLevel(logging.INFO) def hello(event: dict, context: Any) -> None: logger.info("hello world.")
serverless.yml
service: pyminizip frameworkVersion: "3" configValidationMode: error provider: name: aws runtime: python3.9 region: ap-northeast-1 plugins: - serverless-python-requirements functions: pyminizip: handler: handler.hello custom: pythonRequirements: usePoetry: true package: patterns: - "!**/" - handler.py
pyproject.toml
[tool.poetry] name = "aws_lambda_pyminizip" version = "0.1.0" description = "" authors = ["yokoyama.fumihito <xxx@xxx.jp>"] [tool.poetry.dependencies] python = "^3.9" pyminizip = "^0.2.6" [tool.poetry.dev-dependencies] [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api"
編集後Python3.9でinstallを実行する。
$ poetry env use 3.9 $ poetry install
ここまでのディレクトリ構造
$ tree . . ├── handler.py ├── poetry.lock ├── pyproject.toml └── serverless.yml 0 directories, 4 files
serverless deployをして動作確認
pluginをインストールしてからdeployをします。
$ export AWS_PROFILE=xxx #Deploy先のAWSのProfile $ serverless plugin install -n serverless-python-requirements $ serverless deploy
AWS Lambdaの実行を確認
deployしたAWS Lambdaの関数を実行するとエラーになります。
{ "errorMessage": "Unable to import module 'handler': No module named 'pyminizip'", "errorType": "Runtime.ImportModuleError", "requestId": "a4ca31b5-49a0-4a15-9d27-ad5ef41f2382", "stackTrace": [] }
pyminizipのパッケージがUnix/Linuxの共有ライブラリ形式(*.soファイル)でインストールされています。今回のエラーはMacの実行環境とAWS Lambda内の実行環境とでCPUアーキテクチャの違いにより、共有ライブラリ経由のimportが失敗しているからです。
対処方法
今回は、serverless plugin serverless-python-requirements
の クロスコンパイル機能を使いAWS Lambdaへdeployし直します。
クロスコンパイルするために、AWSから用意されているdocker imageを使います。
事前に下記のdocker image public.ecr.aws/sam/build-python3.9:latest-x86_64
をlocalにpullしておきます。
$ docker pull public.ecr.aws/sam/build-python3.9:latest-x86_64
Dockerfileを新たに作成します。 下記記述し、 build/Dockerfile
へ保存します。
FROM public.ecr.aws/sam/build-python3.9:latest-x86_64
serverless.ymlのpluginの設定を変更します。 クロスコンパイル機能は、poetryの機能がonのままだとうまく動かないので、 usePoetry: false
にするのを忘れないようにしてください。
custom: pythonRequirements: usePoetry: false dockerizePip: true dockerFile: build/Dockerfile
ここまで対応したディレクトリ構成はこんな感じになります。
$ tree . -I node_modules . ├── build │ └── Dockerfile ├── handler.py ├── package-lock.json ├── package.json ├── poetry.lock ├── pyproject.toml └── serverless.yml
修正版をserverless deploy をして動作確認
deployをする前に、Pythonのパッケージをダウンロードしたcacheが残っているとcacheからdeploy packageを作成しようとしてしまうので、一度cacheの方を削除します。
$ serverless requirements cleanCache
クロスコンパイル機能を動かすために、deploy直前に requirements.txt を手動で出力します。
$ poetry export --without-hashes -f requirements.txt -o requirements.txt --with-credentials
docker imageを利用してdeploy packageを作成しているか確認するために --verbose
の引数を使用してserverless deployをしていきます。
Running docker run --rm 〜
というログが出ていればうまく実行できてます。
$ serverless deploy --verbose Running "serverless" from node_modules Deploying pyminizip to stage dev (ap-northeast-1) Packaging Generated requirements from /path/to/requirements.txt in /path/to/.serverless/requirements.txt Installing requirements from "/Users/user/Library/Caches/serverless-python-requirements/97a77931bd1f047a2b8a5c20db8ef50459973e46acdb97ddee8842ecce71c43b_x86_64_slspyc/requirements.txt" Docker Image: sls-py-reqs-custom Using download cache directory /Users/user/Library/Caches/serverless-python-requirements/downloadCacheslspyc Running docker run --rm -v /Users/user/Library/Caches/serverless-python-requirements/97a77931bd1f047a2b8a5c20db8ef50459973e46acdb97ddee8842ecce71c43b_x86_64_slspyc\:/var/task\:z -v /Users/user/Library/Caches/serverless-python-requirements/downloadCacheslspyc\:/var/useDownloadCache\:z -u 0 sls-py-reqs-custom python3.9 -m pip install -t /var/task/ -r /var/task/requirements.txt --cache-dir /var/useDownloadCache... Excluding development dependencies for service package Injecting required Python packages to package Retrieving CloudFormation stack ...(snip)...
AWS Lambdaの実行を確認
importエラーが解消されて、ハンドラーが正常に実行できました。
まとめ
共有ライブラリを含むPythonのパッケージをAWS Lambdaへdeployする場合の一助となれば幸いです。
補足:importの挙動のあれこれ
ここからは、CPUアーキテクチャの違う共有ライブラリのimportの挙動に関しての補足です。
前述の通り、pyminizipはC言語のコードをコンパイルし、共有ライブラリとして利用しています。 → GitHub
serverless deployのコマンドを叩いたときに作成するzipファイルを解凍して中を見てみると、pyminizipの共有ライブラリが含まれています。
## Deployのための生成物が .serverless ディレクトリに集められている $ cd .serverless/ ## pyminizip.zipを解凍 $ mkdir dist $ cd dist $ unzip ../pyminizip.zip ## 共有ライブラリを検索 $ ls *.so pyminizip.cpython-39-darwin.so $ file pyminizip.cpython-39-darwin.so pyminizip.cpython-39-darwin.so: Mach-O 64-bit bundle arm64
共有ライブラリのファイルはpathが通っている場所にあるとimportすることが可能です。 python実行するディレクトリ直下においてもimportすることが出来ます。
## 実行するPython環境にはpyminizipはインストールされていない $ python3 -mpip freeze | grep pyminizip ## ディレクトリ直下にpyminizipの共有ライブラリが存在する場合 ## importエラーが出ない $ ls pyminizip.cpython-39-darwin.so $ python3 -c "import pyminizip" $ ## ディレクトリ直下にpyminizipの共有ライブラリを削除した場合 ## importエラーが発生する $ rm pyminizip.cpython-39-darwin.so $ python3 -c "import pyminizip" Traceback (most recent call last): File "<string>", line 1, in <module> ModuleNotFoundError: No module named 'pyminizip'
Macとは違うCPUアーキテクチャの環境に共有ライブラリを持っていきpyminizipをimportしようとしても、importエラーになります。 下記はCloudShell上に共有ライブラリを転送し、コマンドを叩いた結果です。
[cloudshell-user@ip-10-0-x-x tmp]$ uname -a Linux ip-10-0-127-227.ap-northeast-1.compute.internal 4.14.287-215.504.amzn2.x86_64 #1 SMP Wed Jul 13 21:34:43 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux ## ディレクトリ直下に存在することを確認 [cloudshell-user@ip-10-0-x-x tmp]$ ls pyminizip.cpython-39-aarch64-linux-gnu.so ## pyminizipをimportしようとしてもエラーになる [cloudshell-user@ip-10-0-x-x tmp]$ python3 -c "import pyminizip" Traceback (most recent call last): File "<string>", line 1, in <module> ModuleNotFoundError: No module named 'pyminizip'
ですので、最初にdeployした方法ですと、MacのCPUアーキテクチャ用に作成された共有ライブラリが封入されます。そして、AWS Lambdaの環境で動かそうとしてimportに失敗した、という感じでエラーで動きませんでした。