ゼロからDjango環境構築(Windows10 WSL、Docker)

Windows 10でDockerコンテナ内にDjango環境を構築します。
経緯としては、WSLのAnaconda環境でDjangoをインストールしてマイグレーションファイルを作ろうとしたところ、以下のようなエラーが出たためです。

$ python manage.py makemigrations
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?

詳しくは分かりませんが、AnacondaとDjangoは相性が悪いみたいです...
なので、vnen(Docker使用)環境で作り直そうと思います。
また、Dockerコンテナに構築しておくことで、ホスト環境を汚すことがなくなり、違う環境(Mac OSとか)にも全く同じ環境を展開することが出来るようになります。
さらに今回インストールするVS Codeエディタのリモート拡張機能を用いれば、開発対象のリソースがコンテナ内にあったとしてもローカルで開発するかのように作業が行えるようになります。

構築前環境

Windows 10 Pro Version1903
WSL Ubuntu 16.0.4

1.Docker Desktop for Windowsのダウンロードとインストール

https://store.docker.com/editions/community/docker-ce-desktop-windows

登録していない場合は、Sign Up → Get Docker
 

2.Ubuntu 18.0.4のインストール

インストールする前にWSL2をデフォルトにするよう設定を変えます。
WS2の要件はx64 システムの場合、バージョン 1903 以降、ビルド 18362 以上です。


WSL2でのUbuntu環境を作るために、以下のコマンドを実行します。

wsl --set-default-version 2

これでWSL2がデフォルトに設定されたので、Microsoft Storeから今回hUbuntu18.0.4 LTSをインストールします。
その後、再度以下のコードを実行してWSLのバージョンを確認します。

PS C:\Users\alice> wsl --list --verbose
  NAME                   STATE           VERSION
* Ubuntu-16.04           Stopped         1
  Ubuntu-18.04           Running         2
  docker-desktop-data    Running         2
  docker-desktop         Running         2

ちなみに、WSLとWSL2ではファイルシステムが違うため、データへのアクセス方法が異なります。

WSL ボリュームファイルシステムVolFsNTFS上)
C:\Users\{User名}\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu16.04onWindows_79rhkp1fndgsc\LocalState\rootfs\
WSL2 Ext4ファイルシステム(VHDX内)
C:\Users\{User名}\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu18.04onWindows_79rhkp1fndgsc\LocalState\ext4.vhdx
\\wsl$\Ubuntu-18.04\

WSL2では9Pネットワークプロトコルを利用しているため、Windowsからはネットワーク接続で参照できます。


3.Docker Desktopの設定

1.でインストールしたDocker Desktopを開き、Settings > Resources > WSL INTEGRATION > Ubuntu-18.04を有効化し「Apply & Restart」で適用します。
f:id:zamdin:20201017150605p:plain
Ubuntu 18.0.4を開き、docker --versionでWSL2でDockerが認識されていることを確認します。

$ docker --version
Docker version 19.03.13, build 4484c46d9d

テストとしてhello-worldイメージを実行し、コンテナ一覧を出力してみます。

$ docker image ls --all
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              bf756fb1ae65        9 months ago        13.3kB
$ docker container run hello-world
Hello from Docker!
$ docker container ls --all
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
3d13e334b944        hello-world         "/hello"            About a minute ago   Exited (0) About a minute ago                       trusting_mendel

hello-worldコンテナが起動されていることが確認できましたので、コンテナを削除しておきます。
コンテナを削除するときは、docker container rmの後にコンテナIDを指定します。前頭検索で指定可能です。

$ docker container rm 3d

また、dockerと打つことでDockerで使用できるコマンド一覧が表示されます。
コンテナの仕組みについては以下の記事で詳しく書かれています。
qiita.com


4.Dockerファイルの場所を変更

デフォルトではDocker HubからpullしたイメージファイルはCドライブにダウンロードされていき圧迫されていくため、下記を参考にDockerイメージファイルの場所をDドライブに変更します。
qiita.com


5.Dockerイメージの作成

コンテナ作成の元となるイメージファイルを作成します。
まず、Docker Hubからイメージをダウンロード(pull)します。

コマンド docker pull {イメージ名}:{タグ名}

イメージ名とタグ名は下記Docker Hubページから探します。
https://hub.docker.com/
今回は、ubuntu:l20.10と指定し、ubuntuのバージョン20.10のイメージをダウンロードします。

$ docker pull ubuntu:20.10
$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              20.10               da5958a2de8e        36 hours ago        79.5MB


Dcokerfileは以下のGithubページのコードを流用させて頂きます。
https://github.com/akiyoko/django-book-mysite-sample/blob/master/Dockerfile
mysiteというディレクトリを作成し、その直下に下記コードをDockerfileという名前で保存します。

FROM ubuntu:20.10

ENV PYTHONUNBUFFERED 1
ENV PYTHONIOENCODING utf-8

ENV HOME /root
ENV DEPLOY_DIR ${HOME}/mysite

RUN apt update \
&& apt install -y sudo \
&& apt -y upgrade

# Set locale
# https://stackoverflow.com/a/28406007
RUN apt install -y locales
RUN sed -i -e "s/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/" /etc/locale.gen \
    && locale-gen
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

# Install Python 3.8
RUN apt install -y wget \
    build-essential \
    zlib1g-dev \
    # https://stackoverflow.com/a/43923402
    libssl-dev \
    # https://stackoverflow.com/a/29862854
    libsqlite3-dev

WORKDIR ${HOME}

RUN wget https://www.python.org/ftp/python/3.8.0/Python-3.8.0.tgz \
    && tar zxf Python-3.8.0.tgz \
    && cd Python-3.8.0 \
    && ./configure --enable-optimizations \
    && make altinstall

# Set alias
RUN update-alternatives --install /usr/local/bin/python3 python /usr/local/bin/python3.8 1
RUN update-alternatives --install /usr/local/bin/pip3 pip3 /usr/local/bin/pip3.8 1
RUN pip3 install -U pip

# Install other requisites
RUN apt install -y vim

RUN mkdir -p ${DEPLOY_DIR}
WORKDIR ${DEPLOY_DIR}

# Install packages for project
ADD requirements.txt .
RUN pip3 install -r requirements.txt

CMD ["/bin/bash"]

requirements.txt には、Djangoだけインストールするように書いてます。

Django==3.1.2 

その後、dockerイメージをビルドします。

$ docker image build -t mysite:1.0 .

tオプションでイメージ名:タグ名を指定します。
. はDockerfileの保存場所(カレントディレクトリ)を指定しています。

$ docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
mysite              1.0                 39782a5aeaca        42 seconds ago      991MB
ubuntu              20.10               da5958a2de8e        4 days ago          79.5MB

6. コンテナ作成

次のコマンドでDockerイメージからコンテナを作成します。

$ docker container run -it -p 8000:8000 -v /home/py/mysite:/root/mysite --name mysite mysite:1.0
-t コンテナの中に疑似ターミナルを割り当てる
-i 標準入力(STDIN)を取得
-v ボリュームを割り当てる(マウント)ホスト:コンテナ
--name コンテナ名
mysite:1.0 イメージ名:タグ名

今回、Dockerfileの中でCMD ["/bin/bash"]と設定しているため、docker runでの記述は不要です。
itと/bin/bashはセットで使用します。
これでホストディレクリがコンテナのディレクトリにマウントされた状態でコンテナが起動します。

root@cd0c0a5f847d:~/mysite# ls
Dockerfile  requirements.txt  test.php  venv
root@cd0c0a5f847d:~/mysite# pwd
/root/mysite

通常は、マウントしたホストディレクトリをプロジェクトフォルダとして作業していきます。
コンテナのターミナルから抜けたい場合はexitを打ちます。


Dockerの操作

存在しているDocker一覧 $ docker container ls -a
起動しているDocker一覧 $ docker container ls
起動しているDockerの停止 $ docker container stop {コンテナ名}
起動しているDockerの削除 $ docker container rm {コンテナ名}

Python3のインストール

コンテナ内にPythonをインストールすればいいので、必ずホストOSにPythonをインストールする必要もないかもしれませんが、
Pythonのインストール方法もメモします。
執筆時点でPython 3.8がリリースされていたので、3.8をインストールします。

$ sudo apt update
$ sudo apt install -y python3.8
$ python3 -V
Python 3.6.9

3.8をインストールしたのに、3.6.9となってます。
Ubuntu18.0.4のインストールした初期からPython3.6.9もインストールされてたみたいです。

$ ls /usr/bin/ | grep python
python3
python3-jsondiff
python3-jsonpatch
python3-jsonpointer
python3-jsonschema
python3.6
python3.6m
python3.8
python3m

しっかり3.8もインストールされているみたいで、切り替える必要があります。

$ sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.6 1
$ sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.8 2
$ sudo update-alternatives --config python
There are 2 choices for the alternative python (providing /usr/bin/python).

  Selection    Path                Priority   Status
------------------------------------------------------------
* 0            /usr/bin/python3.8   2         auto mode
  1            /usr/bin/python3.6   1         manual mode
  2            /usr/bin/python3.8   2         manual mode

Press <enter> to keep the current choice[*], or type selection number: 2
$ python -V
Python 3.8.0

update-alternatives --install [symbolic link path] python [real path] number
という風で、pythonのバージョンを登録することで切り替えられるようになります。
pythonと打つと/usr/bin/pythonが呼び出され、そこは/etc/alternatives/pythonを参照します。さらにその参照先を今回は/usr/bin/python3.8にした感じ)
ちなみに、python3と打つと3.6.9が参照されています。

$ python3 -V
Python 3.6.9
$ which python3
/usr/bin/python3

python3はpythonとは元々2系と3系を分けるために存在しているようですが、2系は自分は使わないので別にこれで良いと思います。
パッケージ管理ツールpipのインストールとアップグレードも行います。

$ apt install python3-pip
$ python -m pip install --upgrade pip
$ pip --version
WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip.
Please see https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue.
To avoid this problem you can invoke Python with '-m pip' instead of running pip directly.
pip 20.2.3 from /home/py/.local/lib/python3.8/site-packages/pip (python 3.8)

pipはそのままpipと打つと上記のようにWARNINGが出ます。
この書き方は古いようで、python -m pipという記法が正しいとのことです。

$  python -m pip -V
pip 20.2.3 from /home/py/.local/lib/python3.8/site-packages/pip (python 3.8)
$ python3 -m pip -V
pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6)

これもpython3と打つと、3.6環境のpip(インストールしただけでアップグレードしていないpip)を参照します。

.bash_aliasesファイルにalias pip='python -m pip'と記述しておけば、pipだけで済むので楽です。


venv仮想環境構築

コンテナ自体が仮想環境のように環境構築できるため、別途venv仮想環境も要らないとは思いますが、
一応venvの使い方もメモ。

venvはPythonの仮想環境を簡単に構築する機能で、主にpip(パッケージ管理ツール)でインストールするパッケージをプロジェクトごとに分けたい時に利用します。
Python3.3~標準でインストールされており、基本的に1プロジェクト1venv仮想環境を用意します。
また、Python3.5~は仮想環境を作成する際にはvirtualenvやpyenvではなくvenvが推奨されています。

今回はDjangoプロジェクトとして、mysiteディレクトリを作成し、そこにvenv仮想環境を構築します。

$ mkdir mysite
$ cd mysite
$python3 -m venv venv

これでvenvという名前の仮想環境が作られます。
仮想環境のアクティベート

$ source venv/bin/activate
(venv) $ python -V
Python 3.8.0

venv仮想環境内のPythonバージョンはローカルのPythonバージョンと同じになります。

(venv) $ pip  --version
pip 9.0.1 from /home/{User名}/mysite/venv/lib/python3.8/site-packages (python 3.8)

pipのバージョンもvenv仮想環境を参照しているため9.0.1のままですので、アップグレードします。

(venv) $ pip install --upgrade pip
(venv) $ pip  --version
pip 20.2.3 from /home/{User名}/mysite/venv/lib/python3.8/site-packages/pip (python 3.8)
/* -----codeの行番号----- */