Raspberry Pi 3 Model B+にNginxとuWSGIを使ってFlaskを動かしてみた

# 背景・目的
Pythonで実装したアプリケーションを本番環境にデプロイする際にuWSGIを用いるのは知っていたが、なぜ使うのかや使い方について知らなかった。そこで、NginxとuWSGIとPython + Flaskを用いてRaspberry Pi 3 Model B+で動かしてみる。

そもそもWSGIとはWeb Server Gateway Interfaceの略である。PEP 3333のAbstructに記載されているようにウェブサーバーとPythonアプリケーションを繋ぐインターフェースのことで、様々なWebサーバ間でWebアプリケーションの移植性を高めるものである。

>This document specifies a proposed standard interface between web servers and Python web applications or frameworks, to promote web application portability across a variety of web servers.

このWSGIに即したWSGIアプリケーションコンテナのuWSGIを用いてPython + Flaskで作成したアプリケーションをデプロイする。

# 実装
実装した順序は以下である。
1. 開発環境でFlaskのアプリケーションを動かす
2. uWSGIでFlaskのアプリケーションを動かす
3. Nginxの設定をしてブラウザからラズパイのIPを叩く
4. uWSGIを自動起動化させる

### 開発環境でFlaskのアプリケーションを動かす
まず、開発環境のディレクトリは以下のようになっている。

```
$ tree
.
├── README.md
├── conf
│   └── uwsgi.ini
├── requirements.txt
└── src
    └── main.py
```

requirements.txtに必要なライブラリを記載している。これを用いて `pip install -r requirements.txt` でライブラリをインストールできる。

```
$ cat requirements.txt 
flask
isort
flake8
ipython
uwsgi
```

src/main.py は以下である。

```
$ cat src/main.py 
from flask import Flask

app = Flask(__name__)

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

if __name__ == '__main__':
    app.run()
```

`python src/main.py` で開発用サーバーが5000番ポートで起動し、アプリケーションが実際に動いていることがわかる。

### uWSGIでFlaskのアプリケーションを動かす

実際にアプリケーションを運用する際には、様々な設定をする必要がある。例えば、ソケットの出力先やプロセス数やスレッド数などが挙げられる。これらの設定をuwsgiコマンドのオプションに連ねて指定するのは面倒なので、以下のuwsgi.iniファイルにオプションをまとめる。

そうすると、`uwsgi --ini uwsgi.ini` でアプリケーションが実際に動いているかを確認することができる。

```
$ cat conf/uwsgi.ini
[uwsgi]
wsgi-file = ../src/main.py
; module = main
callable = app
chmod-socket = 666
socket = /tmp/%n.sock
;http = 0.0.0.0:8080
;daemonize = /var/log/uwsgi/flask/%n.log
;pidfile = /tmp/app.pid
;harakiri = 600
```

### Nginxの設定をしてブラウザからラズパイのIPを叩く
Nginx の80番ポートにHTTPリクエストでアクセスした後、UNIXドメインソケットを利用してuWSGIにプロキシを行う。そのNginx側の設定が以下になる。

```
server {
    listen 80;
    # 80番ポートにアクセスするとFlaskのアプリにリバースプロキシする
    # この方法だとポートを無駄に使わずに済む
    location / {
        include uwsgi_params;
        uwsgi_pass unix:///tmp/uwsgi.sock;
    }
    proxy_set_header  Host                $host;
    proxy_set_header  X-Forwarded-For     $proxy_add_x_forwarded_for;
    proxy_set_header  X-Forwarded-Host    $host;
    proxy_http_version      1.1;
    proxy_connect_timeout 600;
    proxy_read_timeout    600;
    proxy_send_timeout    600;
    send_timeout          600;
    uwsgi_read_timeout    600;
}
```

ちなみに、UNIXドメインソケットとは、同一マシン上で動いているプロセス間通信を行うためのソケットのことである。従って、他のマシンと通信することはできない。一方で、IPとポート番号を頼りにネットワークを介して他マシンと通信するソケットは、INETドメインソケットと呼ばれる。UNIXドメインソケットは同一マシン上のプロセスとしか通信することができないが、INETドメインソケットと比較すると高速である。

### uWSGIを自動起動化させる

uWSGIをdaemon化する方法はいくつか挙げられる。1つ目は、uwsgiコマンドのオプションに`daemonize`のオプションを付けることである。2つ目は、systemctlにuWSGIを登録して自動起動化させる方法である。今回の実装では2つ目を採用した。

- サービス(/etc/systemd/system/app.service) の作成

```
[Unit]
Description=uWSGI instance to serve myapp
After=network.target

[Service]
;User==www-data
Group=www-data
WorkingDirectory=/home/pi/WorkSpace/DeployFlask/conf
ExecStart=/home/pi/.pyenv/shims/uwsgi --ini /home/pi/WorkSpace/DeployFlask/conf/uwsgi.ini

[Install]
WantedBy=multi-user.target
```

- サービスの起動

```
$ sudo systemctl start app
```

- サービスの自動起動の設定

```
$ sudo systemctl enable app
```

- サービスの状況把握

```
$ sudo systemctl status app
● app.service - uWSGI instance to serve myapp
   Loaded: loaded (/etc/systemd/system/app.service; enabled; vendor preset: enabled)
   Active: active (running) since Tue 2020-05-12 18:22:25 JST; 7min ago
 Main PID: 2615 (uwsgi)
    Tasks: 1 (limit: 2200)
   Memory: 10.9M
   CGroup: /system.slice/app.service
           └─2615 /home/pi/.pyenv/versions/DeployFlask/bin/uwsgi --ini /home/pi/WorkSpace/DeployFlask/conf/uwsgi.ini
```

# 詰まったこと・課題
- uwsgi.iniファイルに _daemonize = /var/log/uwsgi/flask/%n.log_ を記述していたので、一定時間後にサーバーが502を返す自体が生じた。uwsgiをdaemon化させるために取った1つ目の方法だったが、その記述が悪さをしていたので削除した。

# 参考
#### Deploy
- [PEP 3333 -- Python Web Server Gateway Interface v1.0.1](https://www.python.org/dev/peps/pep-3333/)
- [Web Server Gateway Interface](https://ja.wikipedia.org/wiki/Web_Server_Gateway_Interface)
- [uWSGI](https://flask.palletsprojects.com/en/1.1.x/deploying/uwsgi/)
- [DeployFlask](https://github.com/dilmnqvovpnmlib/DeployFlask)


#### 記事
- [uWSGI入門](https://www.python.ambitious-engineer.com/archives/1959#uWSGI-6)
- [systemdからuWSGIの起動後にタイムアウトエラーが何度も出てしまったのでメモ](https://wwld.jp/2019/11/17/uwsgi-process-timeout.html)

#### Socket通信
- [調べなきゃ寝れない！と調べたら余計に寝れなくなったソケットの話]()