Flask+Pipenv+Gunicorn+Apacheでwebサーバーを構築する


概要

開発時は、Flaskのbuild in サーバーで事足りましたが、いざ本番環境にdeployするときには、WARNING: This is a development server. Do not use it in a production deployment.とあるように、build in サーバーは使わない方がいいので、python系のフレームワークを本番環境で使うときにはどうしたらいいのかをまとめます。

もともとinstallされているpythonを使うと依存関係でごちゃごちゃするので、pyenvでinstallしたpythonおよびpipでpipenvをinstallし、そのpipenvを使って、パッケージを管理すると比較的わかりやすいです。

  1. pipenvをinstallするためのpackageをinstallする
  2. pyenvをinstallする
  3. pyenvで好みのversionのpythonをinstallする

参考:
pyenvを用いたpython環境構築手順(CentOS7.1)

  1. globalで利用するpipでpipenvをinstallする
  2. pipenvでpackageを管理する(今回の場合は、flaskやgunicornなど)

開発環境

  • CentOS Linux release 8.1.1911
  • pipenv, version 2018.11.26
  • pyenv 1.2.18-7-gae4d4893
  • Server version: Apache/2.4.37 (centos)

WSGI(Web Server Gateway Interface)について

PythonのwebアプリケーションをapacheやNginxといったwebサーバーと併用するときに必要となるInterfaceがWSGIというものです。WSGIは、webサーバーからのリクエストをバックエンドのPython webアプリケーションに飛ばします。そして、リスポンスが、webサーバーを介してクライエントに返されます。また、staticファイルに関しては、webサーバーが直接アクセスような仕組みにすることができます。WSGIの仕様に関しては、PEP 3333でまとまっています。

参考:
PEP 3333を日本語訳しました

Secrets of a WSGI master

Flaskのdeployに関する公式ホームページにはWSGIに関して、mod_wsgi (Apache)を利用した例も挙げられていますが、今回は、Gunicornを使って設定しました。'

pipenvのコマンド例

# 仮想環境のPATH
 pipenv --venv
/home/centos/.local/share/virtualenvs/flask-xxxxxx
# 仮想環境のpythonインタプリタ
pipenv --py
/home/centos/.local/share/virtualenvs/flask-xxxxxx/bin/python

# 仮想環境の生成&必要packageのinstall
pipenv install

# 仮想環境の削除
pipenv --rm

# 仮想環境のactivate
pipenv shell

pipenvを使うことで、pythonの仮想環境を構築することができます。仮想環境の場所は、デフォルトだと$HOME/.local/share/virtualenvs/以下に設定されますが、PIPENV_VENV_IN_PROJECT=true pipenv installとすると、カレントディレクトリ以下に.venvが生成されます。

Flaskの設定

簡易的に次のようなディレクトリ構成だとします。

tree .
.
├── Pipfile
├── Pipfile.lock
.
.
.
├── app.py
└── gunicorn_config.py

開発時には、pipenv run python app.pyでbuild in サーバーが起動することを確認します。

app.py
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'

if __name__ == '__main__':
    app.run(host='0.0.0.0')

Gunicornの設定

gunicornは、コマンドでも指定できますが、次のような設定ファイルを読み込ませることも可能です。特に重要なのは、bindでgunicornの起動portの指定とworkersの数を設定することでしょうか。accesslog = '-'errorlog = '-'はコンソールへの出力を意味します。

gunicorn_config.py
bind = '127.0.0.1:8000'
backlog = 2048

workers = 8
worker_class = 'sync'
worker_connections = 1000
timeout = 30
keepalive = 2

umask = 0
errorlog = '-'
loglevel = 'info'
accesslog = '-'
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'

参考:
gunicorn/examples/example_config.py

サービスの設定

サービスが停止したら、3秒後に再起動する設定を入れています。
ExecStartは、絶対パスで指定します。
gunicornは、-cオプションで、設定ファイルを読み込みます。
app:appは、app.pyのappを指定しています。

[Unit]
Description=Python Flask App
After=network-online.target

[Service]
User=centos
WorkingDirectory=/var/www/html/flask
ExecStart=/var/www/html/flask/.venv/bin/gunicorn -c gunicorn_config.py app:app
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

注意:
今回は、pythonの仮想環境をプロジェクトのディレクトリで管理する設定にしています。

Apacheの設定

proxyPassで、クライエントから/にアクセスが来たら、http://127.0.0.1:8000/にリクエストを飛ばします。ProxyPassReverseを設定することで、Flask App内のリダイレクト処理を適切に行います。

<VirtualHost *:80>
        ServerName your.domain.name.com
        ProxyPass / http://127.0.0.1:8000/
        ProxyPassReverse / http://127.0.0.1:8000/
</VirtualHost>

トラブルシューティング

flaskのurl_forのredirectがhttp://127.0.0.1/8000/hogeのようになってしまう

これは、クライエントからHTTPSで通信する時に発生するエラーで、次のようにrequestが送られていることが原因です。

Browser -----HTTPS----> Reverse proxy -----HTTP----> Flask

例えば、DNSサーバーとしてCloudflareを使えば、flexibleの設定で、クライアントからの通信は、https、Cloudflareからwebサーバーへの通信は、httpで行われます(下記参照)。

ここで、Reverse proxyの役割を果たすapacheのVirtualHostの設定に次のHeaderを追加すればFlask側でうまく処理してくれます。

RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_SCHEME}

X-Forwarded-Proto リクエストヘッダーを使用すると、クライアントがCloudflareへの接続に使用したプロトコル (HTTP または HTTPS) を識別することができます。

参考:
Flask Proxy Setups
Setting X-Forwarded-Proto under Apache 2.4
Flask url_for generating http URL instead of https
How does Cloudflare handle HTTP Request headers?

通信がうまくいかない

firewallやselinuxの設定を確認してみてください。