Open4

heroku 上で poetry を使った python 環境を作る

ChanmoroChanmoro

docker を使う

heroku のアプリコンテナを使う方式 (Procfile を書く方式) では requirements.txt からのインストールしか対応していないので、poetry を使う場合は docker を使ったデプロイにする必要がある

docker を使ったデプロイには現時点で 2 種類ある

  • heroku を使わずにビルドされた docker イメージを heroku にデプロイする方式 (Container Registry​)
  • heroku 上で docker イメージをビルドしてデプロイする方式 (heroku.yml を書くやつ)

https://devcenter.heroku.com/categories/deploying-with-docker

ここでは heroku.yml を使ったデプロイについて書く

heroku.yml を作る

こちらを参考にリポジトリのルートに heroku.yml を作る
https://devcenter.heroku.com/articles/build-docker-images-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 と同じディレクトリと認識される
  • web アプリが listen するポート番号は通常の heroku のアプリと同様に $PORT から取得する必要がある
    • $PORT はコンテナ起動時に heroku により動的にセットされるので固定ではない
ChanmoroChanmoro

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 での実行ユーザーはルートユーザーではないことが公式ドキュメントにも書かれている
https://devcenter.heroku.com/ja/articles/container-registry-and-runtime#run-the-image-as-a-non-root-user

なぜ実行ユーザーが違うと仮想環境の置き場所が変わるのか?

poetry のデフォルトでは inux 環境の場合、仮想環境の置き場所は ~/.cache/pypoetry になる
※実装でそうなっていることを念のため確認した
https://github.com/python-poetry/poetry/blob/1e1585321e90a771af3da33f5154278fe9ee5ca2/src/poetry/locations.py#L18

このため root ユーザーの場合は /root がホームディレクトリになる
コンテナ実行時のユーザーは heroku によって設定される uid のユーザーで、ホームディレクトリは WORKDIR で設定したディレクトリ
※WORKDIR がホームディレクトリになることは公式ドキュメントからは分からなかったが実際の動作からそうなることを確認

ChanmoroChanmoro

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 を固定すればいいので実際それでもいい
https://python-poetry.org/docs/configuration/#virtualenvspath

cache-dir は今の所 virtualenvs.pathのデフォルト値のためだけにしか使われていないが、今後 poetry の機能追加などで別の用途でも使われるようになったとするとその時に別の機能で同様の問題が起きる可能性があるので、より根本的な cache-dir から固定することで対処する

ChanmoroChanmoro

(お気持ち)

poetry の挙動で地味にハマったが、 poetry は依存関係の管理やパッケージのビルド・配布のためのツールであることを考えると、docker イメージのようなイミュータブルな実行環境を構築するためのものではないのでこれは仕方ないかもと思った

開発用の PC に環境を作るのであれば仮想環境は必須だけど、コンテナでの実行環境ではそもそも分離されているのでグローバルな領域にインストールしても問題ないケースがほとんどと思われる
なのでコンテナ内では poetry の virtualenvs.create を false にして使えば今回のような問題を防げていいのかもしれないと感じた