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

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

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

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

## メリット・デメリット
あくまで個人的な考えですが

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

## FlaskでHelloWorld
まずは`pip install Flask`でFlaskをinstall

```python

# インストールした「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で書いておきます。

```python
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つのプロセスが確認できます。
<img width="552" alt="スクリーンショット 2018-12-02 16.30.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/211139/713e434e-336a-d864-fb58-6f3e15d84d69.png">
ワーカーのどちらかを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のワーカーを使ったときに、クライアントのバッファは遅くなります。

参考：

- https://stackoverflow.com/questions/26861761/why-use-gunicorn-with-a-reverse-proxy/34273740
- http://docs.gunicorn.org/en/stable/deploy.html

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)を書いていきます。

```nginx
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の設定](http://docs.gunicorn.org/en/stable/settings.html#accesslog)を行っていればそこに書き込まれます。なければ、/var/logしたのnginxのaccessログに書き込まれているはず。

もっと詳しい設定は[gunicornの公式](http://docs.gunicorn.org/en/stable/deploy.html)にあります。ロードバランサとか設ける場合は`x-forwarded-for`の設定をすべきかと思います。(実務寄り) 

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

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

```python
socket_path = 'unix:/tmp/hello_app-api.sock'
bind = socket_path
```
これをgunicornのconfigファイルに書けばおｋ

# Systemdでサービス化
前述でnginxとの設定ができて無事デプロイメントはできたものの、もし変更を加えたらいちいち、プロセスをしらべて止めて更新して再度起動する必要があります。なので、そこをsystemdでサービス化してしまえば`sudo gunicorn restart`みたいに一発コマンドで再起動できるので便利です。
[supervisor](http://supervisord.org/)もあるのですが、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.conf`に `d (PIDFileのディレクトリ) 0755 root root -`といった内容を、vimなどで追加します。
これで、パスの中にあるgunicornファイルは再起動しても消えないはずです。

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

```
[Unit]
Description=hello-api socket

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

[Install]
WantedBy=sockets.target
```

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

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

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

# 終わりに
一応ここまで書いた記事をdocker-composeでかけるとこまでを[ここで](https://qiita.com/advent-calendar/2018/excite)かけたらなぁと思っている次第であります。

# 参考

- [Flask + Gunicorn + Nginx + Supervisor ちゃんと動くまで](https://qiita.com/Alice1017/items/fb28e1055c6e498d021e)
- [gunicorn+nginx+flaskで動かしてみた](http://kuroneko0208.hatenablog.com/entry/2014/07/02/191202)
- [Ubuntuでsystemdの使い方](https://pc.atsuhiro-me.net/entry/2015/07/11/013314)

# 2018年12月13日追記
アドベントカレンダーを書いてたところ、nginxのdefault.confが `nginx -t`で通らなかったため、
修正しています。

## 変更点

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