問題の概要
Gunicorn + Nginx + Flaskでアプリケーションをデプロイした際、プロジェクトは正常に起動するものの、APIアクセス時にエラーが発生しました。
Traceback (most recent call last):
File "/usr/local/lib/python3.6/dist-packages/gunicorn/workers/sync.py", line 135, in handle
self.handle_request(listener, req, client, addr)
File "/usr/local/lib/python3.6/dist-packages/gunicorn/workers/sync.py", line 176, in handle_request
respiter = self.wsgi(environ, resp.start_response)
TypeError: __call__() takes from 1 to 2 positional arguments but 3 were given
ただし、Flaskの開発用サーバー(runserver)で起動すると問題が発生しませんでした。
原因の特定
Flaskの開発用サーバーで起動した場合、Flaskアプリケーション自体が直接起動されるため、問題なくアクセスが可能です。
一方でGunicornはWSGIアプリケーションを処理するため、Flaskアプリケーションそのものではなく、誤ってflask-scriptのインスタンスが渡されていた可能性があります。
使用していたコード
以下のような構成でFlaskアプリケーションとflask-scriptを使っていました。
from projects import APP, db
from flask_migrate import Migrate, MigrateCommand
from flask_script import Manager
manager = Manager(APP)
migrate = Migrate(app=APP, db=db)
manager.add_command('db', MigrateCommand)
if __name__ == '__main__':
manager.run()
このコードでは、manager.run()がflask-scriptの起動ポイントとなっています。
しかし、GunicornはWSGIインターフェースを通してFlaskアプリケーションとやり取りを行うため、flask-scriptのインスタンスでは対応できません。
解決方法
Gunicornが起動する際には、Flaskアプリケーション自体(APP)を渡す必要があります。
したがって、以下の起動コマンドを使用します。
gunicorn -D -w 3 -t 300 -b 0.0.0.0:5000 manage:APP
ログ設定を追加する場合、以下のように記述します。
gunicorn -D --error-logfile=/logs/gunicorn.log --pid=/logs/gunicorn.pid --access-logfile=/logs/access.log -w 3 -t 300 -b 0.0.0.0:5000 manage:APP
補足:flask-scriptの仕組み
python manage.py runserverを実行すると、flask-scriptにより内部で以下のような処理が行われます。
def run(self, commands=None, default_command=None):
if commands:
self._commands.update(commands)
argv = list(text_type(arg) for arg in sys.argv)
if default_command is not None and len(argv) == 1:
argv.append(default_command)
try:
result = self.handle(argv[0], argv[1:])
except SystemExit as e:
result = e.code
sys.exit(result or 0)
この処理により、flask-scriptはrunserverコマンドを認識し、最終的にFlaskアプリケーションのapp.run()が呼び出されます。
したがって、flask-scriptはあくまでコマンドラインインターフェースとして機能し、Gunicornが利用するWSGIアプリケーションとは異なる点に注意が必要です。