Flask アプリケーションを Nginx でデプロイする時のメモです。
- Flask: 軽量WAF (Web Application Framework)
- uWSGI: WSGI (Web Server Gateway Interface)
- Nginx: リバースプロキシ (Reverse Proxy)
- Supervisor: プロセス監視
uWSGI
環境は Ubuntu14.04(LTS) ですが, 基本的には CentOS の yum でも提供されているかと思います。詳しくは Installing uWSGI を参照して下さい。
$ sudo pip install flask
# dependent packages
$ sudo apt-get install libxml2-dev libxslt1-dev
$ sudo pip install uwsgi
先に uWSGI で Flask アプリを起動しておきます。 以下の yourapp には Python スクリプト名が入ります。(e.g. yourapp.py)
ログ出力先を /var/log/uwsgi.log としていますが, stdout/stderr も出力したい場合は Flaskで PROPAGATE_EXCEPTIONS=True を設定します。
$ nohup uwsgi -s /tmp/uwsgi.sock \
--module yourapp \
--callable app \
--chmod-socket=666 \
--logto /var/log/uwsgi.log \
--processes 4 &
$ ps aux | grep uwsgi
オプションが多くなる場合, iniファイルにまとめておくと便利です。ちなみに, python の引数は pyargv = –debug True のように指定します。
[uwsgi]
master = true
python-path = /your/project/path
wsgi = yourapp:app
chmod-socket = 666
callable = app
http-socket = :8888
socket = /tmp/uwsgi.sock
pidfile = /tmp/yourapp.pid
logto = /var/log/uwsgi.log
processes = 4
threads = 2
iniファイルからの起動コマンドは以下です。
$ uwsgi --ini uwsgi.ini
また, 停止コマンドは以下で, pidfile の path を指定します。
$ uwsgi --stop /tmp/yourapp.pid
Nginx
Nginx をインストールします。
$ sudo apt-get install nginx
$ nginx -v
nginx version: nginx/1.4.6 (Ubuntu)
設定を変更します。8080 portで公開, 内部では UNIX domain socket で uWSGI に繋ぎます。
設定は Flask の Docs を参考にしました。また, リバースプロキシで Header を書き換えられたくないので, 今回は proxy_set_header を設定しています。
$ sudo vim /etc/nginx/nginx.conf.d/server.conf
server {
listen 8080;
server_name xxxxxxxx;
location / {
try_files $uri @yourapplication;
proxy_set_header Host $host;
}
location @yourapplication {
include uwsgi_params;
uwsgi_pass unix:/tmp/uwsgi.sock;
}
}
ついでに, gzip を有効にします。
$ sudo vim /etc/nginx/nginx.conf
...
gzip on;
gzip_proxied any;
gzip_types text/css text/javascript application/javascript application/x-javascript application/json;
expires 30d;
...
再起動します。
$ sudo service nginx restart
$ sudo service nginx status
デフォルトのlogの場所は以下です。
$ tail -f /var/log/nginx/access.log
$ tail -f /var/log/nginx/error.log
実際に Client のリクエストが Accept-Encoding gzip, deflate の場合, http-response-header で Content-Encoding gzip になっていることを確認できます。
Supervisor
Supervisor でプロセス管理を行います。
$ sudo apt-get install supervisor
service コマンドでデーモンが起動していることを確認します。
$ sudo service supervisor status
* supervisor is running
Supervisor の設定ファイルは /etc/supervisor/supervisord.conf です。
; supervisor config file
[unix_http_server]
file=/var/run/supervisor.sock ; (the path to the socket file)
chmod=0700 ; sockef file mode (default 0700)
[supervisord]
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP)
; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket
; The [include] section can just contain the "files" setting. This
; setting can list multiple files (separated by whitespace or
; newlines). It can also contain wildcards. The filenames are
; interpreted as relative to this file. Included files *cannot*
; include files themselves.
[include]
files = /etc/supervisor/conf.d/*.conf
各プロセスの設定は /etc/supervisor/conf.d 以下に書きます。section values は Configuration File を参照して下さい。
$ vim /etc/supervisor/conf.d/uwsgi.conf
[program:uwsgi]
directory=/your/project/path
command=/usr/local/bin/uwsgi --ini uwsgi/uwsgi.ini
numprocs=1
autostart=true
autorestart=true
user=root
redirect_stderr=true
stdout_logfile=/var/log/supervisor/uwsgi.log
supervisorctl start でプロセスを起動します。
$ sudo supervisorctl start uwsgi
uwsgi: started
$ sudo supervisorctl status
uwsgi RUNNING pid 7790, uptime 0:00:01
試しに kill して, restart されるか確認してみます。
$ sudo killall uwsgi
$ ps aux | grep uwsgi
root 27901 0.0 2.4 98380 25304 ? S 23:34 0:00 /usr/local/bin/uwsgi --ini uwsgi/uwsgi.ini
設定変更時は, supervisorctl reload で再起動します。
Anaconda 環境での uWSGI / Supervisor
Anaconda 環境で uwsgi コマンドを実行すると libpcre.so.1 を共有ライブラリを探せないため, 環境変数 LD_LIBRARY_PATH に /opt/conda/lib を指定します。OS は Ubuntu16.04 です。 (2018-12-01 追記)
$ source activate py27
$ uwsgi
uwsgi: error while loading shared libraries: libpcre.so.1: cannot open shared object file: No such file or directory
$ export LD_LIBRARY_PATH=/opt/conda/lib
$ uwsgi --version
2.0.17.1
/etc/supervisor/conf.d 以下のプロセスの設定を以下のように変更します。
[program:uwsgi]
directory=/your/project/path
command=/bin/bash -c "source /opt/conda/bin/activate py27 && /opt/conda/envs/py27/bin/uwsgi --ini uwsgi.ini"
numprocs=1
autostart=true
autorestart=true
user=root
redirect_stderr=true
stdout_logfile=/var/log/supervisor/uwsgi.log
また, Python 実行時に以下のエラーが発生する場合, Ubuntu の /usr/lib/python2.7 の不具合である可能性があります。[6]
ImportError: No module named _sysconfigdata_nd
回避策として /opt/conda/envs/py27/lib/python2.7 へのシンボリックリンクを張ります。
$ sudo ln -s /usr/lib/python2.7/plat-*/_sysconfigdata_nd.py /opt/conda/envs/py27/lib/python2.7
Ubuntu の TimeZone 変更
対話形式で TimeZone を Tokyo に変更します。
$ sudo dpkg-reconfigure tzdata
FlaskでCookieの属性設定
Cookieを扱う際, http と https が混在しているサイトでは http 通信時に Cookie を平文で送信してしまいます。Secure属性を追加することで https 通信時のみ Cookie を送信します。また, HttpOnly属性を追加することで通信時のHTTPヘッダ以外, JavaScript から Cookie にアクセスすることを禁止します。
Flaskでは下記のように設定します。
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True
)
Chrome developer tools > Resources > Cookies で確認できます。
下記で HTTP が HttpOnly属性, Secure が Secure属性 です。
余談ですが uWSGI 起動時に if __name__ == “__main__”: の中で app.run()しないと, unable to load configuration from uwsgi というエラーとなります。
Flask logging
logging.handlers.rotatingfilehandler を用いた Flask アプリケーションのログ管理の例です。
import logging
from logging.handlers import RotatingFileHandler
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/')
def hello():
app.logger.info('request args: {0}'.format(request.args))
return "hello"
if __name__ == '__main__':
formatter = logging.Formatter(
"[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s"
)
handler = RotatingFileHandler('flask.log',
maxBytes=10000,
backupCount=10)
handler.setFormatter(formatter)
handler.setLevel(logging.INFO)
app.logger.addHandler(handler)
app.logger.setLevel(logging.INFO)
app.run()
localhost:5000/?foo=bar にアクセスすると, 以下のようにファイルへのログ出力を確認できます。
$ tail -f flask.log
[2019-09-01 16:06:17,102] {app.py:11} INFO - request args: ImmutableMultiDict([('foo', 'bar')])
[1] Flask-Viewsのmodule化のアイデア
[2] Flask and uWSGI – unable to load app 0 (mountpoint=”) (callable not found or import error)
[3] supervisorctlについて調べてみた
[4] SupervisorでPythonのWebアプリをデーモン化する
[5] PCRE issue when setting up WSGI application
[6] _sysconfigdata_nd.py missing in /usr/lib/python2.7/
[7] Flask logging example