Django+Nginx+MySQLの開発環境をDockerで構築する

Djangoを使用するための環境をDockerで構築していきます。
また、`Nginx` `MySQL`を使用するための環境も同時に構築します。

# 環境
MacOS Mojave
docker for mac（下記からインストールできます）
https://docs.docker.com/docker-for-mac/install/

# ディレクトリ構成
`python` `nginx` `MySQL`で構成を分けておきます。

```
django
├── docker-compose.yml
├── mysql
├── sql
├── nginx
│   ├── conf
│   │   └── app_nginx.conf
│   └── uwsgi_params
└── python
    ├── Dockerfile
    └── requirements.txt
```

# Djangoの設定

### Dockerfileの作成
`PYTHONUNBUFFERED`は、バッファが溜まってから出力するのを避けるための記述です。
パフォーマンスの観点からはバッファが溜まってからの出力の方が良いのですが、今回は素早い反応を求めるため、設定を記述しておきます。
参考記事：[Python で stdout/stderr のバッファを無効にするオプション](https://qiita.com/kitsuyui/items/7d1704850e503be8dd1c)

```python:Dockerfile
FROM python:3.6
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt
ADD . /code/
```

### requirement.txtの作成

環境によって、バージョンは変更します。
`uwsgi`はPythonでWebサービスを動かすためのアプリケーションサーバで、今回はnginxと連携するために使用します。
`PyMySQL`はDjangoのMySQLを接続するために使用します。

```requirement.txt
Django==2.0.4
uwsgi==2.0.17
PyMySQL==0.8.0
```

### docker-compose.ymlへの記述

- `command`の欄ですが、上述のuwsgiを使用してポート8001（任意のポート番号）を開放します。これが、後のnginxへの連携の際に必要な処理となります。
- `app.wsgi`のappはDjangoのプロジェクト名です。(例:`django-admin startproject app`のapp部分)
- `--py-autoreload 1`はDjangoアプリ開発の際に、ファイル等に変更があった際は自動リロードするための設定です。
参考記事：[How to set up autoreload with Flask+uWSGI?](https://stackoverflow.com/questions/8831748/how-to-set-up-autoreload-with-flaskuwsgi/15864997#15864997)
- `--logto /tmp/mylog.log`はログを残すための記述です。

```docker-compose.yml
python:
    build: ./python
    command: uwsgi --socket :8001 --module app.wsgi --py-autoreload 1 --logto /tmp/mylog.log
    volumes: 
      - ./src:/code
      - ./static:/static
    expose:
      - "8001"
    depends_on:
      - db
```

# MySQLの設定

コンテナに入ったデータは、起動を停止する際にすべて破棄されます。
データを永続化するため、ローカルのmysqlフォルダと同期しておきます。
`command`はMySQLの文字コードの設定です（defaultはlatin1が入っており、日本語入力ができない）

```docker-compose.yml
db:
    image: mysql:5.7
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: todoList
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      TZ: 'Asia/Tokyo'
    volumes:
      - ./mysql:/var/lib/mysql
      - ./sql:/docker-entrypoint-initdb.d
```

`docker-entrypoint-initdb.d`にマウントされた`.sql`に記載されたsql
文は初回コンテナ起動時に実行されます。
参考記事：[Docker で MySQL 起動時にデータの初期化を行う](https://qiita.com/moaikids/items/f7c0db2c98425094ef10)
後にdjangoでtestを実行する際にDB作成を許可するため、以下を記述します。

```sql/init.sql
GRANT ALL PRIVILEGES ON test_todoList.* TO 'user'@'%';

FLUSH PRIVILEGES;
```

# uWSGIの設定
nginxのuWSGIモジュール用の設定ファイル
例えばアクセスのあったユーザのIPアドレスが知りたい場合、`REMOTE_ADDR`を使用します。
参考記事：[How to Hide Nginx Server Version in Linux](https://www.tecmint.com/hide-nginx-server-version-in-linux/)

```python:uwsgi_params
uwsgi_param  QUERY_STRING       $query_string;
uwsgi_param  REQUEST_METHOD     $request_method;
uwsgi_param  CONTENT_TYPE       $content_type;
uwsgi_param  CONTENT_LENGTH     $content_length;

uwsgi_param  REQUEST_URI        $request_uri;
uwsgi_param  PATH_INFO          $document_uri;
uwsgi_param  DOCUMENT_ROOT      $document_root;
uwsgi_param  SERVER_PROTOCOL    $server_protocol;
uwsgi_param  REQUEST_SCHEME     $scheme;
uwsgi_param  HTTPS              $https if_not_empty;

uwsgi_param  REMOTE_ADDR        $remote_addr;
uwsgi_param  REMOTE_PORT        $remote_port;
uwsgi_param  SERVER_PORT        $server_port;
uwsgi_param  SERVER_NAME        $server_name;
```

# Nginxの設定

```docker-compose.yml
nginx:
    image: nginx:1.13
    ports:
      - "8000:8000"
    volumes:
      - ./nginx/conf:/etc/nginx/conf.d
      - ./nginx/uwsgi_params:/etc/nginx/uwsgi_params
      - ./static:/static
    depends_on:
      - python
```

### 設定ファイルの作成

`location / {`以降の記述によって、nginxにアクセスのあったすべての情報がDjango側へ行くことになる（静的ファイルの保管してある/staticを除く）
参考記事：[nginxでDjangoを使うときの設定ファイル：クライアント、nginx、uwsgiの流れを整理しよう](https://www.mathpython.com/ja/django-nginx-conf/)
`server_tokens off;`を設定することで、Response Headersにnginxのバージョンを非表示にすることができます。
これは、セキュリティ強化の面で追加します。
<img width=75% alt="Screen Shot 2019-05-30 at 10.16.41 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291139/15448f69-f27f-3f86-4069-2513c30756cc.png">

#####なぜバージョンを非表示にする必要があるのか
仮にNginxのあるバージョンに脆弱性が発見された場合、ハッカーが該当のバージョンをResponse Headersから探し出し、攻撃される恐れがあるためです。


```app_nginx.conf
upstream django {
    ip_hash;
    server python:8001;
}

server {
    listen      8000;
    server_name 127.0.0.1;
    charset     utf-8;

    location /static {
        alias /static;
    }

    location / {
        uwsgi_pass  django;
        include     /etc/nginx/uwsgi_params;
    }
}

server_tokens off;
```

# 最終的なdocker-compose.yml

```docker-compose.yml
version: '3'

services:
  nginx:
      image: nginx:1.13
      ports:
        - "8000:8000"
      volumes:
        - ./nginx/conf:/etc/nginx/conf.d
        - ./nginx/uwsgi_params:/etc/nginx/uwsgi_params
        - ./static:/static
      depends_on:
        - python

  db:
      image: mysql:5.7
      command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
      ports:
        - "3306:3306"
      environment:
        MYSQL_ROOT_PASSWORD: root
        MYSQL_DATABASE: todoList
        MYSQL_USER: user
        MYSQL_PASSWORD: password
        TZ: 'Asia/Tokyo'
      volumes:
        - ./mysql:/var/lib/mysql
        - ./sql:/docker-entrypoint-initdb.d

  python:
      build: ./python
      command: uwsgi --socket :8001 --module app.wsgi --py-autoreload 1 --logto /tmp/mylog.log
      volumes:
        - ./src:/code
        - ./static:/static
      expose:
        - "8001"
      depends_on:
        - db
```

# Djangoプロジェクトの実行

```
docker-compose run python django-admin.py startproject app .
```

実行の結果、下記のディレクトリ構成になります。

```
django
├── docker-compose.yml
├── mysql
├── sql
├── nginx
│   ├── conf
│   │   └── app_nginx.conf
│   └── uwsgi_params
├── python
│   ├── Dockerfile
│   └── requirements.txt
├── src
│   ├── app
│   │   ├── __init__.py
│   │   ├── settings.py
│   │   ├── urls.py
│   │   └── wsgi.py
│   └── manage.py
└── static
```

### MySQLとの接続
`settings.py`に以下を記述

```settings.py
"""
Django settings for app project.

Generated by 'django-admin startproject' using Django 2.0.4.

For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
"""

import os
import pymysql

# connect mysql
pymysql.install_as_MySQLdb()
```

DATABASESの内容を変更
`NAME`はMySQLのデータベース名です

```settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'todoList',
        'USER': 'user',
        'PASSWORD': 'password',
        'HOST': 'db',
        'PORT': '3306',
    }
}
```

### マイグレーションの実施

```
docker-compose run python ./manage.py makemigrations
docker-compose run python ./manage.py migrate
```

### 管理者の設定

```
docker-compose run python ./manage.py createsuperuser
```

# コンテナを起動し、ブラウザで確認する
`-d`はバックグラウンドで実行を意味する

```
docker-compose up -d
```

起動後、`http://localhost:8000`へアクセス
下記画面が表示されれば成功です。
<img width=100% alt="Screen Shot 2019-05-30 at 10.06.33 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291139/6e188cc6-3fe6-2c1c-6ec4-6467e808601e.png">

#CSSを適用させる
管理者画面(`http://localhost:8000/admin`)にいきますと、CSSが適用されていないのが確認できます。
<img width="365" alt="Screen Shot 2019-05-30 at 10.09.23 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291139/7837d10d-f1e0-dcbe-1dab-fcd42acd9976.png">

settings.pyに`STATIC_ROOT = '/static'`を追加します。

```python:settings.py
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/

STATIC_URL = '/static/'
STATIC_ROOT = '/static'
```

コマンドを実行

```
docker-compose run python ./manage.py collectstatic
```

再度アクセス(`http://localhost:8000/admin`)すると、適用が確認できます。
<img width="581" alt="Screen Shot 2019-05-30 at 10.12.40 PM.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/291139/f11bda48-dd20-82af-1fcc-e8e7a106551d.png">

# 注意点
git管理する際は、MySQLの中身をオープンにするのはセキュリティ的によろしくないので`.gitignore`に下記を追加しておきましょう。


```python:.gitignore
mysql/*
```


# 参照元
[Docker-Composeで作るDjango開発環境(Django + MySQL + uWSGI + Nginx)](https://nmmmk.hatenablog.com/entry/2018/05/01/101126)
[【Django入門】Nginxを使ってWebサーバーを動かそう](https://www.sejuku.net/blog/28751)