heroku 上で poetry を使った python 環境を作る
docker を使う
heroku のアプリコンテナを使う方式 (Procfile を書く方式) では requirements.txt
からのインストールしか対応していないので、poetry を使う場合は docker を使ったデプロイにする必要がある
docker を使ったデプロイには現時点で 2 種類ある
- heroku を使わずにビルドされた docker イメージを heroku にデプロイする方式 (Container Registry)
- heroku 上で docker イメージをビルドしてデプロイする方式 (heroku.yml を書くやつ)
ここでは heroku.yml を使ったデプロイについて書く
heroku.yml を作る
こちらを参考にリポジトリのルートに heroku.yml
を作る
ミニマムはこれで OK
build:
docker:
web: Dockerfile
run:
web: poetry run uvicorn server.main:app --host 0.0.0.0 --port $PORT
注意点
- docker build のコンテキストパスの設定は変更できず Dockerfile と同じディレクトリと認識される
- 最初は
docker/web/Dockerfile
とかのディレクトリに置きたかったがそれだとダメだった - ここに書いてある https://devcenter.heroku.com/articles/build-docker-images-heroku-yml#known-issues-and-limitations
- 最初は
- web アプリが listen するポート番号は通常の heroku のアプリと同様に
$PORT
から取得する必要がある-
$PORT
はコンテナ起動時に heroku により動的にセットされるので固定ではない
-
poetry でインストールされているはずのライブラリが見つからない問題
こんなエラーで起動に失敗していた
2022-06-25T14:39:57.607068+00:00 heroku[web.1]: Starting process with command `/bin/sh -c poetry\ run\ uvicorn\ server.main:app\ --host\ 0.0.0.0\ --port\ \41985`
2022-06-25T14:39:58.930705+00:00 app[web.1]: Creating virtualenv pr-board-9TtSrW0h-py3.10 in /app/.cache/pypoetry/virtualenvs
2022-06-25T14:39:59.381153+00:00 app[web.1]:
2022-06-25T14:39:59.381183+00:00 app[web.1]: FileNotFoundError
2022-06-25T14:39:59.381184+00:00 app[web.1]:
2022-06-25T14:39:59.381215+00:00 app[web.1]: [Errno 2] No such file or directory: b'/bin/uvicorn'
heroku run bash でコンテナの中で調査
確かに同じエラーが出ていて uvicorn が見つからないことを確認
~ $ poetry run uvicorn
FileNotFoundError
[Errno 2] No such file or directory: b'/bin/uvicorn'
pip list
の結果を見るとインストールしているはずのライブラリが何も入ってない
~ $ poetry run pip list
Package Version
---------- -------
pip 22.0.4
setuptools 62.1.0
wheel 0.37.1
WARNING: You are using pip version 22.0.4; however, version 22.1.2 is available.
You should consider upgrading via the '/app/.cache/pypoetry/virtualenvs/pr-board-9TtSrW0h-py3.10/bin/python -m pip install --upgrade pip' command.
原因
docker ビルド時のユーザーとコンテナ実行時のユーザーが違うことが原因だった
これにより poetry が作成する仮想環境のパスがイメージビルド時とコンテナ実行時で異なる
- docker イメージのビルド時には
/root/.cache/pypoetry/virtualenvs
- コンテナの実行時には
/app/.cache/pypoetry/virtualenvs
※heroku での実行ユーザーはルートユーザーではないことが公式ドキュメントにも書かれている
なぜ実行ユーザーが違うと仮想環境の置き場所が変わるのか?
poetry のデフォルトでは inux 環境の場合、仮想環境の置き場所は ~/.cache/pypoetry
になる
※実装でそうなっていることを念のため確認した
このため root ユーザーの場合は /root
がホームディレクトリになる
コンテナ実行時のユーザーは heroku によって設定される uid のユーザーで、ホームディレクトリは WORKDIR で設定したディレクトリ
※WORKDIR がホームディレクトリになることは公式ドキュメントからは分からなかったが実際の動作からそうなることを確認
poetry で仮想環境のパスを固定する
poetry が仮想環境を配置するディレクトリを固定して実行ユーザーによらず一定になるように設定する
固定先はどこでもいいが /usr/local/.cache/pypoetry
にしておく
# poetry のデフォルトでは実行ユーザーごとに仮想環境のパスが変わるので /usr/local/.cache/pypoetry 配下に固定する
ENV POETRY_CACHE_DIR=/usr/local/.cache/pypoetry
RUN poetry install
なんで cache-dir を固定したの?
先程の問題を解決するだけであれば virtualenvs.path
を固定すればいいので実際それでもいい
cache-dir
は今の所 virtualenvs.path
のデフォルト値のためだけにしか使われていないが、今後 poetry の機能追加などで別の用途でも使われるようになったとするとその時に別の機能で同様の問題が起きる可能性があるので、より根本的な cache-dir
から固定することで対処する
(お気持ち)
poetry の挙動で地味にハマったが、 poetry は依存関係の管理やパッケージのビルド・配布のためのツールであることを考えると、docker イメージのようなイミュータブルな実行環境を構築するためのものではないのでこれは仕方ないかもと思った
開発用の PC に環境を作るのであれば仮想環境は必須だけど、コンテナでの実行環境ではそもそも分離されているのでグローバルな領域にインストールしても問題ないケースがほとんどと思われる
なのでコンテナ内では poetry の virtualenvs.create
を false にして使えば今回のような問題を防げていいのかもしれないと感じた