DjangoをDocker Composeでupしよう！

今更ながらDockerの勉強を始めました。
とりあえず[プログラマのためのDocker教科書 第2版](https://www.amazon.co.jp/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AEDocker%E6%95%99%E7%A7%91%E6%9B%B8-%E7%AC%AC2%E7%89%88-%E3%82%A4%E3%83%B3%E3%83%95%E3%83%A9%E3%81%AE%E5%9F%BA%E7%A4%8E%E7%9F%A5%E8%AD%98-%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AB%E3%82%88%E3%82%8B%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89%E3%81%AE%E8%87%AA%E5%8B%95%E5%8C%96-WINGS%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E9%98%BF%E4%BD%90-ebook/dp/B07BHK5KX7/ref=pd_cp_351_1?_encoding=UTF8&pd_rd_i=B07BHK5KX7&pd_rd_r=5faad092-af73-11e8-8c17-d926f8f0be1c&pd_rd_w=Y1zcM&pd_rd_wg=OKCGy&pf_rd_i=desktop-dp-sims&pf_rd_m=AN1VRQENFRJN5&pf_rd_p=0683df07-c43c-4cfd-a379-13ad9a6cfcf0&pf_rd_r=B7BBMAHDYVNHRV949TKW&pf_rd_s=desktop-dp-sims&pf_rd_t=40701&psc=1&refRID=B7BBMAHDYVNHRV949TKW)を読み終えたので力試しのトライです。
今回はnginx + (Gunicorn + Django) + PostgreSQLな構成で作成します。
本記事で使用しているコードは[ここ](https://github.com/kyhei/docker-django)にあるので気になった方は見てみてください。

##開発環境
- macOS High Sierra 10.13.6
- Docker for Mac
    - Engine : 18.06.1-ce
    - Compose : 1.22.0
- Python : 3.7
- Django : 2.1

##0.プロジェクトの雛形用意
今回のディレクトリの構成このようになっています。

```
docker-django
├── docker-compose.yml
├── django
│   ├── Dockerfile
│   ├── Dockerfile.base
│   ├── requirements.txt
│   └── requirements.base.txt
└── nginx
    ├── mime.types
    └── nginx.conf
```
コンテナ毎の設定ファイルをそれぞれのコンテナ名のディレクトリ下に配置しています。
例えばnginx用のコンテナに使用する設定ファイルはnginx/のフォルダに格納されています。

##1.Django+Gunicorn用のベースイメージを作成

はじめにDjangoプロジェクトをゴリゴリ開発するためのベースイメージを作成します。
このイメージの用途はdjangoのプロジェクト作成コマンド(`django-admin startproject`)を実行するときのコンテナに使ったり、後に作成するデプロイ用イメージのベースイメージとして使用します。

ベースイメージのDockerfileはこのようになります。

```dockerfile:Dockerfile.base
FROM python:3.7

ENV APP_PATH /opt/apps

COPY requirements.base.txt $APP_PATH/
RUN pip install --no-cache-dir -r $APP_PATH/requirements.base.txt

WORKDIR $APP_PATH
```

また、requirements.base.txtの内容はこのようになっています。

```requirements.base.txt
Django==2.1.11
gunicorn==19.9.0
psycopg2==2.7.5
psycopg2-binary==2.7.5
```


これをサクッとビルドしましょう。

```bash
$ docker build -t django2.1 -f ./django/Dockerfile.base ./django
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
django2.1           latest              c391e4c70e98        6 minutes ago       960MB
python              3.7                 825141134528        4 weeks ago         923MB
```

`python:3.7`のイメージをもとに`django2.1:latest`というイメージが作成されたのがわかります。

##2.Djangoプロジェクトのローカルへの作成
Djangoを使用できるイメージを作成したところで、
次はこのイメージを使ったコンテナからプロジェクトファイルを作成します。
ここでキモとなるのがコンテナ作成時に`docker-django/django/`をmountオプションでバインドします。
こうすることで何が嬉しいかというとコンテナがあたかもvirtualenvのように扱えるのです！
早速次のコマンドを実行します。

```bash
$ cd django
$ docker run --rm \
--mount type=bind,src=$(pwd),dst=/opt/apps \
django2.1 \
django-admin startproject my_docker_project .
```
少しコマンドが長くなってしまいましたので改行をいれてあります。
一行ずつみていきましょう。
まずはDjangoプロジェクトを配置したい、`./django/`へカレントディレクトリを移動します。
次の行からコンテナの起動オプションです。
１行目の`--rm`はコマンドの実行が終えたらコンテナを削除するオプションです。
２行目の`--mount`はコンテナ起動時にマウントするホストディレクトリなどを設定します。今回はホストへの読み書きを許可するのでtypeはbind,srcはカレントディレクトリ,ｄｓｔはコンテナ側のカレントディレクトリを指定します。
３行目にはコンテナのもととなるイメージを指定しています。
４行目がコンテナ内で実行されるコマンドです。今回は`my_docker_project`という名前のプロジェクトを作成しました。

実行後、djangoフォルダの中身をみてみると、

```
$ tree
.
├── Dockerfile
├── Dockerfile.base
├── manage.py
├── my_docker_project
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── requirements.base.txt
└── requirements.txt
```
Djangoのプロジェクトファイルがローカルに作成されています。素晴らしい！！
このようにDjangoのコマンドを使ってあれこれするときは、ホストをマウントしたコンテナで実行していきます。

ここまでできたらDjango関連は一旦おやすみです。

##3.docker-compose.ymlの作成と各コンテナ間の接続設定
Django+Gunicornな環境構築が落ち着いたところで、視点をコンテナの全体構成に向けます。
目標は、nginx + (Gunicorn + Django) + PostgreSQLな構成でした。
docker-compose.ymlは以下のようになります。

```yaml:docker-compose.yml
version: '3.7'
services:
  django:
    restart: always
    build: ./django
    expose:
      - "3031"
    depends_on:
      - postgres
    command: bash -c "python manage.py migrate && gunicorn my_docker_project.wsgi -b 0.0.0.0:3031"
    volumes:
      - "staticdata:/opt/static/"
  nginx:
    restart: always
    image: nginx
    depends_on:
      - django
    ports:
      - "80:80"
    volumes:
      - "./nginx/:/etc/nginx/"
      - "staticdata:/opt/apps/static/"
  postgres:
    image: postgres
    ports:
      - "5432:5432"
    volumes:
      - "dbdata:/var/lib/postgresql/data"
    environment:
      POSTGRES_PASSWORD: hogemojahogemoja

volumes:
  dbdata:
  staticdata:
```
ここでは、
「DBのデータは永続化したい。」
「静的ファイルはnginxコンテナから配信したい。」
がキモになります。

#####「DBのデータは永続化したい。」　<=>　「コンテナ外部のボリュームを使いたい」
ので、docker-compose.ymlのトップレベルに `dbdata`というボリュームを宣言し、
それをコンテナにマウントしています。
#####「静的ファイルはnginxコンテナから配信したい。」　<=>　「コンテナ間でリソースを共有したい。」
ので、同じくトップレベルに`staticdata`というボリュームを宣言し、
それをdjangoとnginx両方のコンテナにマウントしています。

###3-1.nginxコンテナの設定
今回の構成ではnginxで受け取ったリクエストを適宜Django(Gunicorn)に振り分けたり、静的ファイルを配信したりします。
さっそくconfファイルに書いていきましょう。

```nginx:nginx.conf
http {
#~中略~
  include /etc/nginx/mime.types;
#~中略~
  upstream django_server {
    #docker-compose.ymlに記入したサーバ名、ポートを指定します。 #
    server django:3031 fail_timeout=0;
  }
    
  server {
#~中略~
    location /static/ {
　　　　　　　　　　　　#先ほどしていた静的ファイルのボリュームのマウント先を指定します。#
      alias /opt/apps/static/;
    }
    
    location / {
      try_files $uri @proxy_to_django;
    }

    location @proxy_to_django {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_redirect off;
      proxy_pass http://django_server;
    }
#~中略~
  }
}
```

nginxコンテナの設定は以上です。
今回は最小の構成で、mime.typesはデフォルトのまま使用しています。

###3-2.djangoコンテナの設定とビルド用のDockerfile作成
docker-compose.ymlやnginx.confファイルの情報をもとにsettings.pyを編集していきます。

```python:settings.py
#〜中略〜
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'postgres',  #作成時のデフォルト
        'USER': 'postgres',  #作成時のデフォルト
        'PASSWORD' : 'hogemojahogemoja', #作成時にdocker-compose.ymlで設定
        'HOST' : 'postgres', #コンテナのサーバ名
        'PORT' : 5432,
    }
}
#〜中略〜
STATIC_URL = '/static/'
STATIC_ROOT = '/opt/static' #ボリュームマウント先のパス
#〜中略〜
```

続いてDockerfileを編集していきます。

```dockerfile:Dockerfile
FROM django2.1:latest

ENV APP_PATH /opt/apps

COPY . $APP_PATH/
RUN pip install --no-cache-dir -r $APP_PATH/requirements.txt

WORKDIR $APP_PATH
RUN python manage.py collectstatic --noinput
```
こちらのイメージは先ほどのベースイメージとは異なり、デプロイ用のイメージです。
プロジェクトで使用しているpythonライブラリをインストールしたり、配信用に静的ファイルをまとめたりしてます。

##4.Docker Composeでupする。
お疲れさまでした。
あとはupするだけです。

```bash
$ docker-compose up --build
```

各コンテナが立ち上がったあと[localhost](http://localhost/)にアクセスすると…
![スクリーンショット 2018-09-04 20.08.47.png](https://qiita-image-store.s3.amazonaws.com/0/94505/82ba8c45-aa3d-f344-b51a-bd3de1beecb6.png)
素晴らしい！！

![スクリーンショット 2018-09-04 20.09.01.png](https://qiita-image-store.s3.amazonaws.com/0/94505/9ad008e9-dd07-b604-e4f9-85d8ade33f46.png)
CSSもちゃんと効いてます！

## 5.終わりに
Dockerを触り始めて日が浅いですが
こんな風に各コンテナが協調して動いてるのが作れると脳汁出まくりでホント楽しいです。
今後もっと業務や趣味にDocker取り入れて行きたいです！！