Apache2でwsgiを使ってvenv上のFlaskを動かす

## 始めに

Ubuntu上のApache2でmod_wsgiを利用してFlaskのwebアプリを動かす方法を記述する。
Pythonはpyenvとvenvを利用した仮想環境を使う。

上記の構成でWEBサーバーを構築しようとしたところ、多くのエラーに見舞われ、たくさんの記事を読んだり、一つひとつ設定を試すことになったため、自分が利用した情報とわかったことをまとめた。

### 前提条件
Apacheの設定、Flaskのプログラム作成やpyenvとvenvによる環境構築は終わっているものとする。
`python app.py`などでローカルサーバーを動かせる状態にあればよい。

### 筆者の環境
OS : Ubuntu 22.04 (VPS上)
Pyhton : 3.11.6 (pyenv+venv)

<details><summary>ディレクトリ構成(抜粋)</summary>

```
root/
  ├ etc/
    └ apache2/
      ├ sites-available/
        └ example.com.conf
        └ example.com-le-ssl.conf
      └ sites-enabled/
        └ example.com.conf
        └ example.com-le-ssl.conf
  └ var/
    └ www/
      └ example.com/
        ├ html/
        └ wsgi-bin/
          ├ app.py
          ├ index.wsgi
          └ venv/
            ├ bin/
            └ lib/
              └ pyhton3.11/
                └ site-packages/
                  └ mod_wsgi/
                    └ server/
                      └ mod_wsgi-py311.cpython-311-x86_64-linux-gnu.so
```
</details>

## mod_wsgiのインストール

Apacheでwsgiを使うためのモジュールmod_wsgiをインストールする。
今回はデーモンモードを使う。(違いについては[この記事](https://blog.amedama.jp/entry/2015/08/16/220628)が詳しい。)
本番環境で使用するvenvの仮想環境に入った上で、下記コマンドを実行する。
```shell
pip install mod-wsgi
```

この時点でapache2-dev を apt などでインストールしてないとエラーになる。
(インストール方法:`apt install apache2-dev`)

mod_wsgiをaptよりインストールする方法もあるが、pyenvとvenv を使う場合は`apt install libapache2-mod-wsgi-py3`でインストールすると、ライブラリの読み込み時などにエラーが発生した。
[この記事](https://qiita.com/shigechiyo/items/2b25f60918be6b81581a)によると、aptでのインストール時に使うPythonとアプリを動かすPythonのバージョンが異なるため、エラーになるらしい。


## Apache の設定
`/etc/apache2/sites-available/`あたりにあるVirtualHostの設定ファイルを編集する。
```conf:example.com.conf
<VirtualHost *:80>
	ServerName example.com

	ServerAdmin webmaster@localhost
	DocumentRoot /var/www/example.com/html

	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined

# ここから記入
	LoadModule wsgi_module /var/www/example.com/wsgi-bin/venv/lib/python3.11/site-packages/mod_wsgi/server/mod_wsgi-py311.cpython-311-x86_64-linux-gnu.so
	WSGIDaemonProcess app user=www-data  group=www-data threads=5 python-home=/var/www/example.com/wsgi-bin/venv python-path=/var/www/example.com/wsgi-bin/venv/lib/python3.11/site-packages
	WSGIScriptAlias / /var/www/example.com/wsgi-bin/index.wsgi
	<Directory "/var/www/example.com/wsgi-bin/">
		WSGIProcessGroup app
		WSGIApplicationGroup %{GLOBAL}
		WSGIScriptReloading On
		Order allow,deny
		Allow from all
		#.htaccessの有効化(無くてもよい)
		AllowOverride All
	</Directory>
# ここまで記入

	RewriteEngine on
	RewriteCond %{SERVER_NAME} =example.com
	RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
```
### LoadModule
LoadModuleで、venvの中の`/lib/python3.11/site-packages/mod_wsgi/server/mod_wsgi-py311.cpython-311-x86_64-linux-gnu.so`を指定することで、pipによってインストールしたmod_wsgiを読み込んでいる。

### WSGIDaemonProcess
WSGIDaemonProcessとWSGIProcessGroupの`app`の部分は任意の名前でいいが、同じにしないといけない。
また、VirtualHostなどで複数のWSGIDaemonを動かす場合は、互いに重複しないようにする。

userとgroupはpythonを実行するユーザーとグループの名前にする。(www-dataはApacheなどが利用するもの)

python-homeはvenvのディレクトリを指定する。
`venv/bin/`ではなく`venv/`を指定しないといけないので注意。

python-pathにはライブラリのあるディレクトリを指定する。
ここに書く代わりに、wsgiファイルのsys.pathに書く方法もある。(index.wsgiのコメントアウト部参照)

SSLを使う場合は、同様の内容をexample.com-le-ssl.confなどに書くと思うが、SSLの方にはWSGIDaemonProcessを書いてはいけない。
Let's encryptなどでは自動でコピーされるので、この部分だけ消す必要がある。
また、LoadModuleもSSL用のファイルでは不要である。

### WSGIScriptAlias
cgiで使うAliasと同じ書き方をする。
今回は`example.com`へアクセスすればFlaskに繋がるようにしている。
`WSGIScriptAlias /wsgi/ /var/www/example.com/wsgi-bin/index.wsgi`と書けば、`example.com/wsgi/`にアクセスすることでFlaskへ繋がるようにできる。
詳細は[この記事](https://itkobo-z.jp/archives/3882)が詳しい。


## wsgiファイル
```python:index.wsgi
import sys

sys.path.insert(0, "/var/www/example.com/wsgi-bin")
# sys.path.insert(0, "/var/www/example.com/wsgi-bin/venv/lib/python3.11/site-packages")

from app import app as application
```

sys.path.insertでpythonプログラムが置かれているディレクトリを指定し、importできるようにする。

コメントアウトしている部分は、ライブラリのディレクトリをwsgiファイルで指定する場合である。


## 最後に
これで`systemctl restart apache2.service`をすれば完了である。

mod_wsgiをデーモンモードで動かしているため、Flaskのプログラムを更新したらApache2をリスタートしなくても変更が反映されるという情報を得たが、自分の環境では再現できなかった。


## 参考にさせていただいたサイト

https://it-syoya-engineer.com/django-ubntu-apache-webserver/

https://qiita.com/shigechiyo/items/2b25f60918be6b81581a

https://conta.hatenablog.com/entry/2012/01/16/005312

https://qiita.com/Riskchan/items/e799adedcf683f352873

https://itkobo-z.jp/archives/3882

https://stackoverflow.com/questions/27832777/where-should-wsgipythonpath-point-in-my-virtualenv

https://blog.amedama.jp/entry/2015/08/16/220628

https://ichir.ai/posts/mod_wsgi-daemon-mode/