AWS Lambda、FlaskとOpenCVの環境構築

# はじめに
- AWS LambdaでのOpenCVの環境構築は、大変です。具体的には、デプロイ環境の構築が大変です。
- デプロイ環境は、`AWSCLI`、`Serverless Framework`等色々な物が必要です。
- 今回も、以前作成した顔認識アプリをLambdaへ移植します。ソースコードは、同一です。上記のデプロイに関する設定ファイルが異なる程度です。
- ソース一式は [ここ](https://github.com/maedamikio/public/tree/master/flask_opencv_lambda) です。


# 概要
- Mac
    - 顔認識アプリ
        - Flask
        - OpenCV
        - Python
    - デプロイツール等
        - AWSCLI
        - Docker
        - Homebrew
        - Node.js
        - Serverless Framework
- AWS
    - API Gateway
    - CloudFormation
    - CloudWatch
    - IAM
    - Lambda
    - S3


# Homebrew
- まず、各種ツールインストールのための[Homebrew](https://brew.sh/)を入れます。
- これは、説明割愛でOKですよね?

```shell
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
```


# AWSCLI
- AWSでIAMユーザーを作ります。今回は、`serverless`にしました。
- プログラムによるアクセスを有効にします。
- アクセス許可は、AdministratorAccessにします。
- アクセスキーIDとシークレットアクセスキーを控えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/1b8ceed0-823d-57f1-7bd0-61d86ba56697.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/a0283ff6-3f51-0e30-0df6-6f34b75080e6.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/00818050-5d34-15ad-69e0-b7b25cc439a2.png)

- AWSCLIをインストールして、上記のアクセスキーIDとシークレットアクセスキーを登録します。
- リージョンは、東京（ap-northeast-1）にしました。
- ユーザー一覧を表示で出来るはずです。

```shell
$ brew install awscli

$ aws configure
AWS Access Key ID [None]: xxxxxxxxxxxxxxxxxxxx
AWS Secret Access Key [None]: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Default region name [None]: ap-northeast-1
Default output format [None]:

$ aws iam list-users
```


# Node.js
- Node.jsをインストールします。これは、後述のServerless Frameworkをインストールするためです。
- npmと言う、パッケージ管理システムもインストールされるはずです。

```shell
$ brew install node
```


# Serverless Framework
- [Serverless Framework](https://serverless.com/)は、各種クラウド(AWS、Azure、GCP等)でサーバレス環境構築を簡単にしてくれます。
- Flaskのための、WSGIやrequirements.txtのプラグインも有り、かなり楽が出来ます。
- 正直、これが無いとコントロールパネルポチポチで相当辛いと思う。

## インストール
- npmでserverlessをインストールします。

```shell
$ npm install -g serverless
```

## プロジェクト作成
- python3のプロジェクトを作成します。
- プロジェクト名とフォルダ名は、`flask_opencv`にしました。
- `handler.py`は、参考で作成されます。今回は、顔認識アプリの`main.py`を利用するので、削除します。
- `serverless.yml`は、この後書き換えます。

```shell
$ serverless create --template aws-python3 --name flask_opencv --path flask_opencv
省略
Serverless: Successfully generated boilerplate for template: "aws-python3"
```

## package.jsonの作成
- パッケージ管理用の `package.json`を作成します。

```shell
$ npm init -f
{
  "name": "flask_opencv",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
```

## プラグインのインストール

### serverless-wsgi
- Flask、DjangoやPyramid等のWSGIアプリのためのプラグインです。
- Lambdaでは、LambdaとFlaskの間にこのWSGIプラグインを使うのが人気の様です。
    - HerokuやGoogle App Engineでは、`Gunicorn`を使いました。
- https://github.com/logandk/serverless-wsgi

### serverless-python-requirements
- requirements.txtを元に関連ライブラリをまとめてくれます。
- デプロイ時にDockerを活用する事で、OpenCVの様なライブラリも利用可能にしてくれます。

### serverless-apigw-binary
- API Gatewayで、送受信時にバイナリデータを扱うための設定をしてくれます。
- API Gatewayは、デフォルトでは、テキストでデータを扱うため、画像等の場合は、そのまま扱う様にする必要があります。

### インストール
- 上記3種をインストールします。package.jsonに追加されるはずです。

```shell
$ npm install --save-dev serverless-wsgi serverless-python-requirements serverless-apigw-binary

$ cat package.json
{
  "name": "flask_opencv",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "serverless-apigw-binary": "^0.4.4",
    "serverless-python-requirements": "^5.0.1",
    "serverless-wsgi": "^1.7.3"
  }
}
```

## serverless.yml
- 今回の設定ファイルの中心ファイルです。ここに、デプロイの設定を記載します。
- service: は分かりやすい名前を適当につけてOKです。ただし、`_`など使えない文字が有ります。
- provider: は、AWS、Python3.7、東京リージョン、タイムアウトは30秒など指定します。
- provider: stage: は、デフォルトで `dev` が指定されます。場合に応じて `stg` や `prod` などへ変更して下さい。
- plugins: は、上記でインストールしたプラグインを記載しています。
- functions: main: handler: は、`wsgi_handler.handler`を指定します。つまり、Lambdaは、serverless-wsgiを経由してFlaskへアクセスする事になります。
- functions: main: events: は、httpのすべてのアクセスを通過させています。
- custom: wsgi: app: は、`main.py`の`app`を対象とするようにします。
- custom: pythonRequirements: dockerizePip: は、Dockerを使って、requirements.txtのライブラリをパッケージします。このおかげで、OpenCVを利用できる様になります。
- custom: apigwBinary: types: は、API Gatewayでバイナリデータをテキストデータへ変換しない様にするためのものです。今回は、顔画像の送信は、POSTで`multipart/form-data`を使います。顔画像の受信は、生jpegなのですが、`image/jpeg`ではうまく行かず、`text/html`を追加したらOKでした。このあたりの理由は、調べきれていません。

```shell
$ cat serverless.yml
service: flask-opencv
provider:
  name: aws
  runtime: python3.7
# stage: dev
  region: ap-northeast-1
  timeout: 30
plugins:
  - serverless-apigw-binary
  - serverless-python-requirements
  - serverless-wsgi
functions:
  main:
    handler: wsgi_handler.handler
    events:
      - http: ANY /
      - http: ANY {proxy+}
custom:
  wsgi:
    app: main.app
    packRequirements: false
  pythonRequirements:
    dockerizePip: true
  apigwBinary:
    types:
#     - 'image/jpeg'
      - 'multipart/form-data'
      - 'text/html'
```


# Docker
- あと少しでデプロイ出来ます。
- 上記で、`serverless-python-requirements`のDocker機能を利用してOpenCVのライブラリを利用すると話ました。
- [Install Docker Desktop](https://docs.docker.com/docker-for-mac/install/)を参考にインストールして下さい。
- もしくは、下記でもOKなはずです。

```shell
$ brew install docker
```


# 顔認識アプリ
- 以下で解説したものと同一です。解説は、こちらを参照して下さい。
    - [Heroku、FlaskとOpenCVの環境構築](https://qiita.com/maeda_mikio/items/1a6899e104aac7b138fc)
    - [Google App Engine、FlaskとOpenCVの環境構築](https://qiita.com/maeda_mikio/items/10fe47ba3aaeeacb94ed)


# デプロイ
- いよいよ、デプロイです。
- requirements.txt を読み込んで、Docker Image lambci/lambda:build-python3.7 を利用して `docker run`しています。
- そのあと、CloudFormationを利用して、S3バケットを作成し、ソース、パッケージ等のzipがアップロードされています。
- 引き続き、CloudFormationがIAMロール、API Gateway、CloudWatch、Lambdaを作成します。
- 作成が終わると、エンドポイントが表示されます。

```shell
$ serverless deploy -v
省略
Serverless: Stack update finished...
Service Information
service: flask-opencv
stage: dev
region: ap-northeast-1
stack: flask-opencv-dev
resources: 12
api keys:
  None
endpoints:
  ANY - https://dlhkrgpmfi.execute-api.ap-northeast-1.amazonaws.com/dev
  ANY - https://dlhkrgpmfi.execute-api.ap-northeast-1.amazonaws.com/dev/{proxy+}
functions:
  main: flask-opencv-dev-main
layers:
  None
```

# 動作確認
- 上記のエンドポイントへアクセスします。
- 画像(jpeg)と処理回数(1〜100)を送信し、顔認識の画像を受信します。
- 処理回数は、性能評価用に準備したものです。今回は、10回にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/0b8efe51-586b-e063-6f71-fd599d177858.png)

## 送信画像
- 顔認識に使う画像は、一部界隈ではおなじみの顔画像を使いました。
- Heroku用アプリ、Google App Engine用アプリで利用した物と同じです。

![image.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/68632/424c0b6d-a613-ac99-90e8-48bbb66290d1.jpeg)

## ログの確認
- 今回は、約9秒でした。1回あたり、0.9秒ですね。Google App Engineよりは速く、Herokuよりは遅いって感じか。
- まぁ、時間帯や色々違うし、参考程度にしておきます。

```shell
$ serverless logs -f main
START RequestId: f88920f5-27d1-417f-bada-4bafde912fc2 Version: $LATEST
opencv version: 4.1.1, elapsed second: 9.593120813369751
```

# おわりに
- AWS LambdaでFlaskとOpenCVを使う方法と動作確認をしました。
- 正直、Serverless Frameworkや関連プラグインが無いと辛いんだろうなぁと思いました。しかし、これらのおかげで、コントロールパネルをポチポイせずに簡単な作業となりました。
- あと、エンドポイントに関しては、[Serverless Domain Manager](https://github.com/amplify-education/serverless-domain-manager)を使うと、ドメインを利用出来たり、パスを修正出来そうです。
- 今後は、Azureで同様の事をやってみたいと思います。