gunicorn + Flask + nginx + Systemdで動かしてみた


はじめに

python軽量なウェブアプリケーションFlaskとそれを包み込むgunicornをつかって動かすところまでメモした記事です。

Flask

公式:http://flask.pocoo.org/
wikiによると、

プログラミング言語Python用の、
 軽量なウェブアプリケーションフレームワークである。(wiki)

メリット・デメリット

あくまで個人的な考えですが

  • メリット:必要最低限のメソッド(GET, POST, ルーティング等)しかないので、早くて使いやすい → 学習コストが低い
  • デメリット:比較的自由度は高いので、フォルダ構成など、決めることが多い(逆を言い換えるとなんでもできる)

FlaskでHelloWorld

まずはpip install FlaskでFlaskをinstall


# インストールした「flask.py」から「Flask」Classをimport
from flask import Flask

# Flaskクラスをnewしてappに代入
app = Flask(__name__)

# urlとして”/hello”がgetメソッドで呼ばれると定義した関数が実行される
@app.route('/hello')
def hello():
    hello = "Hello world"
    return hello

if __name__ == "__main__":
    #runメソッドでビルトインサーバーが走る
    app.run()

補足:関数外やmain文で定義した変数は「グローバル変数」になるので、mainの中でapp~を使っててもエラーにはならない。

実行すると,
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
がでてくるので、http://127.0.0.1:5000/hello
こうURLをたたくと"Hello World"がでてくるはず(ポートとかなければ...)。
ルーティングに関してはデフォルトがGETになっているのですが、POSTを指定したければ
@app.route('/hello', methods=['POST'])
このようにmethodを指定する必要がある。

あくまで簡易的なビルトインサーバー

公式:http://flask.pocoo.org/
によると、

You can use the builtin server during development, but you should use a full deployment option for production applications.

日本語:あなたは開発の間はFlaskをつかってサーバーをビルトインできるけど、フルデプロイメント機能があるアプリケーションを使うべきだよ

ここでの、「デプロイ」はウェブサーバーの公開だと解釈すればいいかと。

なので、Flask単体でpublicにするアプリケーションにするのはいろいろまずいとのことです...

そこでgunicornの登場

gunicorn

公式:https://gunicorn.org/

Gunicorn ‘Green Unicorn’ is a Python WSGI HTTP Server for UNIX. It’s a pre-fork worker model ported from Ruby’s Unicorn project. The Gunicorn server is broadly compatible with various web frameworks, simply implemented, light on server resources, and fairly speedy.

日本語:GunicornはpythonのWSGI HTTP ServerでRubyのUnicornの派生だよ。Gunicornは様々なフレームワークと比べると、とってもシンプルな実装で、軽量なサーバーリソース、そしてとっても早いんだ。

WSGI HTTP Server:Web Server Gateway Interfaceのこと。
Pythonにおいて、WebサーバとWebアプリケーションを接続するための、標準化されたインタフェース定義
(wiki)

Gunicornの良いところ(個人的)

  • ワーカーを複数設定できる(マルチプロセスみたいな)
    →デーモン化が可能

  • systemdと組み合わせれる

    • サーバー再起動時もちゃんと自動で立ち上げてくれる
    • flaskソース変更してdeployしてもrestartで素早く変更点を反映できる

なのかなって思っています。(そうじゃないって方はコメントください)

gunicornの起動方法

pip install gunicornでgunicornをインストールして、
gunicorn hello:appで起動する。
"hello"はhello.pyのファイル名
"app"はなかで宣言しているFlaskクラスインスタンス

起動するとListening at: http://127.0.0.1:8000 (25737)が出てくるので、
http://127.0.0.1:8000/hello
を叩くと同様な結果がでるはずです。

gunicornのconfig

gunicornはワーカーを複数起動することができるので、そういったものはconfigで書いておきます。

import multiprocessing

# Worker Processes
workers = 2
worker_class = 'sync'

# Logging
logfile = '/var/www/apps/sampleapp/app.log'
loglevel = 'info'
logconfig = None

worker_classはsync, asyncで決めれますし、logの場所も設定できるので便利
なんなら、このコンフィグにのみ指定できる環境変数も設定できます。

configをよみこませつつ起動させる方法は
gunicorn hello:app --config guniconf.py
で起動されます。
もっと設定を見たい方は、http://docs.gunicorn.org/en/stable/settings.html#

起動すると、ps -aux|grep gunicornなんかでプロセス数を確認するとマスター+ワーカー2台の3つのプロセスが確認できます。

ワーカーのどちらかをkillしてみると、別プロセスが自動で生成されるはずです。
ただし、マスターをkillするとどっちも消えます。

Gunicorn+nginx

Although there are many HTTP proxies available, we strongly advise that you use Nginx. If you choose another proxy server you need to make sure that it buffers slow clients when you use default Gunicorn workers. Without this buffering Gunicorn will be easily susceptible to denial-of-service attacks. You can use Hey to check if your proxy is behaving properly.

日本語訳:httpプロキシはたくさんの種類があるけど、我々(gunicorn制作会社)は強く”Nginx”をおすすめします。もし、あなたがほかのプロキシサーバーを必要だと感じ、使うとしたら、デフォルトのGunicornのワーカーを使ったときに、クライアントのバッファは遅くなります。

参考:

Dos攻撃などを考えるとリバースプロキシを使うNginxがいいよってことなのかなと思います。

構成図的には
client ⇔ Nginx ⇔ gunicorn ⇔ Flask
みたいな感じになります。
(https://medium.com/ymedialabs-innovation/deploy-flask-app-with-nginx-using-gunicorn-and-supervisor-d7a93aa07c18)

nginxの設定ファイル

nginxの設定ファイル(/etc/nginx/site-available/defaultやvhost)を書いていきます。

upstream hello_app {
    server unix:/tmp/hello_app-api.sock;
}

server {
    listen 80;
    access_log /var/log/nginx/access.log main;

    root /var/www/hello_project/
    server_name hello.hogehoge.com

    location / {
        try_files $uri @flask;
    }

    location @flask {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_redirect off;

        proxy_pass http://hello_app;
    }
}

こんな感じで設定します。
http://hello.hogehoge.com/
を打つと(80番ポート)、location /でマッチングして、@flaskにマッチングして、来たアクセスをproxy_passで設定されている hello_app というサーバ群(upstreamブロックに書かれている中身)に投げるという流れです。なので、rootである/var/www/hello_project/下にgunicornのプロジェクトをおいておくと、その下でルーティングした/helloが叩くと表示されるわけです。そして、gunicornのconfigでaccess.logの設定を行っていればそこに書き込まれます。なければ、/var/logしたのnginxのaccessログに書き込まれているはず。

もっと詳しい設定はgunicornの公式にあります。ロードバランサとか設ける場合はx-forwarded-forの設定をすべきかと思います。(実務寄り)

また、/tmpしたにsocketを設定してやることで、gunicornとの通信を可能にします。

gunicorn側でもconfigファイルにそのパスをつけてやることで、gunicornがsocketを生成し、nginxでそのsocketを通じてgunicornとの通信を行います。

socket_path = 'unix:/tmp/hello_app-api.sock'
bind = socket_path

これをgunicornのconfigファイルに書けばおk

Systemdでサービス化

前述でnginxとの設定ができて無事デプロイメントはできたものの、もし変更を加えたらいちいち、プロセスをしらべて止めて更新して再度起動する必要があります。なので、そこをsystemdでサービス化してしまえばsudo gunicorn restartみたいに一発コマンドで再起動できるので便利です。
supervisorもあるのですが、3系ではなかった気がします。
(3系にできるのですが、β版をフォークしたような気がします。
参考:http://supervisord.org/upgrading.html

こちらもgunicornの公式に乗っているものを少しいじったくらいです。
http://docs.gunicorn.org/en/stable/deploy.html#systemd

[Unit]
Description=hello-api daemon
Requires=hello-api.socket
After=network.target

[Service]
PIDFile=/run/gunicorn/pid
User=root
Group=root
RuntimeDirectory=gunicorn
WorkingDirectory=/var/www/hello/
ExecStart=/usr/local/bin/gunicorn --pid /run/gunicorn/pid hello:app --config guniconfig.py
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

留意する点としては、

  • gunicornのコマンドはフルパスでかくこと。

  • PIDFileは/run下を設定すると、ubuntuの場合、リブート時に消えてしまうので、消えないように設定する必要があります。

/run下が消えないように、

/etc/tmpfiles.d/gunicorn.confd (PIDFileのディレクトリ) 0755 root root -といった内容を、vimなどで追加します。
これで、パスの中にあるgunicornファイルは再起動しても消えないはずです。

hello-api.socketも下記のように書けばおk

[Unit]
Description=hello-api socket

[Socket]
ListenStream=/tmp/hello-api.sock

[Install]
WantedBy=sockets.target

最後に、systemctl enable ソケット名で起動できればsudo サービス名 startができればおk。

systemctl list-unit-files --type=service|grep gunicorn
などで、
gunicorn.service enabledが確認できればpsなどでgrepして動いていることが確認できると思います。

だめな場合は、sudo systemctl status gunicornなどで状態を調べつつやるしかないんや....!!!

終わりに

一応ここまで書いた記事をdocker-composeでかけるとこまでをここでかけたらなぁと思っている次第であります。

参考

2018年12月13日追記

アドベントカレンダーを書いてたところ、nginxのdefault.confが nginx -tで通らなかったため、
修正しています。

変更点

  • access.logのlstv形式は別途定義しないといけないため、mainに変更
  • try_files $uri @flask; -> try_files $uri @flask;