Python
AWS
scikit-learn
pandas
lambda

AWS Lambdaでpandasとscikit-learn使おうとしてハマった話 (/tmp からのモジュールの読み込み)

More than 1 year has passed since last update.

機械学習モデルを利用したAPIを作成しようと思い、

Lambdaでpandasscikit-learnを使おうとしてみたら思いのほかハマった。


今回のハマりポイント


  • lambdaのデプロイパッケージ容量制限(ZIPで50MB)

  • Amazon Linuxの環境依存

  • pythonでのモジュールのインポート


lambdaのデプロイパッケージ容量制限(ZIPで50MB)

まず問題になったのはデプロイパッケージ容量だ。

pandasscikit-learnと依存ライブラリを含めると、zipで圧縮しても60MBほどあって、制限を超えてしまう...

そこで、/tmp フォルダ を使う

lambdaではユーザーが使用できる容量512MBの/tmpが割り当てられている。

つまり、デプロイパッケージに入りきらないライブラリを

実行時にs3からダウンロードして/tmpに展開してパスを通した後インポートすればいい。

今回やりたいのはコレだけだけど、付随する問題がいろいろ出てきて死んだ。


Amazon Linuxの環境依存

まず、容量制限に引っかからないpandasと依存ライブラリを含めたデプロイパッケージ(30MBほど)を作成し動作確認してみた。

なんかエラー出る...

Unable to import module 'main': Missing required dependencies ['numpy']

numpyはパッケージに含めているのに、、なんで?

調べてみるとこんな記事が見つかった。

Amazon Linuxと同じオプションでビルドされたPythonを使って安全にLambda functionをデプロイしよう

つまりlambdaで正常に動作させるためには、

lambda実行環境である Amazon Linux でデプロイパッケージを作成する必要がある、ということ。

めんどいかよ

docker環境、以前ec2で使ってたのがあるけど、

アクセスキー紛失してアクセスできなくなったんだよ(え?)

インスタンス作り直すのめんどいなー、と思ってggってたら、

すばらしい人がすでにAmazon Linuxでビルドしたパッケージを公開してくれてた。

https://github.com/chennavarri/aws-lambda-pandas-sample

https://github.com/ryansb/sklearn-build-lambda

...だめでした。

pythonのバージョンがそれぞれ違った。

しかたないのでec2でAmazon Linux環境作ってパッケージ作った。

これを使うとlambda環境まで再現できる。

https://github.com/lambci/docker-lambda


pythonでのモジュールのインポート

ようやくデプロイパッケージができたのでlambdaで実行してみる。

pandasはデプロイパッケージに含めて、sklearn/tmp以下に展開することにした。

コードは単純で、

s3からsklearnのzipファイルを/tmpにダウンロードし、展開するというもの。


main.py

import boto3

import os
import sys
import zipfile
import pandas as pd
from pprint import pprint

s3 = boto3.resource('s3')
bucket = s3.Bucket('data')

def main():
load_sklearn()
pprint(os.listdir("/tmp/site-packages/"))

import sklearn

return "done"

def load_sklearn():
bucket.download_file("sklearn-packages.zip", '/tmp/sklearn-packages.zip')

zip_ref = zipfile.ZipFile('/tmp/sklearn-packages.zip', 'r')
zip_ref.extractall('/tmp')
zip_ref.close()

os.remove("/tmp/sklearn-packages.zip")

def handle(event, context):
return main()


実行結果 (抜粋)

['bin',

'scipy-1.1.0.dist-info',
'sklearn',
'scipy',
'scikit_learn-0.19.1.dist-info']
No module named 'sklearn': ModuleNotFoundError

sklearnはちゃんとダウンロードされて展開されているのに、

モジュールが見つからないというエラー...

どうやらlambdaの環境変数でPYTHONPATH/tmp/site-packagesを追加していたのが原因らしい。

PYTHONPATHから/tmp/site-packagesを消し、モジュール追加後にモジュールパスを追加するようにすると正常に動作した。


main.py(抜粋)

def main():

load_sklearn()
pprint(os.listdir("/tmp/site-packages/"))

sys.path.append('/tmp/site-packages') # ←これを追加
import sklearn

return "done"


実行結果

"done"

あらかじめ環境変数で指定したモジュールパスについては、実行の初めに探索されて、以降に追加されたモジュールは検知しない、とかなのかな?

import文のタイミングで探索すると思ってたから、最初は詰んだかと思った。

詳しい方はコメントぜひ!


まとめ

cold startだと数秒かかるので実用は厳しい

おすすめを教えてほしい