Python製のパッケージをAPIとして使いたいときは、chaliceなりserverlessなりでaws Lambdaを使う方法が早いと思います。しかし、比較的容量の重いOpenCVのようなライブラリを用いて画像処理を行うような要件の場合、aws Lambdaでは何かと融通が効かず不便でした。今回はそのときのことを書いていきたいと思います。
ことのはじまり 🔗
メインサーバで動かしていたPythonによる画像処理がそれなりにサーバ負荷を与えていたので別サーバに移したくて。新しく別のサーバでAPIとして動かせばいいかなみたいな。画像処理とはいってもGPUを使用するようなメソッドを内部で使用していなかったのでLambdaで動けばそれが低コストでいいなっていうのもありました。
いざはじめてみるとpipで管理していたOpenCVパッケージが重く、そのままではLambdaレイヤーとしてzipでアップロードできませんでした。 aws Lambdaはクォータという割り当て量の制限が設けられていて、デプロイパッケージ(.zipファイルアーカイブ)のサイズは50MB、解凍後250MBまでと定められています。OpenCVは依存パッケージであるNumpyも含むとpipでは60MB程あり普通に圧縮してUPができません。
ググると色々とLambdaでOpenCV動かしている記事を見つけましたが、一旦ローカルDocker環境でLambda立ち上げてzipレイヤーをオリジナルで作るか、serverlessの拡張ライブラリのようなものでレイヤーを自動生成するかみたいな方法で、それだったらサーバ立てた方がいいなってなりました。サーバ色々いじれた方が融通が効きやすいと思ったのと、そもそもあまり重くはないといえ画像処理の負荷的にLambdaでは不安。結局サーバ立てないとってなりそうなので。Lambdaサーバってレイヤーに依存パッケージを入れるのが基本のようで、Pythonライブラリを複数使ってあーだこーだしたり、重い処理を行ったりするのは適さないんだなと。
実装したこと 🔗
- Pythonパッケージのアプリ化(API)
- Flask + mod-wsgiライブラリを利用
- aws EC2環境構築
- 必要パッケージイストール
- 依存ライブラリのインストール
- Apache設定
環境 🔗
python 3.7.9
pip 21.2.4
amazon Linux 2
Apache 2.4.48
手順 🔗
1, FlaskでAPIを作成 🔗
APIを1つ用意するだけで事足りたので、今回はFlaskを使ってみることにしました。軽量なフレームワーク(というかライブラリな感覚)で低コストに開発したかったのもあります。
自分の作成したディレクトリ構造は以下の通りです。(主要部のみ抜粋)
root/
├ config/
│ ├ development.json
│ ├ production.json
│ └ staging.json
├ package/
│ └ ... 画像処理Pythonパッケージ(これをAPIにしたかった)
├ src/
│ ├ module/
│ │ └ ... アプリケーションモジュール(コントローラ的な処理)記述用
│ ├ script/
│ │ └ ... ビジネスロジック記述用
│ └ config.py
├ app.py
├ requirements.txt
├ staging.wsgi
└ production.wsgi
主要ファイルごとに内容と詳細を載せていきます。ファイルやディレクトリの命名などなんとなくなので、気になる部分は個人で好きなようにつけてください。
app.py
from flask import Flask
from src.config import config
from src.module import index, execute_xxxxx
app = Flask(__name__)
app.register_blueprint(index.module)
app.register_blueprint(execute_ncode.module,
url_prefix=config('URL_PREFIX'))
if __name__ == '__main__':
app.run()
Flaskのメインファイル。自分の場合は@app.route('/')
などの記述を別ファイルで書いているため、ここではルーティング用のモジュールとconfig(環境変数)のインポートのみを行っています。
Flaskではregister_blueprint
でアプリケーションをモジュール化できます。
Blueprintを使ったアプリケーションのモジュール化 — Flask Documentation 2.0.x
URL_PREFIX
は環境変数として、後述するconfigモジュールで指定した値を引っ張ってきています。/api/v1
的なやつを入れてます。
src / module / index.py
from flask import Blueprint
MODULE_NAME = 'index'
module = Blueprint(MODULE_NAME, __name__)
@module.route('/')
def execute():
return 'This is root.'
とりあえずAPIのURLのルートを叩かれたときに何か表示しておくために書きました。Blueprintを使用するために必要なアプリケーションモジュールになります。書き方がとても単純。
src / module / execute_xxxxx.py
from flask import Blueprint, request, jsonify
from src.script import script_xxxxx
MODULE_NAME = 'execute_xxxxx'
module = Blueprint(MODULE_NAME, __name__)
@module.route(MODULE_NAME, methods=['POST'])
def execute():
try:
response = script_xxxxx.execute(request.form['data'])
return jsonify(response)
except Exception as e:
raise ...
こちらがAPIの実装。POSTリクエストでリクエストボディにdata
を渡したときの記述です。
script_xxxxx
はビジネスロジックを書いているモジュールになります。内部で画像処理Pythonパッケージを呼び出して今回やりたかった処理を記述しています。
Flaskが用意しているrequest
モジュールとjsonify
モジュールを使用することでJSON形式のAPIをこのように作成できます。
src / config.py
import os
import json
def config(key):
env = os.environ.get('FLASK_ENV') or 'development'
root_path = os.environ.get('APP_ROOT_PATH') or os.getcwd()
with open('{}/config/{}.json'.format(root_path, env)) as f:
return json.load(f)[key]
configの呼び出し関数を定義しています。これによってconfig/development.json
の内容にアクセスでき、
from src.config import config
url = config('URL_PREFIX')
のような書き方で環境に応じた変数を取得可能です。
少し長くなってきたので、EC2へのデプロイ手順等の続きは次回にします。
以上ありがとうございました。