Docker + django + Nginx + MySQL を理解する

2020-03-27: Windows 10 Professional/Docker Desktop 2.2.0.4/docker-commpose 1.25.4

Django + Nginx + MySql で開発環境を作るための情報。
Qiita 内に優れた記事があり、その通りやれば動くのだが、何をやっているのかよく分からなかったので調べた。

参考サイト
https://qiita.com/kenkono/items/6221ad12670d1ae8b1dd

注意すべき落とし穴

- AWS のサーバーを使うときはセキュリティグループの設定に注意
- MySQL の初期設定をするときは、ユーザー名やパスワードは相互に矛盾がないようにする
- docker-compose はいきなり up せず、build してからソースの手直しがいる
- MySQL の 8.0 は新しい認証方式への対応が複雑なので、5.7 のままにしておくのが吉


# まず Windows 側での作業ディレクトリの準備
## 概要

c:\Local 以下にこういうディレクトリ構造を作成。
全体を入れるディレクトリの名前は c:\Logal\projectname\ としたが、違う名前を使っても以降の記述はそのまま使えるはず。

```:c：\Local
projectname\
    backup\
    mysql\
    nginx\
        conf\
            app_nginx.conf
        log\
        uwsgi_params
    python\
        Dockerfile
        requirements.txt
    sql\
        init.sql
    src\
    static\
    docker-compose.yml
```

|ディレクトリ名|内容|格納するファイル|
|---|---|---|
|projectname|docker 用の全体ディレクトリ|直下に docker-compose.yml を格納|
|projectname\backup|MySQL バックアップデータ|mysqldump でダンプしたデータのやり取りするために使用 |
|projectname\mysql|MySQL データ|初期は空。MySQL がデータを保存するディレクトリ。|
|projectname\nginx|Nginx 設定用ファイル |uwsgi_params を格納 |
|projectname\nginx\conf|Nginx 設定用ファイル|Nginx のメイン設定ファイル app_nginx.conf を格納 | 
|projectname\nginx\log|Nginx ログ|初期は空。Nginx のログが作られる。|
|projectname\python| |dockerfile と requirements.txt を格納|
|projectname\sql|MySQL 初期化ファイル|MySQL の初期化用設定ファイル init.sql を格納|
|projectname\src|django のソース|初期は空。jango のプロジェクトディレクトリ。この直下にアプリのフォルダを置く |
|projectname\static|Web の静的ページ用|静的な HTML ファイルを入れる|



## 準備するファイル

### uwsgi_params
wsgi(Web Server Gateway Interface)は、Python で Web サーバーと Web アプリケーションをつなぐインターフェース。
uwsgi_params は Nginx用 の wsgi 設定のファイル。
特に書き換える必要がないとされていて、ネット上でも「こう書いておけばいいのだ」とスルーされている。

```:projectname\nginx\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;
```


### app_nginx.conf

Nginx のメインの設定ファイル。
upstream で、django のサーバーを指定する。
server はドメイン名か IP アドレスとオプションでポートを指定する。
location では /static には静的コンテンツを入れる。url を変える場合は /web という部分を書きかえる。2 つ目の location の記述によって、nginx にアクセスのあった static を除くすべての情報 wsgi 経由で Django へ行く。
access_log はアクセスログを標準的なフォーマット(main)で出力する。
error_log はエラーログを出力する。
server_tokens off; はエラーページに nginx のバージョンを非表示にする。




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

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

    location /web {
        alias /static;
    }

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

access_log  /var/log/nginx/access.log main;
error_log  /var/log/nginx/error.log warn;

server_tokens off;

```

### init.sql
docker の MySQL image は、/docker-entrypoint-initdb.d/ にあるスクリプトを使って、最初に image を起動したときの初期化が行える。django で使用するデータベース名（後述）が sakura だと、テスト用のデータベースは test_sakura となる。ここではユーザー名を sumire とした。

```:projectname\sql\init.sql
GRANT ALL PRIVILEGES ON test_sakura TO 'sumire'@'%';

FLUSH PRIVILEGES;
```


### requirements.txt
requirements.txt を使うと、pip で指定のパッケージを一括してインストールすることができる。
バージョンを省略すると最新版が入る。
検証に使ったバージョンは 2020/03 時点のもの。

```:projectname\python\requirements.txt
Django==3.0.4
uwsgi==2.0.18
mysqlclient==1.4.6
```

### dockerfile

`ENV PYTHONUNBUFFERED 1`は、バッファがたまってから出力するのを避ける指定。
RUN 命令は既存イメージ上でコマンドを実行し、その結果得られたイメージは Dockerfile の次のステップで使われる。
WORKDIR 命令は Dockerfile で次の行の ADD の実行時の作業ディレクトリの指定。
もし WORKDIR が存在しなければディレクトリを作成する。
ADD は requirements.txt に書かれたものを pip で読み込む。

```:projectname\python\dockerfile
FROM python:3.8.2
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt
ADD . /code/
```


### docker-compose.yml

docker-compose の本体の設定ファイル。Nginx と MySql と Python+Django の 3 つの設定と連携について定義する。
YAML ファイルのインデントはスペース 2 個単位で行わないと、エラーが出ることに注意。

#### Nginx 記述部
nginx というのがサービス名。それぞれのサービスに個別にアクセスしたくなったときに必要となる。
この例ではポート番号は 8000 になっているが、本番用の 80 にしたい場合は、"8000:8000" を "80:8000" に変更する。
volume はローカル側のフォルダをコンテナ内にマウントする指定。ホスト（Windows）のディレクトリ:コンテナ内のディレクトリ、の書式。
environment の TZ はタイムゾーンで、これをやっとかないとログの時計が標準時になってしまう。

### db 記述部
db というのがサービス名。
environment でデータベースの基本情報を記述。データベース名は sakura で、ユーザー名は sumire としてある。MySQL の root と sumire のパスワードの指定も行う。
volume はローカル側のフォルダをコンテナ内にマウントする指定。Windows 側の mysql フォルダと同期して永続化する。

### python 記述部
python というのがサービス名。
command は uwsgi を使用してポート 8001 を開放。app.wsgi の app は Django のプロジェクト名で、django-admin startproject app の app部分。--py-autoreload 1 は Django アプリ開発の際に、ファイル等に変更があった際は自動リロードする。--logto /tmp/mylog.log はログを残す指定。

### docker-compose.yml のファイル全体



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

services:
    nginx:
        image: nginx:1.16
        ports:
            - "8000:8000"
        volumes:
            - ./nginx/conf:/etc/nginx/conf.d
            - ./nginx/uwsgi_params:/etc/nginx/uwsgi_params
            - ./nginx/log:/var/log/nginx
            - ./static:/static
        depends_on:
            - python
        environment:
            TZ: 'Asia/Tokyo'

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

    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
        environment:
            TZ: 'Asia/Tokyo'
```










# イメージのダウンロードとコンテナ作成、設定

ここまでの時点で作成した django フォルダのバックアップを取っておくと、このあと各イメージのバージョンが合わないなどの不具合があったときに、やり直しが早く済む。

## インストールと docker-compose の実行

新しく作るプロジェクトの名前を app とする。
projectname\ で以下のように docker-compose を実行する。
実行するときカレントディレクトリを間違えると謎のエラーが連発するので注意。

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

## ALLOWED_HOSTS の追加

新しいバージョンの django は ALLOWED_HOSTS の記述が必要。settings.py を編集する。

```projectname\src\app\settings.py
ALLOWED_HOSTS = ["*"]
```

## データベースの切り替え
エラーが出なかったら、次にデータベースをデフォルトの sqlite から MySQL に切り替えるために django\src\mysite\mysite 内の stttings.py を変更する。もともとは PyMySQL を使用していたが、いまは mysqlclient の方が標準らしいのでそうする。データベースの指定をする記述があるので、それを次のように置き換える。ユーザー sumire とパスワード akutagawa を指定して、データベース sakura にアクセスできるようにする。

```projectname\src\app\settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'sakura',
        'USER': 'sumire',
        'PASSWORD': 'akutagawa',
        'HOST': 'db',
        'PORT': '3306',
    }
}
```

## マイグレーション
projectname\ で以下のコマンドを実行する。
実行時のカレントディレクトリを間違えると謎のエラーに悩まされるので注意。
データベースの変更を django に知らせて反映させる。
データベースのバージョンが合わなかったりするとエラーが出る。

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

## 管理者の設定

管理者のユーザー名とパスワードを設定する。
projectname\ で以下のコマンドを実行する。
ユーザー名はデフォルトで root となっている。まずは捨てパスでいいと思うが、あまりいい加減なパスワード（password など）を入力するとダメと言われるので注意。

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

# コンテナの起動

```
docker-compose up -d
```
ブラウザで `http://localhost:8000` にアクセスして Django のページが見えたら完成。停止は `docker-compose stop`で、起動は`docker-compose up -d`で。

# 作成したソースの反映

## 新しいアプリを置き django の設定を更新

開発したアプリ（ここでは memo とする）をプロジェクトに入れる。フォルダ memo ごと projectname\src\ 下に置く。

### 新しいアプリのインストール

projectname\src\app\settings.py の INSTALLED_APPS に`'memo.apps.MemoConfig'`を追加

```projectname\src\app\settings.py
INSTALLED_APPS = [
    'memo.apps.MemoConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
```

### アプリの url を設定

projectname\src\app\urls.py に`from django.urls import include`と、 url へのパス`path('memo/', include('memo.urls'))`を追加

```py:projectname\src\app\urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('memo/', include('memo.urls')),
    path('admin/', admin.site.urls),
]
```

## 変更をマイグレーションして django に認識させる

projectname\ で以下のコマンドを実行する。

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

これでブラウザから`http://localhost:8000/web`で \static に置いた静的な html ファイルが見える。`http://localhost:8000/memo`で、memo アプリケーションが見える。



おわり