【AWS Lambda】Amazon Linux の Docker イメージを使ってデプロイパッケージを作成する

AWS Lambda の実行環境に無いライブラリを利用する場合には、ローカル環境でライブラリのソース群をダウンロードしておいてそれをデプロイパッケージに含めて Lambda にアップロードする必要があります。

Python の例だとデプロイパッケージを作る際に例えば下記のような手順を踏みます。

```sh 
$ virtualenv -p python2.7 venv
$ source venv/bin/activate
$ pip install -r requirements.txt
$ zip deploy_package.zip lambda_function.py # 実行スクリプトを zip にする
$ cd venv/lib/python2.7/site-packages
$ zip -r ../../../../deploy_package.zip * # pip でインストールしたライブラリを zip に追加
```

利用したいライブラリが純粋な Python コードであればこのパッケージは問題無く Lambda 上でも動作するのですが、OS の機能に依存しているようなライブラリの場合、ローカル環境でビルド・インストールされたものを Lambda 上にアップロードしても動きません。
上記の例で言うと `pip install` は Lambda の実行環境と同じ環境で行う必要があります。

# 失敗例
`pycrypto` というライブラリを使う Lambda Function を作って実行します。

```text:requirements.txt
pycrypto==2.6.1
```

```py:lambda_function.py
from Crypto.PublicKey import RSA


def lambda_handler(event, context):
    return {"messagge": "success"}

```
上記のファイルを用意してローカルの OS X 環境でデプロイパッケージを作成します。

```sh 
$ virtualenv -p python2.7 venv
$ source venv/bin/activate
$ pip install -r requirements.txt
$ zip deploy_package.zip lambda_function.py
$ cd venv/lib/python2.7/site-packages
$ zip -r ../../../../deploy_package.zip *
```

作成した `deploy_package.zip` を AWS Lambda にアップロードして実行すると次のようなエラーが出ます。
`Unable to import module 'lambda_function'`

<img src="https://qiita-image-store.s3.amazonaws.com/0/21180/2a452136-8ce3-8922-fdc9-610118d86dbd.png" width="480" alt="Unable to import module 'lambda_function'" />

エラーログ全文を見ると `Unable to import module 'lambda_function': /var/task/Crypto/Util/_counter.so: invalid ELF header` とあります。`pycrypto` が参照するファイルのヘッダ情報が不正であるようです。原因は `pycrypto` をインストールした環境が Lambda の実行環境と異なるところにあります。

# 対策
Amazon Linux の Docker イメージを用いてライブラリのインストールを行うことでこの問題を回避することができます。

先述のファイルと同じ階層にこのような Dockerfile を用意して

```Dockerfile:Dockerfile
FROM amazonlinux:latest

RUN yum -y update && yum -y install gcc python27-devel
RUN cd /tmp && \
    curl https://bootstrap.pypa.io/get-pip.py | python2.7 - && \
    pip install virtualenv
WORKDIR /app
CMD virtualenv -p python2.7 venv-for-deployment && \
    source venv-for-deployment/bin/activate && \
    pip install -r requirements.txt
```

このようにコマンドを実行すると `venv-for-deployment` という名前で、Amazon Linux でビルドされた Python ライブラリコードが作成されます。

```sh 
$ docker build . -t packager
$ docker run --rm -it -v $(PWD):/app packager
```

その後は下記のようにデプロイパッケージの zip を作成して AWS Lambda にアップロードします。

```sh 
$ zip deploy_package.zip lambda_function.py
$ cd venv-for-deployment/lib/python2.7/site-packages
$ zip -r ../../../../deploy_package.zip * .* # dotfile が含まれる場合は ".*" も
```

実行するとライブラリが import 出来て無事 `"success"` が表示されます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/21180/f4e26848-a31c-3127-cd3e-32961567ddf2.png" width="480" alt="success" />

## 自動化
ちょっと叩くコマンド量が多いのでこのような Makefile を作っておくと `make` だけで zip が生成されて便利です。

```Makefile:Makefile
package:
	docker build . -t packager
	docker run --rm -it -v $(PWD):/app packager
	zip deploy_package.zip lambda_function.py
	cd venv-for-deployment/lib/python2.7/site-packages && zip -r ../../../../deploy_package.zip * .*
	echo "Completed. Please upload deploy_package.zip to AWS Lambda"
```

# サンプル
今回用いた Lambda Function のサンプルはこちらのリポジトリに置いています。
https://github.com/morishin/python-lambda-function-test

# 所感
AWS Lambda 便利なのですがちょっと凝ったことをしようとすると泣きそうになりますね。