opencv-python を AWS Lambda で実行してみた

この記事はTech KAYAC Advent Calendar 2019の8日目の記事です。

こんにちは!技術部の ひめの です。

クライアントワーク事業部のフロントエンドで2年半 → サーバーサイドエンジニアで1年ちょっと働いています。趣味でラーメンを食べ漁っています。

つい先日の re:Invent 2019 の発表で、RDS + Lambda のコネクションプール周りやコールドスタート問題の軽減の話もあり、どんどん AWS Lambda で解決できる幅が広がっているのを感じます。

Lambda のセッションについてはクラスメソッドさんの Developers.IO にて紹介されてます。

今回はそんな AWS Lambda で、opencv-python を動かすためにやったことついて紹介します。

(実装だけ見たい方は GitHub をごらんください)

まって、その選択大丈夫?

先に述べておきますが、 opencv-python を Lambda で実行するという選択はかなり慎重に考えたほうが良いです。

  • 予算(EC2インスタンスたてたくない、など)
  • リソース(サーバーの管理リソースを割きたくない、など)
  • 技術的要因
  • GPU(CUDA関連の関数など)を利用しない
  • レスポンスに関してある程度の遅延が許される
  • マシンスペックで解決できるチューニングなどを考慮しない

など、ある程度の条件下でないとこの構成はおすすめできません。実行速度のチューニングやGPUを利用する関数を利用するなら、問答無用で GPU つきの AMI を借りて非同期実行させるほうが健全です。

「動かせるんじゃね?」と興味本位でやってみた結果動いた、と念頭において読み進めていただけると幸いです。

opencv-python って何?

opencv-python は、OpenCVという画像処理に特化したライブラリを、Python 上で実行できるようにしたバインディングパッケージです。

https://pypi.org/project/opencv-python/

利用用途はたくさんありますが、画像を用いた機械学習を実施する前処理として画像処理が必要なときに利用するケースが考えられます。

実装方法

サンプルコードをGitHubにアップしていますので、詳細を見たい方はそちらもご確認ください。

opencv-python はOpenCV のビルドされたファイルが必要です。

しかし、これはビルド環境と実行環境を一致させる必要があります。

そのために今回はserverless-opencv-python を利用しました。この Serverless Framework 用のプラグインを利用することで、requirements.txtPipfile をもとにパッケージを zip 形式でまとめてデプロイできます。

また、特定のDockerイメージ上でのパッケージングと、対象パッケージのみ Lambda Layer にアップロードする設定が存在します。

今回はこの設定を利用して、 sls deploy コマンドで

  • lambci/lambda:build-python3.7 のコンテナ上で opencv-python をインストールする
  • OpenCV のコアファイルのダウンロードとビルド
  • パッケージングして Lambda と Lambda Layer にデプロイ

というフローを構築することができました。

手順

1から環境構築する手順について軽く説明します。

ディレクトリを作成して Serverles Framework の初期設定をしましょう。

$ mkdir serverless-opencv
$ cd serverless-opencv
$ npx sls create -t aws-python3 -p sls-opencv
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/.../serverless-opencv/sls-opencv"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.58.0
 -------'

Serverless: Successfully generated boilerplate for template: "aws-python3"

つぎに、 serverless-python-requirements を追加します。

$ cd sls-opencv
$ npx sls plugin install -n serverless-python-requirements

serverless.yml の最後の行に次の設定が追加されているのを確認してください。

sls コマンドからでなくても、 npm /yarn でインストール可能です。その場合は、次の行を serverless.yml に追加してください。

# ...

plugins:
  - serverless-python-requirements

次に、 serverless-python-requirements の設定を serverless.yml に追加します。

custom:
  pythonRequirements:
    layer: true
    dockerizePip: true
    dockerImage: lambci/lambda:build-python3.7
    usePipenv: false
    slim: true
    strip: false
    useDownloadCache: true
    useStaticCache: true

それぞれの設定項目については npm のページを参考ください。

今回ポイントとなる設定は次の3つです。

  • layer:有効化すると AWS Lambda Layer に Python パッケージをデプロイします。
  • dockerizePip:有効化すると、 Docker 上で pip コマンドを実行します。
  • dockerImagedockerizePip 有効時に、利用する Docker イメージを選択します。

設定後、必要な Python パッケージを Pipenv 経由などでディレクトリにダウンロードし、 requirements.txt を作成します。

$ cat requirements.txt
boto3==1.10.16
botocore==1.13.16
docutils==0.15.2
jmespath==0.9.4
numpy==1.17.4
opencv-python==4.1.1.26
python-dateutil==2.8.0
s3transfer==0.2.1
six==1.13.0
urllib3==1.25.7 

Layer との疎通確認用に、opencv-python をインポートし、バージョン情報をレスポンスとして返すように handler.py 内のコードを変更しておきます。

import json
import cv2


def hello(event, context):
    body = {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "input": event,
        "cv2": cv2.__version__
    }

    response = {
        "statusCode": 200,
        "body": json.dumps(body)
    }

    return response

    # Use this code if you don't use the http event with the LAMBDA-PROXY
    # integration
    """
    return {
        "message": "Go Serverless v1.0! Your function executed successfully!",
        "event": event
    }
    """

serverles.yml 内の hello.py をハンドラとしているファンクションを API Gateway で提供します。

# ...

functions:
  hello:
    handler: handler.hello
#    The following are a few example events you can configure
#    NOTE: Please make sure to change your handler code to work with those events
#    Check the event documentation for details
    events:
      - http:
          path: hello
          method: get

# ...

AWS のプロファイル設定や、デプロイに必要な情報を serverless.yml に記述して、デプロイしてみます。

$ sls deploy --profile=xxx

AWS コンソールで Lambda の画面へアクセスし、該当の関数と Layer が表示されているのを確認できればデプロイは成功です。

この状態でローカル環境で API Gateway のエンドポイントに curl してみます。

$ curl https://xxx.api-endpoint.<region>.amazonaws.com/dev/hello

JSON形式で API Gateway のイベントデータと python-opencv のバージョンがリクエストボディで返ってくれば、疎通確認完了です。

注意点

実際に Lambda 上で実行することは可能ですが、.serverless/pythonRequirements.zip を解答したファイルサイズは約200MBになります。これは Layer の容量制限(解凍後で最大250MB)になんとか入っているというサイズ感です。

Web アプリケーションフレームワークとして Flask を検討しようにも入るかどうか...といったところです。 サイズを食っているのは OpenCV のビルドファイルと numpy (計算処理に特化したパッケージ)です。どちらも容量削減するのは結構厳しいです。

今回の方法だとOpenCVのGUIライブラリも入っているため、サイズをもう少し抑えたいのであれば opencv-python-headless の方を使ったほうが Layer サイズを少なくすることができます。

さいごに

今回は AWS Lambda で opencv-python を動作させる方法について紹介しました。

動作自体は可能ですが、Lambda のアーキテクチャと OpenCV の特性を考えると、あまりおすすめできる構成ではありません。日本語の記事どころか他言語の記事もみあたらないのも納得いきます。ご利用は計画的に。

さいごまでお読みいただきありがとうございました!