Apache2.4 (Ubuntu) + Python (Flask) + uWSGI で Python アプリを Web サーバで動かす

今回はタイトルの通り、Ubuntu20.04 LTS 上で動く Apache2.4 で uWSGI を用いて Python アプリを Web サーバで動かす手順について説明します。
Apache の記事を見ると、CentOS などの RedHat 系の Linux OS の記事はたくさん出てくるのですが Ubuntu でやっているような記事はあまり見当たらず、さらに WSGI の導入に大変苦労したので、記事を執筆しました。

#WSGI とは
WSGI とは、Python アプリと Web サーバをつなぐような役割を持ちます。一応 Flask 単体でも Web サーバ上で動かすことは可能ですが、Development 用と明確に記載されているため WSGI を使用する必要があります。詳しい話は [uWSGI入門](https://www.python.ambitious-engineer.com/archives/1959) や [WSGI を使う場合と使わない場合](https://qiita.com/hoto17296/items/e4e9d9b5cdd711271020) などの記事を読むと良いでしょう。
有名どころでは mod_wsgi や uWSGI などがありますが、mod_wsgi の開発自体が2016年頃に終了しているせいなのか、自分の設定が悪いのかよくわかりませんが mod_wsgi の導入が上手くいきませんでした。そこで、uWSGI を導入しました。

#環境・バージョン等

```shell
$ cat /etc/issue
Ubuntu 20.04.3 LTS \n \l
$ apache2 -v
Server version: Apache/2.4.41 (Ubuntu)
Server built:   2021-09-28T11:00:45
$ python --version
Python 3.8.10
```

#導入方法
導入方法を説明します。
##uWSGI と Flask をインストール
今回は Flask と uWSGI を用いて行います。pip でインストールしましょう。

```shell
$ sudo pip install uwsgi
$ sudo pip install flask
```

##動かす Python プログラムを作成
今回は run.py というプログラムを作成。

```python:run.py
from flask import Flask, jsonify, request
app = Flask(__name__) 

@app.route('/')
def hello():
    text = "Hello World!"
    return jsonify(message = text)

if __name__ == '__main__':
    app.run()
```
これをターミナルで

```shell
$ python3 run.py
```

を実行しましょう。ブラウザで http://localhost:5000/ にアクセスし、画面上に
>{"message":"Hello World!"} 

と表示されることを確認してください。もしくは、`curl` コマンドを用いて実行しても構いません。ターミナルをもう一つ開いて `curl` コマンド

```shell
$ curl http://localhost:5000/
```

と実行して
>{"message":"Hello World!"} 

というレスポンスが帰ってくるかを試し、Python ファイルがきちんと動いていることを確認してください。

きちんと動いていれば、Ctrl + C などのコマンドで Python プログラムを終了させてください。動かなかった場合は、スペルミスなどを注意してやり直してください。

##uWSGIの実行（コマンドで）
以下のようなコマンドを実行してください。`module` は Python ファイルの名前（今回は `run`）、`callable` は Flask のアプリの名前を指定します（今回は `app`）。仮想環境（virtualenv など）を使用している場合は、仮想環境のある場所を指定してください（ `--virtualenv=/home/env/` などを付ける）。

```shell
$ uwsgi --http=0.0.0.0:8930 --module=run  --callable=app 
```

これで、先ほどのようにブラウザまたは curl コマンドを用いて
>{"message":"Hello World!"} 

と表示されているかどうかを確かめてください。

##uWSGIの実行（ini ファイルで）
後述するデーモン化（コマンドを入力しなくても Python プログラムが動いている状態にすること）にも関係してくるところなので、ini ファイルでの起動について説明します。一時的なデバッグなら、先述のコマンドで uWSGI を動かせば良いですが、デーモン化するならこちらでやる方がおすすめです。
以下のような ini ファイルを作成します。

```ini:uwsgi.ini
[uwsgi]
socket = 127.0.0.1:8930
module = run
callable = app
master = True 
chdir = /var/www/html/
; virtualenv = /home/api_env/env/
; logto = /var/log/uwsgi/uwsgi.log
```

今回は `virtualenv` や `logto` をコメントアウトにしていますが、必要に応じてこれらの設定を変えてください（後述するデーモン化する際はコンソールに uWSGI のログが出力されないのでコメントアウトを外し、`$ mkdir /var/log/uwsgi/` でディレクトリを作成、そこにログを収集していくのがいいかもしれません）。
また、`chdir` も ini ファイルの存在するディレクトリに変更してください。
先ほどと同様に、uWSGI を起動します。

```
$ uwsgi --ini uwsgi.ini
```

##Web サーバとの連携
localhost 環境ではこのままでOKなのですが、これを Apache などを通して独自ドメインなどで公開する際は Apache の設定が必要です。
まずは、Apache の設定で、ポートフォワードと uWSGI のモジュールを有効化します。/etc/apache2/apache2.conf で以下の2行のコメントアウトを外します。そもそも記述されていない場合は最終行など、適当な場所に書き足してください。

```/etc/apache2/apache2.conf
LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so
LoadModule proxy_uwsgi_module /usr/lib/apache2/modules/mod_proxy_uwsgi.so
```

http://example.com で公開したい場合、/etc/apache2/sites-available/example.com.conf を編集してパスを通す必要があります。

```conf:/etc/apache2/sites-available/example.com.conf
ProxyPass / uwsgi://127.0.0.1:8930/
ProxyPassReverse / uwsgi://127.0.0.1:8930/
```

http://example.com/ のディレクトリで実行する場合は上記の設定で良いのですが、http://example.com/api/ などのディレクトリで実行する際は、 ProxyPass や ProxyPassReverse を

```conf:/etc/apache2/sites-available/example.com.conf
ProxyPass /api/ uwsgi://127.0.0.1:8930/
ProxyPassReverse /api/ uwsgi://127.0.0.1:8930/
```

としてください。うまく行かない場合は、/etc/apache2/apache2.conf に

```conf:/etc/apache2/sites-available/example.com.conf
ProxyPass / uwsgi://127.0.0.1:8930/
ProxyPassReverse / uwsgi://127.0.0.1:8930/
```

を記述してください。

これらの設定後、再度 uWSGI を実行しながら http://example.com/ にブラウザでアクセスしたり `curl` コマンドで
>{"message":"Hello World!"} 

のレスポンスがあることを確認してください。

#500 Internal Error の場合
500 エラーが発生している場合は、`/var/log/uwsgi/uwsgi.log` のログを見て修正してください。

#デーモン化
Ubuntu でのデーモン化について、[systemdでプロセス自動再起動](https://tex2e.github.io/blog/linux/systemd-restart-config) や [Systemd Restart = alwaysは受け入れられません] (https://www.it-swarm-ja.com/ja/systemd/systemd-restart-always%E3%81%AF%E5%8F%97%E3%81%91%E5%85%A5%E3%82%8C%E3%82%89%E3%82%8C%E3%81%BE%E3%81%9B%E3%82%93/962499729/) などの記事を参考にして行いました。

##手順
/etc/systemd/system/ に uwsgi.service ファイルを作成し、以下の内容で書き込みます。

```service:/etc/systemd/system/uwsgi.service
[Unit]
Description = uWSGI
After = syslog.target

[Service]
WorkingDirectory = /var/www/html/
ExecStart = /usr/local/bin/uwsgi --ini /var/www/html/uwsgi.ini
# ここは各自による（後述）
Restart = on-failure
# Restart = always
RestartSec = 3
KillSignal = SIGQUIT
Type = notify
StandardError = syslog
NotifyAccess = all

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

`ExecStart = /usr/local/bin/uwsgi` については（仮想環境を実装している場合は、仮想環境をオンにした状態で）`$ which uwsgi` のコマンドを実行し、表示されたディレクトリを指定してください（私は /usr/local/bin/uwsgi でした）。

また、先述のようにログの出力の指定をしてもいいかもしれません。

`[Service]` の `WorkingDirectory` はご自身の Python ファイルが格納されている場所にしてください。
`Restart` は、`always` にしてしまうと Python プログラムが途中で切れてしまうために `on-failure` としました。
また Restart 間隔がデフォルトのまま（実行間のデフォルトの遅延は100ミリ秒 = 0.1秒）であると、Restart する回数が多すぎると私の環境では怒られたので、`RestartSec = 3` にすることによって間隔を広げています。

これで、デーモンのリロードおよび Apache のリロードを行います。

```shell
$ sudo systemctl daemon-reload
$ sudo systemctl restart apache2
```

デーモンを起動させる時は

```bash
$ sudo systemctl start uwsgi
```

などを実行し、逆にデーモン化を止める場合は

```bash
$ sudo systemctl stop uwsgi
```

などを実行してください。
#おわりに
以上の手順で uWSGI を用いて Python プログラムを Web サーバ上で実行することができます。
できない、間違っている場合などがありましたらご指摘お願いします。