DjangoアプリケーションをDockerコンテナ上で動かす方法（開発および本番環境）

## 記事の概要

**Django**で作成したWEBアプリケーションを**開発・本番環境**で動作させる際はいくつかの方法があります。
最近では、**Kubernetes**が人気となりアプリケーションをコンテナ化する場面も多いです。

本記事では**開発・本番環境**でどのように**Djangoアプリケーション**をのせた**Dockerコンテナ**を作るかについて記載します。

## 前提条件・環境

本記事で使用する環境は**CentOS7**、**Docker**、**Django2系**です。

**Docker**さえ動けば**Dockerコンテナ**は動くため、OSがWindowsでも問題ありません。



## 開発環境用Dockerコンテナ作成

### 使用するDockerファイル

開発環境用に使用するDockerファイルは以下になります。

このDockerファイルでは**debian:10**をベースにして、**Python**をソースコードからインストールしています。
**公式Python**のDockerイメージもあるためそちらを使っても問題ないのですが、勉強のためにこのような構成になっています。

```dockerfile
FROM debian:10

# Install Python3.7.7

WORKDIR /work
ADD ./Python-3.7.7.tar.xz .
WORKDIR Python-3.7.7
RUN apt-get update && apt-get install -y \
    gcc \
    libbz2-dev \
    libssl-dev \
    libffi-dev \
    libsqlite3-dev \
    make \
    tk-dev \
    zlib1g-dev \
    apache2-dev \
    python3-dev \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*
RUN ./configure --enable-shared && \
    make && \
    make install && \
    make distclean && \
    ./configure && \
    make && \
    make altbininstall

# Install with pip

COPY ./requirements.txt .
RUN pip3 install --upgrade pip setuptools
RUN pip3 install --upgrade wheel
RUN pip3 install -r requirements.txt

# Deploy App

WORKDIR /
ADD ./deployfiles.tar.xz .
WORKDIR /myapp

CMD ["python3", "manage.py", "runserver", "0.0.0.0:80"]

```



### Dockerファイルの説明

```dockerfile
FROM debian:10
```

まず、**Docker**ファイルを記載するときは**ベース**として何を使うか指定します。ここでは**debian:10**を指定しています。

[FROM][6]



```dockerfile
# Install Python3.7.7

WORKDIR /work
ADD ./Python-3.7.7.tar.xz .
WORKDIR Python-3.7.7
```

次に**Python**のインストールです。

**[WORKDIR][7]**はOSのカレントディレクトリを移動させるような構文です。**WORKDIR /work**とすれば**ルートディレクトリ（/）**直下に**work**というディレクトリが作られ（**指定したディレクトリがなければ作られます**）、そこがカレントディレクトリとなります。

**ADD**は**ADD ホストOS上のパス Dockerコンテナ上のパス**とすることで指定したホストOS上にあるファイルをDockerコンテナ上のパスへ**コピー・展開**することができます。
**[ADDはCOPY][5]**と異なり、**圧縮されたファイルを展開**することができます。そのため**tar**や**xz**などのファイルは**ADD**を使って**Dockerコンテナ**上へコピーすると良いと思います。



```dockerfile
RUN apt-get update && apt-get install -y \
    gcc \
    libbz2-dev \
    libssl-dev \
    libffi-dev \
    libsqlite3-dev \
    make \
    tk-dev \
    zlib1g-dev \
    apache2-dev \
    python3-dev \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*
```

**[RUN][3]**はDockerコンテナ上でコマンドを実行することが出来ます。apt-getを使用し、**Python**のインストールに必要な依存パッケージをインストールしています。

**apt-get clean**、**rm -rf /var/lib/apt/lists/\***は不必要なキャッシュやファイルを削除するために実行しています。これを削除することで、作成される**Dockerイメージ**の**容量を小さく**保てます。



```dockerfile
RUN ./configure --enable-shared && \
    make && \
    make install && \
    make distclean && \
    ./configure && \
    make && \
    make altbininstall
```

後は**./configure**、**make**、**make install**です。4行ほど余分なものがありますが、これはエラー対応のためにつけています。

※**何らかの組み合わせの問題**だとは思うのですが、私が実行したときはこのようにしなければなりませんでした。もしかすると不要かもしれません。



```dockerfile
# Install with pip

COPY ./requirements.txt .
RUN pip3 install --upgrade pip setuptools
RUN pip3 install --upgrade wheel
RUN pip3 install -r requirements.txt
```

**Python**のインストールが終わったら、**Python**パッケージのインストールを行います。ここでは**pip**コマンドを使用して**requirements.txt**に記載されているパッケージをインストールします。

**[COPYはADD][5]**と似ていて、**COPY ホストOS上のパス Dockerコンテナ上のパス**とすることでホストOS上にあるファイルをDockerコンテナ上のパスへコピーできます。こちらは文字通りコピーしかしません。

後は**[RUN][3]**で**pip**のアップグレードを行い、**requirements.txt**に基づいて**Python**パッケージをインストールします。



```dockerfile
# Deploy App

WORKDIR /
ADD ./deployfiles.tar.xz .
WORKDIR /myapp

CMD ["python3", "manage.py", "runserver", "0.0.0.0:80"]
```

最後に自分が作ったアプリケーションを**Dockerコンテナへ**加えます。

**deployfiles.tar.xz**は**myapp**というディレクトリを圧縮したもので、**myapp**直下に**manage.py**があります。

**[CMD][4]**は**Dockerコンテナ**が起動するときに実行するコマンドを設定する構文です。これは**Python**のリスト形式のように記載でき、各要素は半角スペースで結合されます。つまり、以下のようなコマンドになります。

```
CMD ["python3", "manage.py", "runserver", "0.0.0.0:80"]
↓
python3 manage.py runserver 0.0.0.0:80
```



### Dockerイメージのビルド

**Dockerイメージ**をビルドするには以下のコマンドを実行します。

```bash
docker build -t test/myapp .
```

**-t**オプションは**Dockerイメージ**へタグを付けるために使用します。タグを付ければ管理がしやすくなります。

コマンドの最後には**Dockerファイル**のパスを入れます。ここでは**カレントディレクトリ（.）**を指定しています。

デフォルトでは**Dockerfile**という名前のファイルを指定されたパスから探すため、上記で作成したファイル名は**Dockerfile**としておきます。

[docker build][8]

作成したイメージを確認するには以下のコマンドを実行します。

```
docker image ls
```



### Dockerコンテナの起動

**Dockerイメージ**から**Dockerコンテナ**を起動するには以下のコマンドを実行します。

```
docker run --name myapp -d -p 80:80 -v /work/db.sqlite3:/myapp/db.sqlite3 test/myapp:latest
```

**--name**オプションは**Dockerコンテナ**へ名前をつけます。これは後でこの**Dockerコンテナ**を停止、起動、削除したくなったりしたときに便利です。名前は一意なものになるため、名前をつけておけば名前を指定するだけで停止、起動、削除が出来ます。名前がない場合はコンテナIDを指定します。

**-d**オプションは**バックグラウンド**でコンテナを実行します。これをつけなかった場合は、**Dockerコンテナ**の標準出力がコンソール上に表示されます。**Ctrl + c**で抜けられます。ほとんどの場合はバックグラウンドで動かすと思うのでつけておきます。

**-p**オプションは**ホストOSのポート**と**Dockerコンテナのポート**を紐付けます。**ホストOSのポート:Dockerコンテナのポート**とします。今回は**Django**のプロセスを**Dockerコンテナの80番ポート**を使用して公開しているため80を指定します。

**-v**オプションは**ホストOSのボリューム（DockerボリュームまたはホストOS上のファイルパス）**と**Dockerコンテナのファイルパス**を紐付けます。コンテナは終了したときにデータを保持しません。そのため、残しておきたいデータは外部に保管する必要があります。**推奨はDockerボリューム**ですが開発環境のため**ホスト上OSのファイルパス**へデータを保存させます。ここでは**db.sqlite3**を**ホストOS**から**Dockerコンテナ**へ紐付けています。

**test/myapp**は元になる**Dockerイメージ**の指定です。**latest**というのはバージョン名で**Dockerイメージ**をビルドしたときに何も指定しなければ**latest**になります。**docker image ls**で出力される**TAG**列を参照することで確認できます。

[docker run][9]

正常に起動されているかどうかを確認するには以下のコマンドを実行します。

```
docker ps
```

**STATUS**列が**UP**になっていれば正常に起動しています。

何も出力されない場合は**-a**をつけて停止中のプロセスも確認します。もし**Dockerコンテナ**のログを確認したい場合は以下のコマンドを実行します。

```
docker logs [コンテナ名またはコンテナID]
```

エラーを確認して対応します。



### Djangoアプリケーションへの接続

**Docker**を起動している**ホストOS**上のIPアドレスの公開したポート番号へアクセスします。



## 本番環境用Dockerコンテナ作成

### 使用するDockerファイル

本番環境用に使用する**Dockerファイル**は以下になります。

開発環境と違い、**python3 manage.py runserver**でプロセスを立ち上げるわけにはいきません。**Apache**などの上で動作させるほうが安定します。

ここではベースとして**httpd:2.4**を使用しています。[**Apache Web サーバ**の**公式Dockerイメージ**][1]です。

**Apache**で**Django**を実行するためには**[mod_wsgi][2]**というモジュールを**Apache**へ組み込む必要があります。そのため**Python**、**[mod_wsgi][2]**をインストールしています。

```dockerfile
FROM httpd:2.4

# Install Python3.7.7

WORKDIR /work
ADD ./Python-3.7.7.tar.xz .
WORKDIR Python-3.7.7
RUN apt-get update && apt-get install -y \
    gcc \
    libbz2-dev \
    libssl-dev \
    libffi-dev \
    libsqlite3-dev \
    make \
    tk-dev \
    zlib1g-dev \
    apache2-dev \
    python3-dev \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*
RUN ./configure --enable-shared && \
    make && \
    make install && \
    make distclean && \
    ./configure && \
    make && \
    make altbininstall

# Install with pip

COPY ./requirements.txt .
RUN pip3 install --upgrade pip setuptools
RUN pip3 install --upgrade wheel
RUN pip3 install -r requirements.txt

# install ModWsgi

WORKDIR /work
ADD ./mod_wsgi-4.7.1.tar.gz .
WORKDIR mod_wsgi-4.7.1
RUN ./configure \
    --with-apxs=/usr/local/apache2/bin/apxs \
    --with-python=/usr/local/bin/python3.7 && \
    make && \
    make install 

# Set Apache

WORKDIR /usr/local/apache2/conf
COPY ./httpd.conf .
COPY ./server.crt .
COPY ./server.key .
COPY ./wsgi.conf ./extra
COPY ./httpd-ssl.conf ./extra
```



### Dockerファイルの説明

```dockerfile
FROM httpd:2.4
```

ベースとして**[httpd:2.4][1]**を指定しています。



```dockerfile
# Install Python3.7.7

WORKDIR /work
ADD ./Python-3.7.7.tar.xz .
WORKDIR Python-3.7.7
RUN apt-get update && apt-get install -y \
    gcc \
    libbz2-dev \
    libssl-dev \
    libffi-dev \
    libsqlite3-dev \
    make \
    tk-dev \
    zlib1g-dev \
    apache2-dev \
    python3-dev \
 && apt-get clean \
 && rm -rf /var/lib/apt/lists/*
RUN ./configure --enable-shared && \
    make && \
    make install && \
    make distclean && \
    ./configure && \
    make && \
    make altbininstall
```

開発環境と同様に**Python**をインストールします。



```dockerfile
# Install with pip

COPY ./requirements.txt .
RUN pip3 install --upgrade pip setuptools
RUN pip3 install --upgrade wheel
RUN pip3 install -r requirements.txt
```

開発環境と同様に**pip**で**requirements.txt**に基づいた**Python**パッケージをインストールします。



```dockerfile
# install ModWsgi

WORKDIR /work
ADD ./mod_wsgi-4.7.1.tar.gz .
WORKDIR mod_wsgi-4.7.1
RUN ./configure \
    --with-apxs=/usr/local/apache2/bin/apxs \
    --with-python=/usr/local/bin/python3.7 && \
    make && \
    make install 
```

**Apache**で**Django**を動作させるために**[mod_wsgi][2]**をインストールします。



```dockerfile
# Set Apache

WORKDIR /usr/local/apache2/conf
COPY ./httpd.conf .
COPY ./server.crt .
COPY ./server.key .
COPY ./wsgi.conf ./extra
COPY ./httpd-ssl.conf ./extra
```

最後に**Apache**の各種**config**ファイル、**証明書関連**ファイルをコピーします。



### Dockerイメージのビルド

開発環境と同様です。

### Dockerボリュームの作成

**[Dockerボリューム][10]**を作成するには以下のコマンドを実行します。

```
docker volume create --name volume-name
```

**--name**で指定した名前で**[Dockerボリューム][10]**が作成されます。このボリュームは**Dockerコンテナ**と紐付けます。

**[Dockerボリューム][10]**の寿命は**Dockerコンテナ**と異なります。**Dockerコンテナ**がなくなっても**[Dockerボリューム][10]**は存在し続けるため、データを永続化できます。

### Dockerコンテナの起動

**Dockerイメージ**から**Dockerコンテナ**を起動するには以下のコマンドを実行します。

```
docker run --name myapp -d -p 443:443 -v volume-name:/myapp test/myapp
```

**-v**オプションだけ開発環境とは異なります。ここでは**ホストOS上のファイルパス**ではなく、**[Dockerボリューム][10]**を指定しています。**volume-name**という名前の**[Dockerボリューム][10]**が**Dockerコンテナ**上の**/myapp**と紐付きます。これで**/myapp**上にあるデータが**[Dockerボリューム][10]**へ保存されます。

### Djangoアプリケーションへの接続

開発環境と同様です。



## 使用したソースファイル

[Python][11]

[mod wsgi][12]



[1]:https://hub.docker.com/_/httpd "DockerHub httpd"
[2]: https://modwsgi.readthedocs.io/en/develop/user-guides/quick-installation-guide.html "mod wsgiインストール"
[3]:https://docs.docker.jp/engine/articles/dockerfile_best-practice.html#run "Dockerファイル RUN"
[4]:https://docs.docker.jp/engine/articles/dockerfile_best-practice.html#cmd "Dockerファイル CMD"
[5]:https://docs.docker.jp/engine/articles/dockerfile_best-practice.html#add-copy "Dockerファイル ADDとCOPY"
[6]:https://docs.docker.jp/engine/articles/dockerfile_best-practice.html#from "Dockerファイル FROM"
[7]:https://docs.docker.jp/engine/articles/dockerfile_best-practice.html#workdir "Dockerファイル WORKDIR"
[8]:https://docs.docker.jp/engine/reference/commandline/build.html "docker build"
[9]:https://docs.docker.jp/engine/reference/commandline/run.html "docker run"
[10]: https://docs.docker.jp/engine/reference/commandline/volume_create.html "docker volume"
[11]:https://www.python.org/downloads/source/ "pythonソースファイル"
[12]:https://github.com/GrahamDumpleton/mod_wsgi/releases "mod wsgiソースファイル"