PyCharmを使うならvenvはプロジェクトディレクトリの下にあったほうがいいのかもしれない

2019-07-07

なんだこのラノベみたいなタイトルは

最近では DockerCompose を使って開発環境を作る人が多いのではないでしょうか。

私も例に漏れず、今の案件に入って環境を整備させてもらったんですがPyCharm環境で問題が発生しましたので 共有しておこうと思います。

といってもタイトル通りの結論ですが。

備考

この記事は PyCharm 2019.1 (Pro) 評価版にて検証しました。

確認に使うのはこちらのサンプルプロジェクト djample です。

目次

なにがあったのか

Docker によって環境を作ろうとする場合、

  • Dockerfile では すべての環境で必要となるようなソフトウェアのインストール
  • docker-compose.yml はその環境特有のライブラリのインストール
    • 本番のイメージを作るときは docker-compose.prod.yml みたいなものを別途作ってビルドします
    • ちなみに リポジトリ自身は volume として共有しています。

という感じの使い分けを考えています。

例えば開発環境ではテストのために tox が必要, 本番環境では gunicorn が必要など環境によって必要なライブラリは異なることが多いです。 必要のないライブラリを入れるとイメージのサイズが肥大するのでできれば入れたくないですよね。

しかもイメージにしてしまうと requirements を更新しても、 イメージをリビルドするまでバージョンが固定化されてしまいます。

そんな感じの理由で開発環境作成用 docker-compose.yml の(commandの)中で requirements-dev.txt を インストールしており、 これで問題ないかのように見えました。

しかし、そうは問屋がおろしません。

PyCharm(Pro) には Docker あるいは DockerCompose で作成した環境のインタプリタに接続する機能がありますが こいつが動作しないというのが今回の問題です。(私は普段PyCharmを使ってないので気づきませんでした)

試しに、DockerCompose の設定を有効にして

docker-compose-settings.png

PyCharm からDocker環境を起動してみます。

deployed_successfully.png

うまく言ってそうな雰囲気ですが、 Python Console から django を import してみると..

import-error.png

Django は確かにインストールしているはずですが、なぜか ModuleNotFoundError です。

Consoleをスクロールして上の方を見ると何やら少し気になるコマンドが

/usr/local/bin/docker-compose -f /Volumes/Data/Projects/djample/docker-compose.yml -f /Users/righ/Library/Caches/PyCharm2019.1/tmp/docker-compose.override.28.yml run --rm -p 0.0.0.0:50712:50712 app

/Users/righ/Library/Caches/PyCharm2019.1/tmp/docker-compose.override.28.yml の 中身を見るとこんな感じ

version: "3"
services:
  app:
    command:
    - "/home/www/venv/bin/python"
    - "/opt/.pycharm_helpers/pydev/pydevconsole.py"
    - "--mode=server"
    - "--port=50712"
    entrypoint: ""
    environment:
      PYCHARM_MATPLOTLIB_INTERACTIVE: "true"
      PYTHONPATH: "/opt/.pycharm_helpers/pycharm_matplotlib_backend:/opt/.pycharm_helpers/pycharm_display:/opt/.pycharm_helpers/third_party/thriftpy:/opt/.pycharm_helpers/pydev"
      PYDEVD_LOAD_VALUES_ASYNC: "True"
      PYTHONUNBUFFERED: "1"
      PYTHONIOENCODING: "UTF-8"
      PYCHARM_MATPLOTLIB_INDEX: "0"
      PYCHARM_HOSTED: "1"
      PYCHARM_DISPLAY_PORT: "50394"
      IPYTHONENABLE: "True"
    volumes:
    - "/Volumes/Data/Projects/djample:/opt/project:rw"
    - "pycharm_helpers_PY-191.7479.30:/opt/.pycharm_helpers"
    working_dir: "/opt/project"
volumes:
  pycharm_helpers_PY-191.7479.30: {}

なるほどなるほど、 リモートインタプリタをpycharm から利用するために YAML を差し込み コマンドまで上書きしてくれちゃってますね(#^ω^)

PyCharm から docker-compose.yml を起動すると本来の docker-compose.yml によるコンテナ環境と、 上書きされたコンテナ環境の2つが立ち上がり、PyCharmは後者のコンテナを使いリモートインタプリタへの接続を行っているようです。

PyCharm (というか Intellij?) はイメージの時点で requirements のインストールがされていることを期待しているように見えます。

備考

もしかしてDockerで開発環境を作るときはイメージの時点でライブラリのインストールを完結させるのがベストプラクティスなんですかね?

PyCharmのサンプルコードでもそういうふうになってますが、 こうすべきみたいな記述は見つけらませんでした。

対策1 (イメージ化)

これを愚直に対応するなら requirements のインストールが完了した時点のコンテナをイメージ化して利用すれば良さそうです。

一旦 docker-compose up を普通のコンソールから起動します。

~~ 前略 ~~
app_1       | Looking in links: /home/www/djample/requirements/wheelhouse/
app_1       | Requirement already satisfied: Django==2.1.5 in ./venv/lib/python3.6/site-packages (from -r djample/requirements/run.txt (line 2)) (2.1.5)
app_1       | Requirement already satisfied: django-redis==4.10.0 in ./venv/lib/python3.6/site-packages (from -r djample/requirements/run.txt (line 3)) (4.10.0)
app_1       | Requirement already satisfied: redis==3.2.1 in ./venv/lib/python3.6/site-packages (from -r djample/requirements/run.txt (line 4)) (3.2.1)
app_1       | Requirement already satisfied: six==1.11.0 in ./venv/lib/python3.6/site-packages (from -r djample/requirements/run.txt (line 5)) (1.11.0)
app_1       | Requirement already satisfied: psycopg2==2.7.5 in ./venv/lib/python3.6/site-packages (from -r djample/requirements/run.txt (line 6)) (2.7.5)
app_1       | Requirement already satisfied: psycopg2-binary==2.7.5 in ./venv/lib/python3.6/site-packages (from -r djample/requirements/run.txt (line 7)) (2.7.5)
~~ 後略 ~~

requirements のインストールが終わったので、 djample_apps_1 コンテナを イメージに変換します。

$ docker commit djample_app_1 djample_base  # イメージ名は docker-compose.yml の image に合わせる
sha256:baa6c7e91d7950e887d97d133acbd98ba98783eb4f72be9eb805e16331bd58e1

一旦この docker-compose は落とし、再度実行してみます。

import-succeeded.png

今度は成功しました。

これで一件落着!ですが、 もっと簡単な方法がありました。

対策2 (venvを共有する)

表題にもある通り venv をプロジェクトディレクトリに配置します。

警告

配置するというだけでリポジトリに入れるという意味ではないです。

venv は .gitignore で無視してます。

その心は (volumeで共有されている)プロジェクトディレクトリに venv を置くことで ゲスト間でも venv が共有される。ということです。

具体的にやるべきことは以下の2点です。

  • Dockerfile 内で venv を作るのをやめ、docker-compose.yml で作る

    • (私の場合) working_dir を (volumeで共有している)プロジェクトディレクトリに変更する

      interpreter-path-changed.png
  • リモートインタプリタのパスを プロジェクト配下の venv/bin/python に変更する

この状態でもう一度 PyCharm から DockerCompose を起動して、PythonConsole を開くと

import-django-history-with-venv-modules.png

いずれも import django としているだけなのにそれぞれ結果が違うのがわかるでしょうか。 これは venv の作成状況に応じて異なります。 venv の作成と requirements のインストールが済んでから実行すればよいのです。

備考

Python Console タブに移動したときに Error: Console process terminated with error: と言われたら、 venv がまだ作られていない、もしくはインタプリタのパスが間違っていることを疑ってください。

(初回では)このコンテナを起動するときは同じようになるかもしれませんが、 2回目以降は venv/ ディレクトリが存在しているためエラーにはなりません。

終わりに

PyCharm という1つのエディタのためだけに、venvのパスを移動したくない、 という場合は前者のようにイメージを上書きする手順を開発手順に組み込むというのも手でしょう。

今はPyCharm を使ってる方が多く、看過できない問題だったので後者の対応をしてみました。

もうしばらく PyCharm は触りたくないです。

おまけ

試行錯誤してるときに

Failed to deploy 'Compose: docker-compose.yml': Already disposed

みたいに表示されてハマったんですが、自分の場合は pycharm_helpers イメージを途中で削除してしまった場合に発生してたようです。

対応としてはIntellij や PyCharm を再起動しましょう(プロセスまでちゃんと落として)

それでも直らない場合は、 docker-compose の Path が正しいか確認してみましょう。

https://github.com/JetBrains/phpstorm-workshop/issues/32