DockerでDjangoの開発環境を構築

![small_v.png](https://qiita-image-store.s3.amazonaws.com/0/37724/8a455840-99ba-592a-be3d-8eeebb8fd332.png "small_v.png")


Dockerを利用したDjangoの実行環境セットアップ方法を紹介します。
Docker構成はDjango, Nginx、MySQL, KVS(Redis)のコンテナを組み合せた環境を構築し
MySQL, KVSについてはデータを永続化させるよう対応します。
開発スタイルとしては、Docker-machinは1つで、開発環境、本番環境を構築し
docker-composeでどちらか一方が起動でき簡単に切り替えが行えるようにします。


## コンテナ構成

![Network Diagram.png](https://qiita-image-store.s3.amazonaws.com/0/37724/b74c8fa3-ea88-f444-2b1a-ae86466fdf63.png)

クライアントからのアクセスを受け、NginxがリバースプロキシでバックエンドのAPPサーバへproxyし
Nginxコンテナは、APPコンテナをmountしてAPPコンテナの静的ファイルを参照できるようにします。
MySQLコンテナ、RedisコンテナはAPPコンテナにlinkさせコンテナ同士で通信する形になり、
Storageコンテナは、MySQL、Redisのデータを持ちます


## ファイル構成

サンプル用のDjangoアプリケーションを含めたDockerのファイル構成が以下になります

```
├── app
│   ├── __init__.py
│   ├── env
│   ├── manage.py
│   ├── requirements.txt
│   ├── settings.py
│   ├── static
│   ├── todo
│   │   ├── __init__.py
│   │   ├── apps.py
│   │   ├── migrations
│   │   │   ├── 0001_initial.py
│   │   │   └── __init__.py
│   │   ├── models.py
│   │   ├── templates
│   │   │   ├── _base.html
│   │   │   └── home.html
│   │   └── views.py
│   ├── urls.py
│   └── wsgi.py
├── docker-compose-dev.yml
├── docker-compose-prod.yml
├── docker-compose.sh
├── mysql
│   ├── Dockerfile
│   └── charset.cnf
├── nginx
│   ├── Dockerfile
│   └── sites-enabled
│       └── django_project
└── storage
    └── Dockerfile
```

各コンテナ別にディレクトリを作っていて、Dockerfileを準備してます
docker-compose-dev.ymlとdocker-compose-prod.ymlに開発、本番環境をDocker構成を定義し
docker-compose.shで切り替えを行います。

上記の全ファイルはgithubに置いてます
https://github.com/fujimisakari/django-docker


### docker-compose-dev.yml

```yaml
dev_app:
  image: todo_app
  restart: always
  env_file: ./app/env
  expose:
    - "8000"
  links:
    - dev_mysql:db
    - dev_redis:redis
  volumes:
    - ./app:/usr/src/app
  command: ./manage.py runserver 0.0.0.0:8000

dev_nginx:
  build: ./nginx
  restart: always
  ports:
    - "80:80"
  links:
    - dev_app:app
  volumes_from:
    - dev_app

dev_mysql:
  build: ./mysql
  restart: always
  ports:
    - "3306:3306"
  volumes_from:
    - dev_storage

dev_redis:
  image: redis:latest
  restart: always
  ports:
    - "6379:6379"
  volumes_from:
    - dev_storage
  command: redis-server --appendonly yes

dev_storage:
  build: ./storage
```

appコンテナは、buildに時間を取るのでdocker-composeの実行前に作成するtodo_appイメージを
利用するようにしており、mysqlとredisはデータ永続化するためにstorageコンテナのvolumeを
マウントしてこちらにデータを書き込みます。
redisコマンドの「--appendonly yes」は、redisのデータを/dataに書き残すために必要なオプションになります

### docker-compose-prod.yml

```yaml
prod_app:
  image: todo_app
  restart: always
  env_file: ./app/.env
  expose:
    - "8000"
  links:
    - prod_mysql:db
    - prod_redis:redis
  volumes:
    - ./app:/usr/src/app
  command: /usr/local/bin/gunicorn app.wsgi:application -w 2 -b :8000

prod_nginx:
  build: ./nginx
  restart: always
  ports:
    - "80:80"
  links:
    - prod_app:app
  volumes_from:
    - prod_app

prod_mysql:
  build: ./mysql
  restart: always
  ports:
    - "3306:3306"
  volumes_from:
    - prod_storage

prod_redis:
  image: redis:latest
  restart: always
  ports:
    - "6379:6379"
  volumes_from:
    - prod_storage
  command: redis-server --appendonly yes

prod_storage:
  build: ./storage
```

本番環境といっても開発時に利用するものなのでdocker-compose-dev.ymlとほぼ同じになるのですが、
appコンテナのwebサーバーの起動コマンドがgunicornになっています。
パフォーマンスチェックする時にuwsgiやfastcgiなど別webサーバーに簡単に変更できます


### docker-compose.sh

```sh
if [ $1 ]; then
    case $1 in
        "dev")
            echo "$ docker-compose -f docker-compose-dev.yml ${@:2}"
            docker-compose -f docker-compose-dev.yml ${@:2}
        ;;
        "prod")
            echo "$ docker-compose -f docker-compose-prod.yml ${@:2}"
            docker-compose -f docker-compose-prod.yml ${@:2}
        ;;
    esac
else
   echo "Please input platform to script paramater.\n ./docker-compose.sh platform [args]"
fi

```

開発、本番環境へdocker-composeコマンドを実行しようとすると毎回 -f コマンドで
ymlファイルを指定してする必要があり面倒なのでスクリプトでラップしました。
第一引数に環境を指定して実行します。


## コンテナ起動をさせる

### appイメージ作成
docker-compose up --buildですべてのコンテナをビルドして起動する準備はできますが、
appのビルドはapt-installやpip installがあり少し時間がかかるので利用するイメージを事前に
準備しこちらを利用するようにします。

```sh
$ cd ./app
$ docker build .
$ docker tag <ビルドで生成されたコンテナID> todo_app
```

### hostsファイル更新

docker-machineのIPアドレスを毎回入力したくないので。
以下を追記

```sh
$ sudo vim /etc/hosts
192.168.99.100 docker-machine
```

### Dockerコンテナ起動(開発環境を起動させる場合)

```sh
# 起動(-dを付けるとデーモンにもできる)
$ ./docker-compose.sh dev up --build
$ docker exec -it <devのAppのコンテナ識別子> ./manage.py migrate
$ ./docker-compose.sh dev stop
$ ./docker-compose.sh dev up
```

http://docker-machine でアクセスできるようになる


## ホストからDockerへよく利用するコマンド


### ./manage.py の実行

```sh
$ docker exec -it <コンテナ識別子> ./manage.py <command>
```


### MySQLへの接続

```sh
$ mysql -u root -h docker-machine todo
```


### MySQLのQuery確認

```sh
$ docker exec -it <コンテナ識別子> tail -f /tmp/sql.log
$ docker exec -it <コンテナ識別子> tail -f /tmp/slow_sql.log
```

## 感想

開発の利用用途だけで言うと、ローカルの開発環境の方が効率が良いと思うのですが
Dockerを利用するとプログラム以外の技術もチャレンジしやすくなり幅広く経験できると思います。
まだDocker開発環境に切り替えて数週間ですが以下のメリット、デメリットを感じました。

**メリット**
簡単に異なるアプリケーション環境をいくつも作ることができるので
パフォーマンスチェックやインフラの構成変更などやろうとするモチベーションが上がる。
また、テストはこれまで専用のfixutreデータが入った環境が必要だったのですが
Dockerで簡単に環境が用意できるのでDBの切り替えなども必要もなく
カジュアルにテストできるのはうれしいですね。

**デメリット**
アプリケーション操作しようとすると何でもdocker 〜コマンドになるので
ホストにvirtualenvで開発環境を構築する方が操作が簡単です。
あと、Docker上にpythonのサードパーティモジュールがあるので
Pythonファイルを開いたときpylint等のコードチェッカーでimportエラー発生します。
そのため、結局ローカルにも環境を用意しました。