OpenCVをAWS Lambda + Python + Serverless Frameworkで動かしてみました。
先日、PillowをLambdaで動かす記事を書きましたが、このときと違って、OpenCVはyumでインストールの必要なshared objectに依存しているのでだいぶ面倒でした。
手順概要
- serverless-python-requirements インストール
- Pythonサンプルコードを記述
- requirements.txt と serverless.yml と Dockerfile にOpenCV動作に必要な事項を記述
- あとはデプロイ
OpenCV特有は3のみです。
手順詳細
serverless-python-requirements インストール
AWS Lambda + Python + Serverless FrameworkにPythonのパッケージをインストールする方法は以前の記事に書きました。
これに従って、まずは serverless create
して、serverless-python-requirements
プラグインをインストールします。
$ serverless create --template aws-python3
$ serverless plugin install -n serverless-python-requirements
ServerlessFrameworkのバージョンはv2.18.0でした。
Pythonソースコード
handler.py
の内容です。OpenCVを参照できることを確認できる最小限です。
import cv2
def hello(event, context):
print("Hellow, OpenCV!")
print(cv2.__version__)
requirements.txt
OpenCVのパッケージ名を記述します。この1行のみです。
opencv-python
serverless.yml
serverless.yml
の記載がもっとも面倒でした。成功例を書きます。
service: sample
frameworkVersion: '2'
provider:
name: aws
runtime: python3.8
lambdaHashingVersion: 20201221
region: ap-northeast-1
functions:
hello:
handler: handler.hello
events:
- httpApi: "*"
plugins:
- serverless-python-requirements
custom:
pythonRequirements:
dockerizePip: true
dockerFile: Dockerfile
dockerExtraFiles:
- /usr/lib64/libGL.so.1
- /usr/lib64/libgthread-2.0.so.0
- /usr/lib64/libglib-2.0.so.0
- /usr/lib64/libGLX.so.0
- /usr/lib64/libX11.so.6
- /usr/lib64/libXext.so.6
- /usr/lib64/libGLdispatch.so.0
- /usr/lib64/libxcb.so.1
- /usr/lib64/libXau.so.6
Dockerfile
Dockerfile
を作成します。serverless.yml
からファイル名で参照しています。
FROM lambci/lambda:build-python3.8
RUN yum install -y mesa-libGL
デプロイと実行
デプロイ。
$ serverless deploy -v
デプロイされたLambdaを実行するとCloudWatch Logsに以下が出力されました。
Hellow, OpenCV!
4.5.1
serverless.yml を書くまでの道のり
最初は以下だけで動かそうとしました。
service: sample
frameworkVersion: '2'
provider:
name: aws
runtime: python3.8
lambdaHashingVersion: 20201221
region: ap-northeast-1
functions:
hello:
handler: handler.hello
plugins:
- serverless-python-requirements
これでデプロイしてLambdaを実行すると、CloudWatch Logsに以下のエラーが吐かれました。
[ERROR] Runtime.ImportModuleError: Unable to import module 'handler': libGL.so.1: cannot open shared object file: No such file or directory
libGL.so.1
というファイルが不足しているので、これを追加すればよいのですが、これは環境に依存していそうなので、手元にある同じファイル名をコピーしただけではたぶん動きません。Lambdaの動くAmazon Linux環境でこのファイルを用意する必要があります。
これをするためにserverless.yml
にdockerの記述をします。以下の記述です。
custom:
pythonRequirements:
dockerizePip: true
dockerFile: Dockerfile
dockerExtraFiles:
- ...
Dockerfile
も用意します。
FROM lambci/lambda:build-python3.8
RUN yum install -y mesa-libGL # OpenCVに必要なパッケージをインストール
これを書くだけでserverlessがデプロイ時にDockerを起動して、Lambdaの動くAmazon Linux環境を再現し、その中でyumインストールしてくれます。serverless.yml
のdockerExtraFiles
に記載したファイルを、yumインストール後に抜き出して、Lambdaデプロイイメージに同梱してくれます。
dockerExtraFiles
に書いたリストは、デプロイして実行時のエラーメッセージから1つずつ書き足して、成功するまで繰り返しました。
エラーメッセージにはsoファイル名しか表示されませんので、以下のコマンドでLambdaの動くAmazon Linux環境の中に入ってみて、yum install -y mesa-libGL
してから、soファイルのありかを探しました。
$ docker run -it --rm lambci/lambda:build-python3.8 bash
Amazon Linux上では /usr/lib64/libGL.so.1
は /usr/lib64/libGL.so.1.7.0
へというように、すべてシンボリックリンクになっていますが、serverless.yml
には実態ではなくシンボリックリンクだけ記述すれば動きました。
注意事項
ここに書いているsoファイルのリストはサンプルPythonコードを動かすために最小限のものです。エラーメッセージを見て、足りないsoファイルを追加するというのを繰り返しましたのみです。従ってOpenCVのすべての動作がこれだけで足りてるかどうかはわかりません。
ハマりどころ1
.serverless/requirements/
の中にLambdaのイメージが展開されるのですが、Dockerで構築されるためか、soファイルがroot権限になり、試行錯誤の過程で serverless.yml の変更が権限不足で反映できないというトラブルがありました。原因がわかればroot権限でそのディレクトリを削除することで解決しましたが、それに気が付くまで時間をだいぶ消耗しました。
ハマりどころ2
serverlessが内部でDockerを利用するため、serverless自体をDockerの中で動かしたらデプロイ時にDockerでエラーになりました。Dockerのvolumeも使っていたので、Dockerのsocket共有でもうまくいかず、あきらめました。
ハマりどころ3
最初はLambdaのLayerにOpenCVを入れたかったのですが、LD_LIBRARY_PATH
がLayerには通っていないため、OpenCVを動作させることはできませんでした。