AWS Lambda で、動作環境にない Linux 共有ライブラリに依存するコードを動かす

「Amazon Linux 標準でインストールされていない Linux Shared Library に依存する Lambda Function の動かし方、パッケージの作り方」について書きます

（ AWS Lambda では「依存する Python ライブラリをパッケージ化」してデプロイできますが、動作環境に標準でインストールされていない Linux 共有ライブラリに依存する場合はどうするか明確でない気がしたため書きました ）



# 概要
たとえば画像処理するケースで、画像からQRコードを検出してデコード（URL抽出）結果を返す Lambda Function を考えたとき、Python3 では zbar という 「Linux 共有ライブラリに依存したライブラリ」が選択肢になります．

<img src="https://qiita-image-store.s3.amazonaws.com/0/35755/31132e76-594a-699a-ee1a-d234690a338f.png" width=100px>

- zbar http://zbar.sourceforge.net/download.html
- pyzbar https://pypi.org/project/pyzbar/
- Pillow https://pillow.readthedocs.io/en/5.1.x/index.html

Lambda Function は serverless で実行環境を改変できないため（シンプルな Amazon Linux 上で動作する）、zbar のようなプリインストールされていないパッケージに依存するコードはそのままでは動きません

依存する Python ライブラリ群はパッケージ化してデプロイする方法がドキュメントに明記 [^1] されていますが、Linux 共有ライブラリに依存する場合はどうしたらよいのでしょうか



# 対処

Python ライブラリと同様に、共有ライブラリ群も含めてパッケージ化します
パッケージ化する手順を一通り示します


## lambda 関数の定義
まず lambda 関数を定義します
どのようなツールにするかによって変わりますが、ここでは API Gateway とつないで REST API として動作させます（すでに記事がたくさんあるので設定方法は割愛しますが、次の記事などが参考になりました）
https://qiita.com/Hironsan/items/de5dcd42e1c6f6a9b5c7

```Terminal
関数名　DecodeQR
言語　Python 3.6
ロール　LambdaBasicExecutable
```

![lambda-design-3.png](https://qiita-image-store.s3.amazonaws.com/0/35755/3f72fd45-42c3-67e2-d68f-dccc1519c433.png)


## ビルド環境構築（Amazon Linux）
パッケージを作るために、実際にコードが動作する環境（Amazon Linux）と同じ状態を用意します [^2]
docker コンテナもありお好みでよいのですが、今回は無難に EC2インスタンスでつくります

```Terminal
AMI ID : amzn-ami-hvm-2017.03.1.20170812-x86_64-gp2 (ami-4af5022c)
instance type : t2.micro
```

## Python3 環境の構築
ここでは Python3.6 で実装するため、Python3 の環境を構築します．virtualenv など慣れたやり方で環境をつくってください（pyenv + virtualenv を例示します）

```Terminal
$ sudo yum install -y git gcc zlib zlib-devel openssl openssl-devel

$ git clone https://github.com/yyuu/pyenv.git ~/.pyenv
$ export PYENV_ROOT="$HOME/.pyenv"
$ export PATH="$PYENV_ROOT/bin:$PATH"
$ eval "$(pyenv init -)"
$ exec $SHELL -l
$ pyenv install 3.6.5

$ git clone https://github.com/yyuu/pyenv-virtualenv.git ~/.pyenv/plugins/pyenv-virtualenv
$ eval "$(pyenv virtualenv-init -)"
```

開発する lambda function のディレクトリを作成し、そのディレクトリ以下に python3.6 環境を構築します

```Terminal
$ mkdir lambda-DecodeQR && cd lambda-DecodeQR

$ pyenv virtualenv 3.6.5 lambda-DecodeQR
$ source activate lambda-DecodeQR
$ pyenv local lambda-DecodeQR
$ pyenv versions
  system
  3.6.5
  3.6.5/envs/lambda-DecodeQR
* lambda-DecodeQR (set by PYENV_VERSION environment variable)
```

## lambda 関数の作成
Python3環境下で必要な python ライブラリ群をinstall し、lambda function のコードを書きます
永続的なコードであれば、ディレクトリ以下まとめて git 管理しておきます

```Terminal
$ cd ~/lambda-DecodeQR
$ pip install --upgrade pip
$ pip install Pillow
$ pip install pyzbar
$ pip freeze > requirements.txt # 環境復旧用
```

```DecodeQR.py

from io import BytesIO
from PIL import Image
from pyzbar import pyzbar 
import base64

def lambda_handler(event, context):
    
    bytes_base64 = event['image_data_base64'].encode('utf-8')
    bytes_rawdata = base64.b64decode(bytes_base64)
    response = {}

    # QR コード読取
    bytes_io = BytesIO(bytes_rawdata)
    pillow_object = Image.open(bytes_io)
    data = pyzbar.decode(pillow_object)

    if len(data) > 0:
        response = { "decoded_result": data[0][0].decode('utf-8','ignore') }

    return response
```


## 依存ライブラリのインストール
ここが重要です

上記作成した python コードは zbar というLinux 共有ライブラリに依存するため、zbar ライブラリを実行環境の Amazon Linux 上に展開しなければ動作できません．ここでインストール先指定でパッケージインストールし、ライブラリ同梱の lambda 関数パッケージを作成します [^3]

### zbar の取得と make 
依存ライブラリがみつからないなど、ビルド時にエラー発生する場合には適宜対処します [^4]

```Terminal
$ sudo yum install -y ImageMagick-devel.x86_64 ImageMagick
$ cd && mkdir zbar && cd zbar
$ wget https://jaist.dl.sourceforge.net/project/zbar/zbar/0.10/zbar-0.10.tar.bz2
$ tar -jxvf zbar-0.10.tar.bz2

$ cd zbar-0.10
$ ./configure --prefix=~/lambda-DecodeQR CPPFLAGS=-I/usr/include --with-libiconv-prefix=/usr/include --without-gtk --without-qt --disable-video 

please verify that the detected configuration matches your expectations:
------------------------------------------------------------------------
X                 --with-x=yes
pthreads          --enable-pthread=yes
v4l               --enable-video=no
        => zbarcam video scanner will *NOT* be built
jpeg              --with-jpeg=yes
Magick++          --with-imagemagick=yes
Python            --with-python=yes
GTK+              --with-gtk=no
        => the GTK+ widget will *NOT* be built
Qt4               --with-qt=no
        => the Qt4 widget will *NOT* be built

$ make 
$ make install
```

## パッケージング
lambda 関数を作成していたディレクトリは次のような状態になっていると思います

![DecodeQR-sources-2.png](https://qiita-image-store.s3.amazonaws.com/0/35755/d6376a16-2b81-fb50-02ef-24320b4a5b41.png)


依存する python ライブラリ群、zbar ライブラリ群、`DecodeQR.py` を、すべてパッケージファイル `DecodeQR.zip` へ梱包します

```Terminal
$ cd $VIRTUAL_ENV/lib/python3.6/site-packages
$ zip -r9 ~/lambda-DecodeQR/DecodeQR.zip *
$ cd ~/lambda-DecodeQR/
$ zip -gr DecodeQR.zip DecodeQR.py share lib64 lib include bin
```

これでライブラリ同梱パッケージができました




## パッケージのデプロイ
zip ファイルを直接アップロードするか、S3 経由などで lambda 関数パッケージをデプロイします




## 動作確認

### Lambda コンソール上からテスト登録して実行
Base64 エンコードされたテキストを含むJSONリクエストを受け付けるように書いてあるので、その形式にあわせたリクエストパラメータを書き、テスト実行します（画像データの base64 エンコード処理は、linux コマンドの `base64 hoge.png` などを使います）

```python
{
  "image_data_base64": "/9j/4AAQSkZJRgABA....."
}
```

処理成功とでて、結果が出力されます

![Lambda_test_result2.png](https://qiita-image-store.s3.amazonaws.com/0/35755/4ca319fb-8709-ab08-056c-3a8a68b31018.png)

期待通り、渡したQRコード画像が示す URL を得られることが確認できます

```
{
  "decoded_result": "http://hogefuga.com/decoded_url"
}
```


### curl でリクエスト送信して動作確認
API Gateway と lambda が正しく設定できていれば、REST API 経由でレスポンスが正しく得られることが確認できます

```Terminal
$ curl -X POST -H "Content-Type: application/json" -d '{"image_data_base64":"/9j/4AAQSkZJRgABA....."}' --proxy hoge "https://fuga.execute-api.ap-northeast-1.amazonaws.com/prod/moge"
{
  "decoded_result": "http://hogefuga.com/decoded_url"
}
```

## トラブルシューティング
Lambda 関数の実行ログは CloudWatch Logs で確認できるため、挙動のおかしい場合には随時確認できます
（上記テスト結果画面の「ログ」リンク先が CloudWatch Logs 画面になっています）

### Unable to find zbar
zbar ライブラリが正しくインストールできていないか、同梱できていない可能性があります
インストールログを見直すか、zip に同梱されていることを確認してください（`zipinfo`などで中身を確認します）

```
Unable to import module 'DecodeQR': Unable to find zbar shared library
```

### No module named xxx
python ライブラリが正しくインストールできていないか、同梱できていない可能性があります
インストールログを見直すか、zip に同梱されていることを確認してください（`zipinfo`などで中身を確認します）

```
Unable to import module 'DecodeQR': No module named 'pyzbar'
```


## まとめ
Linux shared library を同梱したlambda パッケージを作成し、デプロイして動作させることができました

パッケージ作成にちょっと手間がかかりますが、デプロイ後の運用コストは非常に低い（メンテナンス不要かつ安価）ので、独立した機能はサーバレスにしておきたいですね



## 参考資料

[^1]: [デプロイパッケージの作成 (Python)](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html)
[^2]: [Lambda 実行環境と利用できるライブラリ](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/current-supported-versions.html)
[^3]: [Using Packages and Native nodejs Modules in AWS Lambda](https://aws.amazon.com/jp/blogs/compute/nodejs-packages-in-lambda/)
[^4]: [さくらインターネットでZBar bar code readerを使う](http://pulltab.info/2017/02/-zbar-bar-code-reader.html)