AWS Lambdaで外部モジュールを追加する方法を情報まとめた
AWS Lambdaだと使えないライブラリ(モジュール)がありますね。追加が必要な場面がありますので外部モジュールの追加についてまとめます。
今回LambdaでS3にアップされた画像いじりたいのででPillow
使おうとしました。
モジュールをzipでアップすればいいのは知っていたのでpip installでデータ取得できたらzipにしてOKでしょ!
と思ってテストを実行したら
[ERROR] Runtime.ImportModuleError: Unable to import module 'lambda_function': No module named 'PIL'
とエラー。pip installだと無理なのこれ...どうするのこれ...みたいになったlambda初心者の私。
調べてみると方法が何個かあって、落とし穴的なものもあったので外部モジュールを追加する方法をまとめました。
今回はPillow使えるようにしますが、NumPyやpandasなどの他のモジュール使いたい時も手順は同じです。
概要
かなり簡単にいうと、Lambdaにモジュール入ったzipファイルをアップロードしてやればいいだけ!ではあります。
- ローカルでpipとzipコマンドが使える環境があれば作れます。
- windowsの人は注意。知識ない人はDockerやEC2インスタンス作ってLinux環境でやった方がスムーズ
- 簡単な方法。ただ、Pillowだと失敗した方法(requestとかならいけた)
- Pillowで成功した方法。whlファイルにてビルド
- 環境に合わせたビルドで作る方法
- Pillow, pandas, numpy使いたいならここみるだけでいい
- なぜ失敗したのかの理由はエラーの理由
- レイヤーを作成
- レイヤーを追加
- 一番スマートで他のLambda関数で使い回しもできるので便利
- 他の方法で追加できた後に試すと便利かも
実行環境はMac。Intelです。
EC2インスタンス作ってやる場合には触れません。適切かわかりませんが以前書いたDjangoアプリのEC2デプロイでUbuntuのインスタンスの立てた記事は書いたのでリンクは一応貼っておきます。7
だけやれば作れるはず。ただし、UbuntuではなくAmazonLinux2
を必ず選択。
※ Python3.7以下で作成する場合はAmazonLinuxにて作成すること。
最初にPillowだと失敗した方法
一番簡単な方法から。最初に言いますが、この方法だとPillowは失敗します。
この方法で十分追加可能なものもあります。
少なくてもpandas, numpy, pillowはこの方法ではできない。あと、画像をいじるような類のモジュールは厳しいのではと思ってます。
テキトウなディレクトリ作ってその中身をzipにしていく。
$ mkdir lambda_sample
作ったディレクトリにダウンロードしたPillowを入れるってコマンド
$ pip install Pillow -t ./lambda_sample
lambdaで処理する関数記述したファイルlambda_function.py
を作る。今回の場合Pillow入ってるかテストするだけなんで本当になんでもいいです。
$ touch ./lambda_sample/lambda_function.py
lambda作ったときに書かれてたほぼそのままの記述だけ書いておきます。もちろん自由に変えてOK
もし、すでにコードを書いていたら↓のコードで上書きされる。その場合はコードをコピペしてください。
import json
from PIL import Image
def lambda_handler(event, context):
print(event)
# TODO implement
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
ディレクトリ配下のファイルに権限与える。
$ chmod -R 755 ./lambda_sample/
アップロードするためにzipにする。ファイル名はなんでもいいです。
$ cd lambda_sample
$ zip -r9 ${OLDPWD}/mypackage.zip .
カレントディレクトリをzipにして名前はmypackage.zip
にしました。
lambdaでzipファイルアップロード
よーし余裕だ!
と思ってテストの実行したら
"Unable to import module 'lambda_function': cannot import name '_imaging' from 'PIL' (/var/task/PIL/__init__.py)"
なんだこれは、、、
エラーの理由
lambdaはAmazonlinux2で実行してます。外部モジュールがAmazonlinux2
用になっていないと出るエラーらしい。
実行環境は以下を参照
そして、python3.7まではAmazon Linux
でpython3.8以上がAmazon Linux 2
です。
これまでやってきた方法で問題ないモジュールはあると思いますが、インストール時にOSに合わせてビルドするというPillowはダメらしい。
試しにrequests
でやったら大丈夫でした。画像扱うものはビルドするようなの多かった記憶あるのでは難しいかも?
環境に合わせたビルドで作る方法
- EC2でamazonlinux2のインスタンス作ってそこで作成する
- Dockerにてamazonlinux2のコンテナでこれまでと同じ手順で作る
- whlファイルから作成
結果から書くと、Pillowだけ入れるなら3.
が一番楽でzipファイルの中身もスッキリしていて容量小さいです。AWSのQ&Aの回答にあった方法ですし。
1の方法と2の方法はやっていることは基本的に同じで、dockerで作るかEC2インスタンスで作るかの違いだけです。pillow以外でも使えるのでpillow以外も使うとか、他の人も使えるようにしたいというなら2の方法を使ってもいいと思います。
1の方法は個人的にモジュール作るだけのためにEC2作りたくないし、会社とかで自由にインスタンス作れないということもあると思うので2
の方法を解説して、もちろん3の簡単な方法も解説します。
2. Dockerにてamazonlinux2のコンテナにてモジュール作る
ということでdocker-hubから使えそうなのを探す。aws-lambda-python
とかいうちょうど良さそうなものを発見したので使ってみる。
この時はlatestがlinux/amd64
です。lambda関数作るときにPythonのバージョンを合わせるの忘れずに。
lambdaの実行環境arm64選択した場合は使えるのかわからないので注意。
Docker imageを作成
私の場合はLambdaはPython3.9で使おうと思っていたので、3.9のイメージを取ってきます。
$ mkdir pillow_lambda
$ touch ./pillow_lambda/Dockerfile ./pillow_lambda/docker-compose.yml
FROM public.ecr.aws/lambda/python:3.9
RUN pip install pillow
CMD [ "app.handler" ]
$ cd pillow_lambda
$ docker build -t lambda_image .
-
lambda_image
といいイメージの名前つける - カレントディレクトリにある
Dockerfile
を使ってbuildする
イメージを実行する。
$ docker run lambda_image
あとはパッケージをコピーしてしまう。CONTAINER IDわかればコピーできるのでまず調べて
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f3a49e4fef5c lambda_image "/lambda-entrypoint.…" About a minute ago Up About a minute
docker cp <CONTAINER ID>:/var/lang/lib/python3.9/site-packages/ <コピー先ディレクトリ>
してやればOK
function_package
ディレクトリにでもコピーすることにします。
$ mkdir ~/function_package
$ docker cp f3a49e4fef5c:/var/lang/lib/python3.9/site-packages/ ~/function_package/
python3.9はバージョンに合わせて変える
あとはlambdaで処理する関数も作成する必要あるので作成。
下記のようにデフォルトのサンプルみたいなコードで試します。
$ cd
$ touch ./function_package/lambda_function.py
import json
from PIL import Image
def lambda_handler(event, context):
print(event)
# TODO implement
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
$ cd function_package/site-packages
$ zip -r9 ${OLDPWD}/mypackage.zip .
$ cd ~/function_package
$ zip -u ~/mypackage.zip lambda_function.py
${OLDPWD}
は適時出力先を変えてください。
一応解凍して確認してみてください。mypackageの中に複数のモジュールとlambda_function.pyが同じディレクトリにあればOK。階層深くなってないのを確認してください。読み込めないので。
mypackage/
├ lambda_function.py/
├ PIL/
│ └.../
└ .../
詳しくないけど、この方法だとbuildだけに使うものとか、不要そうなものも入ってる気がする。使う分には問題ない。
あとはアップロードするだけ。ここは失敗した方法と同じ。
lambdaでzipファイルアップロード
アップロードが問題なく終わると
Lambda 関数「convert_mock」のデプロイパッケージが大きすぎて、インラインコード編集を有効にできません。ただし、関数を呼び出すことはできます。
のような表示が出てコードが見れなくなっているので少しびびった。
ただ、モジュールの作り方に問題がなければ、普通に動くし、テストタブからテストで動かすことも可能です。
関数のコードには問題なくてもモジュールが大きいとこうなるらしいのでしょうがない。(3MB以上?らしい)
3. whlファイルから作成
結果的にこの方法が一番楽でした。というか、AWS公式がこの方法使ったらいいです。と回答してた。
https://aws.amazon.com/jp/premiumsupport/knowledge-center/lambda-python-package-compatible/
この例だとpandasですが、pillowでも同じです。
whlファイル入手
pypiのPillowページからDownload filesのページに。
選ぶファイルは注意。今回のlambdaの実行環境は前述した通りPython 3.9
,amazonlinux2
,x86_64
なので該当する項目をダウンロード。
Pillow-9.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- cp39
- manylinux_2
- x86_64
と書いてあるのが該当する事になる。Pillowのバージョンはこの時の最新9.1.1使ってます。そこはこだわりなければ最新でいいと思います。
whlからもモジュール、zipファイル作成
zipコマンドで作るので一式を入れるディレクトリ作る。
$ mkdir my-lambda-function
この中に.whlを入れる。
以下は例です。lambda_func_name
を変更してください。自分で作ったlambda関数作った時の名前です。
※ 作ってない人はtargetに指定した名前でlambda関数作成
$ pip install \
--platform manylinux2014_x86_64 \
--target=lambda_func_name \
--implementation cp \
--python 3.9 \
--only-binary=:all: --upgrade \
Pillow
バージョン 19.3.0 以降の pip を使用してください。失敗します。
$ cd lambda_func_name
$ touch lambda_function.py
これまでと同じように関数のコードはお好きに変更してください。今回はPillowがimportエラーになるかだけ確認するコードが以下
import json
from PIL import Image
def lambda_handler(event, context):
print(event)
# TODO implement
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
実行するディレクトリに注意して以下を実行。
~/my-lambda-function/lambda_func_name $ zip -r ../my-deployment-package.zip .
あとは作成されたmy-deployment-package.zip
をアップロードすればいいだけ。
ここは他の方法と同じ。
lambdaでzipファイルアップロード
テストを実行してエラーが出なければOK
レイヤーを追加する
デプロイパッケージが大きすぎて、インラインコード編集を有効にできません。
と出る。
- パッケージ入れるとインラインコード見えない。
- 少し確認、変更したい時に不便
- 他のlambda関数と共有もできるので便利。
これらはレイヤーとして登録すれば解消できます。
- 先ほどまではlambdaの関数を含めてzipにしてた。関数のコード書いてあるファイルはモジュールの外
- ディレクトリを
python
で作成して作ることがおすすめ。- pythonにするとimportがわかりやすいし、わかりやすい
- 共通して使えるutilみたいなコードを他のLambda関数でも使いたい場合は共通処理のコードを入れることも可能
手順
基本的にはこれまでのパッケージ作成とあまり変わらないですが
-
lambda_function.py
はzipに含めない - pythonという名前のフォルダ作成。(必須でないですが、やりやすくわかりやすい)
- フォルダの中にモジュールを入れる(
- pip installのインストール先調整
- layerをAWSの画面で作成
関数は以下のコード。ythonという名前のフォルダ作成することでfrom PIL import Image
のようにいつものpythonで使うようにimportできるので便利。
import json
from PIL import Image
def lambda_handler(event, context):
print(event)
# TODO implement
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
モジュールのzip作成
例えばwhl使った方法ならwhlファイルを配置したディレクトリにて
$ mkdir python
$ pip install \
--platform manylinux2014_x86_64 \
--target=python \
--implementation cp \
--python 3.9 \
--only-binary=:all: --upgrade \
Pillow
pythonディレクトリの中にモジュールがはいっていたらいいです。
pip installの場合
例えばrequestsだったらpip installで簡単にできるので以下のコマンドでOK
$ pip install requests -t ./python
zipファイルにする
あとはこれをzip圧縮してしまう。
$ zip -r9 layer.zip python
ファイル完成。名前はlayer.zip
でなくても問題ないです。このzipファイル名はどこにも影響しないので好きなようにつけてOK
レイヤーを追加
先ほど作成したzipをアップロードします。
※ 一応、リージョンでまたいで使用することはできなかったと思うので確認してください。
サイドバーにレイヤーという項目あるので押す → レイヤーの追加 → 名前つけて作成したzipをアップロード
今回はx86_64を使ってますのでそちらを選択。ランタイムは使用するPython選んでおけばOK
layersを押して → レイヤーの追加 → カスタムレイヤー
作成したレイヤーを選択して追加。
以上です!
最後に
レイヤーの追加について。ECR使ってレイヤーを追加することもできるらしい。試してないけど今度トライしてみたい。