サーモン大好き横山です。
今回、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に失敗した、という感じでエラーで動きませんでした。