Django + MySQLの環境構築を通じてDockerの使い方を学ぶ

## はじめに
プログラミングスクールで**Ruby**と**Rails**を学んだあと、個人的な関心から**Python**と**Django**を触り始めたのですが、**Django**を扱い始めて最初に感じた個人的な印象は「(機能実装のための)**コードを書き始めるまでの準備がそもそも、Railsと比べて難しい**」というものでした。

今後、個人的に色々なアプリを作ったり、新しく学んだことを手を動かして実践したりする際の効率性を向上させるためには、その環境構築作業そのものに慣れることはもちろん、**機能の実装や学習内容の実践に集中できるような体勢を整えることも必要**だと感じ、コンテナ技術 ( の扱い ) について学んでみることにしました。

この記事 ( 群 ) の目的は、**Django + MySQLの環境構築を通じて、Dockerの使い方の流れを学ぶこと**、そして、**自分用に使い回しのできるDjangoプロジェクトの雛形を作成すること**です。

１つ１つの記述の意味をなるべく丁寧に追っていくことによって、別言語/フレームワークを扱うときにも再現性のある知識の習得を目指していきたいと思っています。
Docker初心者による備忘録となりますが、よろしければお付き合いください。

## 実行環境
### 利用環境
```
- MacOS catalina 10.15.6
- Docker 19.03.8
    (- docker-desktop for Mac 2.2.0.3)
- pyenv 1.2.20
```
### 構築を目指す環境
```
- Python 3.7.7
- Django 3.0.8
- MySQL 5.7.31
```

## 目次

1. **[venvを利用して仮想環境を準備する](https://qiita.com/toffe_hy/items/8c62f2bc7b14f1ce2ae0)**
venvを利用して、特定のバージョンのPython環境を準備します。Djangoプロジェクトの作成も行います。

2. **[Dockerfileの記述を考える](https://qiita.com/toffe_hy/items/0429a95bfe1786955c2a)**
Dockerfileの記述を通して**`Dockerイメージ`**について考えます。

3. **[docker-compose.ymlの記述を考える](https://qiita.com/toffe_hy/items/f2e6ecc516fdf593c777)**
docker-compose.ymlを作成し、Djangoとそれを接続するMySQLの設定を通して**`Dockerコンテナ`**について学びます。

4. **[設定ファイルを編集し docker-compose up を実行する](https://qiita.com/toffe_hy/items/0571b84faa3e6b834c6e)**
`Django`側の各種設定を行い、`$ docker-compose up`を実行します。

5. **[依存関係にあるサービス間のコンテナ立ち上げタイミングを調整する](https://qiita.com/toffe_hy/items/4b7d8c47c5a1a2defcac)**
これまでの工程だと、**特定の条件下でMySQLのコンテナがDjangoコンテナより先に立ち上がらず、接続に失敗することがあります**。**専用の`Python`ファイル**を作って問題の解決を目指します。

6. **開発作業**
あとは状況に応じて変更を加えつつ、開発作業に集中するだけです。

## ソースコード
主な関連ファイルのソースコードを予めこちらにまとめておきます。
それぞれのファイルの記述内容については、**前述した工程別記事**にて説明します。

```Dockerfile:Dockerfile
FROM python:3.7
ENV PYTHONUNBUFFERED 1

RUN mkdir /code
WORKDIR /code

COPY requirements.txt /code/
RUN pip install --upgrade pip
RUN pip install -r requirements.txt

COPY . /code/

EXPOSE 8000
```

```yml:docker-compose.yml
version: "3"
services:
    web:
        container_name: djst_django
        build: .
        restart: always
        command: >
            bash -c "
                pip install -r requirements.txt &&
                python config/wait_for_db.py &&
                python manage.py runserver 0.0.0.0:8000
            "
        working_dir: /code
        ports: 
            - 127.0.0.1:8000:8000
        volumes:
            - .:/code
        depends_on:
            - db
    db:
        container_name: djst_mysql
        image: mysql:5.7
        restart: always
        environment: 
            MYSQL_DATABASE: $DB_NAME
            MYSQL_USER: $DB_USER
            MYSQL_PASSWORD: $DB_PASSWORD
            MYSQL_ROOT_PASSWORD: $DB_ROOT_PASSWORD
            MYSQL_TCP_PORT: 3306
            TZ: 'Asia/Tokyo'
        volumes: 
            - ./mysql/data:/var/lib/mysql/
            - ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf
        ports: 
            - 3306:3306
```

```Python:config/wait_for_db.py
import os
import MySQLdb
from time import sleep
from pathlib import Path

os.chdir(Path(__file__).parent)
from local_settings import DB_NAME, DB_USER, DB_PASSWORD


count_to_try = 0
LIMIT_OF_COUNT = 20 # 値は必要に応じて調整


def check_connection(count, limit):
    """
    docker-compose up実行時用、時間調整のための関数。
    """
    try:
        conn = MySQLdb.connect(
            unix_socket = "/var/run/mysqld/mysqld.sock",
            user=DB_USER,
            passwd=DB_PASSWORD,
            host="db",
            port=3306,
            db=DB_NAME,
        )
    except MySQLdb._exceptions.OperationalError as e:
        count += 1
        print("Waiting for MySQL... (", count, "/ 20 )")
        sleep(3)
        if count < limit:
            check_connection(count, limit)
        else:
            print(e)
            print("Failed to connect mySQL.")
    else:
        print("Connected!\n")
        conn.close()
        exit()


if __name__ == "__main__":
    check_connection(count_to_try, LIMIT_OF_COUNT)
```

ご指摘などありましたら、コメントいただけますと幸いです。
よろしくお願い致します。