[App Service] Python環境でrequirements.txt通りにインストールされない不具合の解決方法

## 概要

Azure App Service に Python (Flask) アプリをデプロイしたときに、`RuntimeError: eventlet worker requires eventlet 0.24.1 or higher`のエラーが生じてアプリを起動させることが出来ませんでした。

## 直面した問題

Webページを開くとApplication Errorが表示されました。ログストリームを確認すると、以下のようなエラーとともにアプリが停止していたことが確認されました。

```
 Traceback (most recent call last):
   File "/opt/python/3.10.14/lib/python3.10/site-packages/gunicorn/util.py", line 111, in 
     mod = importlib.import_module('.'.join(components))
   File "/opt/python/3.10.14/lib/python3.10/importlib/__init__.py", line 126, in import_module
     return _bootstrap._gcd_import(name[level:], package, level)
   File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
   File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
   File "<frozen importlib._bootstrap>", line 1006, in _find_and_load_unlocked
   File "<frozen importlib._bootstrap>", line 688, in _load_unlocked
   File "<frozen importlib._bootstrap_external>", line 883, in exec_module
   File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
   File "/opt/python/3.10.14/lib/python3.10/site-packages/gunicorn/workers/geventlet.py", line 
     raise RuntimeError("eventlet worker requires eventlet 0.24.1 or higher")
 RuntimeError: eventlet worker requires eventlet 0.24.1 or higher
```

このアプリでは、WebサーバーとしてGunicornとEventletを使っています。非同期処理を行うため、この2つを併用する必要がありました。ところが、これらのライブラリが適切にインストールされいないために、このようなエラーが生じたと分かります。

しかし、`requirements.txt`は正しく記述できており、単なるバージョンの問題ではありませんでした。

```
（一部抜粋）
eventlet==0.37.0
gunicorn==23.0.0
```

## 解消方法

この問題の背景にあるのは、「必要なライブラリをインストールするPython環境」と「gunicornでアプリを立ち上げるときに使うPython環境」が一致していないことです。

AzureポータルからApp Serviceの管理コンソールを開いて、「設定 > 構成 > 全般設定 > スタートアップコマンド」を以下のように設定します。（設定後5分くらい待つと反映される）

```shell
# もともと設定していたこれを消して、
gunicorn --worker-class eventlet -w 1 startup:app

# こっちに変更する
python3.10 -m pip install --upgrade pip && \
python3.10 -m pip install -r requirements.txt && \
python3.10 -m pip list && \
python3.10 -m gunicorn --worker-class eventlet -w 1 startup:app
```

これにより「`pip install`する対象の環境」と「gunicornを呼び出す環境」は、同じものが明確に指定されます。 ここでは`python3.10`の環境を使っています。`pip list`は無くても大丈夫です。

なお、アプリ起動時にインストールが行われるため、コールドスタートの時間が長くなります。「常時接続(Always on)」をオンにしておくこともオススメします。

## 備考

上記のように特定のPython環境を指定せず、`pip list && gunicorn（以下略）`をスタートアップコマンドとして指定すると、以下のような結果が得られます。Flaskやgunicornの古いバージョンがインストールされており、果たして何を参考にインストールされたものであるかは不明です。

```
 Package              Version
 -------------------- -------
 appsvc-code-profiler 1.0.0
 blinker              1.8.2
 click                8.1.7
 debugpy              1.8.1
 distlib              0.3.8
 filelock             3.15.2
 Flask                3.0.3
 gunicorn             22.0.0
 itsdangerous         2.2.0
 Jinja2               3.1.4
 markdown-it-py       3.0.0
 MarkupSafe           2.1.5
 mdurl                0.1.2
 objprint             0.2.3
 orjson               3.10.1
 packaging            24.1
 pip                  24.0
 platformdirs         4.2.2
 psutil               6.0.0
 Pygments             2.18.0
 rich                 13.7.1
 setuptools           70.0.0
 subprocess32         3.5.4
 virtualenv           20.26.2
 vizplugins           0.1.3
 viztracer            0.15.6
 Werkzeug             3.0.3
 wheel                0.43.0
```

## まとめ

正直なところ、ライブラリのインストールはApp Service側で自動的に上手いこと行なってくれるものだと思いこんでいました。思わぬ落とし穴でした。