AWS Lambda の PythonでShared Library(*.so)を含む外部ライブラリを扱う方法
仕事でPython / Lambdaを触る機会があり、Shared Library(*.so) を扱うライブラリの取扱でめちゃくちゃはまったので、メモっておく。
あと最近エモい記事ばっかり書いていたので、たまにはエンジニアアピールしておく。
Python初心者なので、それxxxでできるよ的な話があれば教えてください。
TL;DR
- Lambdaで外部ライブラリを扱うときにはZIPファイルに固めてアップロードする必要があるが、これに
.so
ファイルも一緒に固めておく - LD_LIBRARY_PATH はLambda実行時に指定できないので
ctypes.cdll.LoadLibrary
でPython実行時に指定して読み込む - MacでビルドするとLambdaで動かなかったりするので、Dockerを使ってAWSのAmazon Linuxなどでpip install など行う
何がやりたかったのか
Lambdaでとある形式のファイルから、画像を抜き出すという処理をPythonでやろうとしていた。Lambdaでは依存する外部ライブラリは実行したいファイルと一緒にZIPで固めてアップロードすればよいらしい(Pythonの場合)。
よく紹介されているのは以下の方法だ。
pip install -r requirements.txt -t .
しかしPythonのライブラリの中には、純粋なPythonだけでなくて、Cなどで作られたライブラリをWrapしてPythonから使えるようにしているものがある。その場合は .py
ファイルだけZIPにつめても実行時に以下のようなエラーになってしまう。
ImportError: libgdcmMEXD.so.2.8: cannot open shared object file: No such file or directory
しかし、 *.so を単純にZIPに詰めれば解決するという問題ではない。Lambdaでは *.so
の読み込み先を決める LD_LIBRARY_PATH
をいじれないからだ。
どうするのか
Lambdaでの実行用にZIPに固める際に、以下のように必要なLibraryをlib配下にまとめてしまう。
# 依存関係を./distにインストール pip install -r requirements.txt -t ./dist # gdcm関連パッケージを./distにコピー cp -r /opt/conda/lib/python3.6/site-packages/*gdcm* ./dist mkdir -p ./dist/lib cp -r /opt/conda/lib/libgdcm* ./dist/lib cp -r /opt/conda/lib/libsocketxx* ./dist/lib # 容量削減のためtestsファイルを削除 rm -rf ./dist/**/tests # メインファイルを./distにコピー cp ./main.py ./dist
その上で、Lambdaで実行するファイルで、明示的に lib
配下のファイルを読み込んでやればよい。
import os import ctypes for d, dirs, files in os.walk('lib'): for f in files: if f.endswith('.a'): continue ctypes.cdll.LoadLibrary(os.path.join(d, f))
TIPSとして、Mac環境でこれをやってもAWSのLambdaが動くLinuxの環境では動作しなかったりするので、LinuxのDockerイメージを用意してそこでZIPファイル作成や依存関係の解決を行うのがおすすめ。自分は雑に公式のAmazon Linuxは使わずに miniconda3
のイメージを使ってパッケージングしたけど問題なくLambda上でも動いた。
まとめと感想
Lambdaまともに使ったのは実は初めてだけど便利だった。Shared Library依存ののものがあると結構面倒だけど、気合でなんとなかる。 サーバーレスで自分のDockerImageをシュッと動かせるようになるとめっちゃ楽になりそうなのでGCPに期待します