Djangoの開発環境をDockerに乗せてPipenvでパッケージ管理しようとしたら詰まって結局やめた話
- # Python
- # Pipenv
- # Docker
友人と企画したサービスの開発で Django の開発環境を Docker
コンテナに乗せて作っていたのだけど, パッケージ管理を Pipenv でしようとして色々詰まって結局やめたのでメモしておく.
Pipenv を使おうとした理由
チーム開発だったので環境間の差分を吸収したかったのと, 個人的にキャッチアップしたかったという理由で開発環境は Docker
の上に構築した.
パッケージ管理は,
- Pipfile がパッケージ取得時に自動更新される
pip install <package>
だとrequirements.txt
の更新漏れがあるし, そもそもめんどい
- scripts に独自コマンドを定義できる
- 開発用パッケージ(autopep8, mypy とか)を分けて管理できる
辺りが便利なので, Pipenv でやろうかなって思ったら結構詰まった
まずは詰まった点
コンテナ内だから, わざわざ仮想化する必要もないかなって感じで最初は
FROM python:3.7.6-stretch
WORKDIR /django_app
COPY ./Pipfile /django_app/Pipfile
COPY ./Pipfile.lock /django_app/Pipfile.lock
RUN python -m pip install --upgrade pip
RUN pip install pipenv
RUN pipenv install --dev --system
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
こんな感じで, パッケージを /usr/local/bin/python
に直接インストールする形で構築した.
で, まあ問題なく動くんだけど, 問題点が 2 つあって
1 つ目は, scripts が仮想環境を見に行ってしまう点.
$ docker exec django_app pipenv run which python
/django_app/.venv/bin/python
見ての通り.
2 つ目が, pipenv install <package>
において, システムのパッケージが更新されないこと
—system オプションは, あくまで本番環境用のものなので, やっぱ開発環境での利用には向かないっぽい
と言った感じで, Pipenv を使ってる理由がほぼ潰れてしまった.
てことで, コンテナ内で仮想環境を作ることに
Pipenv 自体が仮想環境と密に結合しているパッケージマネージャなので, 単純にコンテナ内のランタイムも仮想環境を使うほうが良さそう
と言っても面倒な点が多くてまたまた詰まる
1. 仮想環境がマウントで壊れる
シンプルに思いつくのは,
version: "3.7"
services:
django_app:
...
volumes:
- ./backend/django_api_server:/django_app
- /django_app/.venv
environment:
PIPENV_VENV_IN_PROJECT: "true"
...
FROM python:3.7.6-stretch
WORKDIR /django_app
COPY ./Pipfile /django_app/Pipfile
COPY ./Pipfile.lock /django_app/Pipfile.lock
RUN python -m pip install --upgrade pip
RUN pip install pipenv
RUN pipenv install --dev
CMD ["/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 の違いを試してみた
...
ENTRYPOINT [ "./entrypoint.sh" ]
CMD ["/django_app/.venv/bin/python", "manage.py", "runserver", "0.0.0.0:8000"]
#!/usr/bin/env bash
set -e
cmd="$@"
# 仮想環境構築
pipenv install --dev --ignore-pipfile # Pipfile ではなく, Pipfile.lock から
source /django_app/.venv/bin/activate
# Django Dev Server の起動
python manage.py makemigrations && python manage.py migrate
exec $cmd # cmd := /django_app/.venv/bin/python manage.py runserver 0.0.0.0:8000
これで, 仮想環境の python を実行環境として開発サーバーを起動できるようになった
2. コンテナ内でコマンド実行時に毎回仮想環境を呼ぶ必要がある
コンテナ内で作業するときは,
$ docker exec -it django_app bash
をしているのだけど, パッケージが仮想環境にインストールされているので,
$ docker exec -it django_app bash
root@xxx:/django_app# python manage.py check
Traceback (most recent call last):
File "manage.py", line 8, in main
from django.core.management import execute_from_command_line
ModuleNotFoundError: 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 exc
ImportError: 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 から自動的に仮想環境をアクティベートするようにする
#!/bin/bash
if [ "`which python`" != "/django_app/.venv/bin/python" ]; then
source /django_app/.venv/bin/activate
fi
RUN echo "source /django_app/.bashrc" >> /root/.bashrc
これで,
$ docker exec -it django_app bash
(django_app) root@xxx:/django_app# python manage.py check
System 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 コンテナを使わないときだけ使うかなーという結論になりました