友人と企画したサービスの開発で Django の開発環境を Docker
コンテナに乗せて作っていたのだけど, パッケージ管理を Pipenv でしようとして色々詰まって結局やめたのでメモしておく.
Pipenv を使おうとした理由
チーム開発だったので環境間の差分を吸収したかったのと, 個人的にキャッチアップしたかったという理由で開発環境は Docker
の上に構築した.
パッケージ管理は,
- Pipfile がパッケージ取得時に自動更新される
pip install <package>
だとrequirements.txt
の更新漏れがあるし, そもそもめんどい
- scripts に独自コマンドを定義できる
- 開発用パッケージ(autopep8, mypy とか)を分けて管理できる
辺りが便利なので, Pipenv でやろうかなって思ったら結構詰まった
まずは詰まった点
コンテナ内だから, わざわざ仮想化する必要もないかなって感じで最初は
DockerfileFROM python:3.7.6-stretchWORKDIR /django_appCOPY ./Pipfile /django_app/PipfileCOPY ./Pipfile.lock /django_app/Pipfile.lockRUN python -m pip install --upgrade pipRUN pip install pipenvRUN pipenv install --dev --systemCMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
こんな感じで, パッケージを /usr/local/bin/python
に直接インストールする形で構築した.
で, まあ問題なく動くんだけど, 問題点が 2 つあって
1 つ目は, scripts が仮想環境を見に行ってしまう点.
bash$ docker exec django_app pipenv run which python/django_app/.venv/bin/python
見ての通り.
2 つ目が, pipenv install <package>
において, システムのパッケージが更新されないこと
—system オプションは, あくまで本番環境用のものなので, やっぱ開発環境での利用には向かないっぽい
と言った感じで, Pipenv を使ってる理由がほぼ潰れてしまった.
てことで, コンテナ内で仮想環境を作ることに
Pipenv 自体が仮想環境と密に結合しているパッケージマネージャなので, 単純にコンテナ内のランタイムも仮想環境を使うほうが良さそう
と言っても面倒な点が多くてまたまた詰まる
1. 仮想環境がマウントで壊れる
シンプルに思いつくのは,
titleversion: "3.7"services:django_app:...volumes:- ./backend/django_api_server:/django_app- /django_app/.venvenvironment:PIPENV_VENV_IN_PROJECT: "true"...
titleFROM python:3.7.6-stretchWORKDIR /django_appCOPY ./Pipfile /django_app/PipfileCOPY ./Pipfile.lock /django_app/Pipfile.lockRUN python -m pip install --upgrade pipRUN pip install pipenvRUN pipenv install --devCMD ["/django_app/.venv/bin/python", "manage.py", "runserver", "0.0.0.0:8000"]
こんな感じだけど,
- イメージ構築 (Dockerfile の RUN) => コンテナ内に .venv が作成される
- docker-compose.yml で指定したディレクトリのマウントが働く
- ホスト OS の .venv がコンテナにマウントされる
- (マウントで, ホスト OS の .venv でコンテナの .venv が上書きされる)
- コンテナ内の .venv をマウント対象から外す
- 結果として, .venv が空ディレクトリに
- コンテナ構築
て感じで進んでしまうので,
マウント処理が完了している コンテナ構築のタイミングで パッケージインストールなり, 仮想環境を作る必要があるっぽい
てことで, CMD と ENTRYPOINT を併用してコンテナ構築時に作る
参考: [docker] CMD と ENTRYPOINT の違いを試してみた
title...ENTRYPOINT [ "./entrypoint.sh" ]CMD ["/django_app/.venv/bin/python", "manage.py", "runserver", "0.0.0.0:8000"]
title#!/usr/bin/env bashset -ecmd="$@"# 仮想環境構築pipenv install --dev --ignore-pipfile # Pipfile ではなく, Pipfile.lock からsource /django_app/.venv/bin/activate# Django Dev Server の起動python manage.py makemigrations && python manage.py migrateexec $cmd # cmd := /django_app/.venv/bin/python manage.py runserver 0.0.0.0:8000
これで, 仮想環境の python を実行環境として開発サーバーを起動できるようになった
2. コンテナ内でコマンド実行時に毎回仮想環境を呼ぶ必要がある
コンテナ内で作業するときは,
bash$ docker exec -it django_app bash
をしているのだけど, パッケージが仮想環境にインストールされているので,
bash$ docker exec -it django_app bashroot@xxx:/django_app# python manage.py checkTraceback (most recent call last):File "manage.py", line 8, in mainfrom django.core.management import execute_from_command_lineModuleNotFoundError: No module named 'django'The above exception was the direct cause of the following exception:Traceback (most recent call last):File "manage.py", line 19, in <module>main()File "manage.py", line 14, in main) from excImportError: Couldn't import Django. Are you sure it's installed and available on your PYTHONPATH environment variable? Did you forget to activate a virtual environment?
こんな風に, パッケージが見つからずエラーを吐かれてしまう
対策として, .bashrc から自動的に仮想環境をアクティベートするようにする
title#!/bin/bashif [ "`which python`" != "/django_app/.venv/bin/python" ]; thensource /django_app/.venv/bin/activatefi
titleRUN echo "source /django_app/.bashrc" >> /root/.bashrc
これで,
bash$ docker exec -it django_app bash(django_app) root@xxx:/django_app# python manage.py checkSystem check identified no issues (0 silenced).
問題が解消された.
見通しが悪くなったので, d-kimuson/django_docker_pipenv に全体像を貼っておきます
以下追記です。
PyCharm がパッケージを読んでくれない
詳細はよくわからないんだけど, この方法だと PyCharm の docker-compose
のコンテナを実行環境にする機能が, 仮想環境の利用を想定してないらしくて, 上手く扱えなかった
対策のしようもなさそうなので、結局 Pipenv の採用をやめて pip を使うことにした.
- Pipfile がパッケージ取得時に自動更新される
- scripts に独自コマンドを定義できる
- 開発用パッケージ(autopep8, mypy とか)を分けて管理できる
の 3 つを Pipenv
の利点としてあげたけど, この辺はパッケージのインストール/アンインストール用の bash
関数を用意して, 上記と同様に .bashrc
で自動適用することで再現した
詳細は書かない(別記事では書くかも)けど,
インストール・アンインストール用の独自関数で
- パッケージを普通に更新しつつ,
dev
/production
用のrequirements.txt
を更新する
て形にした.
他にも Pipenv
の scripts
に書きたいようなものは .bashrc
に書く形にで落ち着いた
僕の中では, Pipenv は便利だけど, Docker コンテナを使わないときだけ使うかなーという結論になりました