Edited at

Docker+Django(Python3)+nginx+MySQL環境でCRUDを作ってみた


はじめに

Dockerの勉強として、Todo管理アプリを作ってみました。

その手順をメモとして残しておきます。

Docker構築だけではなくDjangoにも触れており、ボリューミーな記事になっています。

ですが、調べながら2日ぐらいで作れたので、この手順に沿えば1日ぐらいで作れると思います。


各バージョン

後々出てきますが、利用したバージョンをこちらにまとめておきます。


  • Docker


    • Docker: Docker version 19.03.2, build 6a30dfc (docker --version で確認)

    • Docekr Compose: docker-compose version 1.24.1, build 4667896b (docker-compose --version で確認)



  • Python


    • python: 3.7

    • Django: 2.2.5

    • uWSGI: 2.0.18

    • mysqlclient: 1.4.4



  • Nginx


    • Nginx: 1.17



  • MySQL


    • MySQL: 5.7.27



※ MySQL8系を使おうと思ったのですが、マイグレーションでこけたので諦めました。


目次


1. Docker設定(Dockerfile, docker-compose.yml)

まずは作業ディレクトリを作成します(app-python/todo の部分はなんでもOKです)


mkdir -p app-python/todo
cd app-python/todo

mkdir python3
mkdir mysql
mkdir nginx


1-1. Django(Python3)

さっそくpythonコンテナの設定をしていきます。

今回はuwsgiもpythonコンテナに含めたので、その設定もここで作成していきます。


Dockerfile(python)

こんな感じの設定にしています。


  1. 作業ディレクトリ作成


  2. requirements.txtをコンテナへコピー


  3. pip installを実行(インストール内容はrequirements.txtに記載)

  4. ディレクトリを移動し、uwsgiの設定を読み込む(ファイルは別途コンテナへ連携する)


python3/Dockerfile

FROM python:3.7

RUN mkdir -p /work
WORKDIR /work
ADD requirements.txt /work
RUN pip install --upgrade pip
RUN pip install -r requirements.txt

WORKDIR /work/src
CMD ["uwsgi","--ini","/work/uwsgi.ini"]



requirements.txt

各ライブラリのバージョンや使い方は、pypi.org から参照できます。

urllib3は今回使いませんが、外部APIを使うかなー、ぐらいの感覚でとりあえず入れています。


python3/requirements.txt

Django==2.2.5

uwsgi==2.0.18
django-bootstrap4==1.0.1
mysqlclient==1.4.4
urllib3==1.25.3


uwsgi.ini

すみません、あまりわかっておらず。。。

おまじないのように書きました


python3/uwsgi.ini

[uwsgi]

wsgi-file = app/wsgi.py
master = true
processes = 1
socket = 0:3031
chmod-socket = 666
die-on-term = true
py-autoreload = 1
logto = /tmp/app.log


docker-compose.yml(python)

設定内容はこんな感じです。



  • build: ./python3python3/Dockerfile から構築する。


  • volumes: ./python3:/workpython3//workへマウントする。(これでuwsgi.iniが連携される)


    • これでイメージ再構築しなくても、ソース変更が反映されるようにする。




  • expose: "3031"port 3031をホストマシンに解放する。


    • linkされたコンテナからアクセス可能




  • depends_on: db でdbコンテナの後に構築する。


docker-compose.yml

version: '3'

services:
python:
build: ./python3
volumes:
- ./python3:/work
expose:
- "3031"
depends_on:
- db



1-2. MySQL


Dockerfile(mysql)

ただmy.cnf をコピーするだけです


mysql/Dockerfile

FROM mysql:5.7.27

ADD my.cnf /etc/mysql/my.cnf


my.cnf

タイムスタンプ、文字コードの設定だけです。


mysql/my.cnf

[mysqld]

explicit_defaults_for_timestamp = 1
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci


docker-compose.yml(mysql)

volumes: データをマウントしておきます。

DBを再構築した時にデータが消えてしまうのがイヤだったので。

portは3306(mysqlデフォルト)を利用し、environmentでDB設定を記載しています。

※ 別出ししたいが、いったんベタ書き


docker-compose.yml

version: '3'

services:
## 省略...
db:
build: ./mysql
volumes:
- ./mysql/data:/var/lib/mysql
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: todo
MYSQL_USER: worker
MYSQL_PASSWORD: worker
TZ: 'Asia/Tokyo'



1-3. Nginx


Dockerfile(nginx)

ただapp_nginx.conf をコピーするだけです


nginx/Dockerfile

FROM nginx:1.17

ADD conf /etc/nginx/conf.d


app_nginx.conf

次にNginxの設定ファイルを作成します。

※ ここはあまり説明しません。


nginx/conf/app_nginx.conf

upstream django {

ip_hash;
server python:3031;
}

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

location / {
include uwsgi_params;
uwsgi_pass django;
}
}



docker-compose.yml(nginx)

nginxからpythonへアクセスするので、リンク設定を行います。


docker-compose.yml

version: '3'

services:
## 省略...
##
nginx:
build: ./nginx
ports:
- "8000:8000"
links:
- python
depends_on:
- python



1-4. Djangoプロジェクト作成&起動

Dockerの設定が終わったので、Djangoプロジェクトを作成します。

以下コマンドを実行すると、python3/src/appが作成されます。

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

準備が整ったのでコンテナ構築します。

docker-compose up --build

ここでlocalhost:8000にアクセスすると以下のロケット画面が表示されるはずです。


2. Todoアプリ作成


2-1. とりあえずHelloWorld

とりあえずHelloWorldを表示できるレベル感で、アプリケーションを作成します。

まずは、アプリケーションを以下コマンドで作成します。

docker-compose run python ./manage.py startapp todo

todoディレクトリをアプリケーションとして認識してもらう設定を行います。

app/settings.pyINSTALLED_APPSを以下のように修正します。


python3/src/app/settings.py

INSTALLED_APPS = [

'todo.apps.TodoConfig', ## 追加
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]

次にURLの設定を行います。

app/urls.pyは、todo/urls.pyを読み込むだけの設定を追加します。


python3/src/app/urls.py

from django.urls import include, path ## includeを追加


urlpatterns = [
path('admin/', admin.site.urls),
path('todo/', include('todo.urls')), ## 追加
]

todo/urls.pyにアプリのURL設定を記載します。

とりあえず、今回はHelloWorldが表示されるレベル感を目指しています。


python3/src/todo/urls.py

from django.urls import path

from . import views

urlpatterns = [
path('', views.index, name='index'),
]


いわゆるコントローラを作成します。


python3/src/todo/views.py

from django.shortcuts import render

from django.http import HttpResponse

def index(request):
return HttpResponse("HelloWorld!")


この時点でlocalhost:8000/todoにアクセスすると「HelloWorld!」と表示されているはずです。


2-2. DB設定

まずDBの接続設定をapp/settings.pyに記載します。

設定内容は「docker-compose.yml(mysql)」の環境設定に合わせて記載します。


python3/src/app/settings.py

DATABASES = {

'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'todo',
'USER': 'worker',
'PASSWORD': 'worker',
'HOST': 'db',
'PORT': '3306',
}
}

次にModelを作成します。

各フィールドの設定についてはこちら

Model field reference | Django documentation | Djangoを見ていただくの1番いいと思います。


python3/src/todo/models.py

from django.db import models

from django.forms import ModelForm

class Todo(models.Model):
todo_id = models.AutoField(
primary_key=True,
max_length=10,
)
title = models.CharField(
verbose_name='TODOタイトル',
max_length=50
)
detail = models.CharField(
verbose_name='TODO詳細',
max_length=300
)
insert_date = models.DateTimeField(
'date published',
auto_now_add=True,
)
update_date = models.DateTimeField(
'date published',
auto_now=True,
)

class TodoForm(ModelForm):
class Meta:
model = Todo
fields = ['todo_id', 'title', 'detail', 'insert_date', 'update_date']
exclude = ['todo_id', 'insert_date', 'update_date']


ここで以下コマンドでマイグレートします。

migrateの結果は一部省略しています(adminとかの結果を除外しています。)

$ docker-compose run python ./manage.py makemigrations todo

Starting todo_db_1 ... done
Migrations for 'todo':
todo/migrations/0001_initial.py
- Create model Todo

$ docker-compose run python ./manage.py migrate
Starting todo_db_1 ... done
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, todo
Running migrations:
Applying todo.0001_initial... OK


2-3. Todoアプリを作る

Djangoの作りについてはあまり触れません。

細かく知りたい方は、はじめての Django アプリ作成、その 1が参考になると思います。

いったん、以下機能をサクッと作ってみました。


  • Todo一覧が見れる

  • 新しいTodoが登録できる

  • Todoを更新できる


urls.py


python3/src/todo/urls.py

from django.urls import path

from . import views

app_name = 'todo'
urlpatterns = [
path('', views.index, name='index'),
path('search', views.search, name='search'),
path('regist', views.regist, name='regist'),
path('edit/<int:todo_id>', views.edit, name='edit'),
]



views.py

次にtodo/views.pyを修正します。



  • / (index) は一覧画面へリダイレクトする


  • /search はTodoテーブルから全データ取得する


  • /edit はパラメータ(todo_id)があれば、DBからデータを取得する


  • /regist は入力チェックに問題がなければ登録し一覧画面へリダイレクト、問題があれば入力画面を表示する


python3/src/todo/views.py

from django.http import HttpResponseRedirect

from django.utils import timezone
from django.urls import reverse
from django.shortcuts import render
from todo.models import Todo, TodoForm

def index(request):
return HttpResponseRedirect(reverse('todo:search'))

def search(request):
list = Todo.objects.all()
params = { 'list' : list }
return render(request, 'todo/search.html', params)

def edit(request, todo_id):
todoForm = TodoForm()
if todo_id > 0:
todo = Todo.objects.get(todo_id=todo_id)
todoForm = TodoForm(instance=todo)

params = {
'todo_id' : todo_id,
'form' : todoForm
}
return render(request, 'todo/edit.html', params)

def regist(request):
todo_id = request.POST.get('todo_id')
todo = Todo()
if int(todo_id) > 0:
todo = Todo.objects.get(todo_id=todo_id)
todoForm = TodoForm(request.POST, instance=todo)
if todoForm.is_valid():
todoForm.save()
return HttpResponseRedirect(reverse('todo:search'))
else:
todo_id = request.POST.get('todo_id')
params = {
'todo_id' : todo_id,
'form' : todoForm,
}
return render(request, 'todo/edit.html', params)



templates

Templates(html)を作成します。

今回は(手を抜いたので)、search.htmledit.htmlの2つだけ作りました。

一覧画面は、全データを表示するだけです。


python3/src/todo/templates/todo/search.html

{% load bootstrap4 %}

<html>
<head>
{% bootstrap_css %}
</head>
<body>
<nav class="navbar navbar-light bg-light">
<form class="form-inline">
<a href="{% url 'todo:edit' '0' %}">
<button class="btn btn-outline-dark" type="button">TODO追加</button>
</a>
</form>
</nav>

<div class="container">
<table class="table mt-3">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">たいとる</th>
<th scope="col">こうしんび</th>
</tr>
</thead>
<tbody>
{% for t in list %}
<tr>
<th scope="row">
<a href="{% url 'todo:edit' t.todo_id %}" >
{{ t.todo_id }}
</a>
</th>
<td>{{ t.title }}</td>
<td>{{ t.update_date|date:"Y/m/d" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</body>
</html>


入力画面はbootstrap_formを使って楽をしました。


python3/src/todo/templates/todo/search.html

{% load bootstrap4 %}

<html>
<head>
{% bootstrap_css %}
</head>
<body>
<nav class="navbar navbar-light bg-light">
<form class="form-inline">
<a href="{% url 'todo:search' %}">
<button class="btn btn-outline-secondary" type="button">TODO一覧</button>
</a>
</form>
</nav>

<div class="container">
<form action="{% url 'todo:regist' %}" method="POST" class="form">
{% csrf_token %}
<input type="hidden" name="todo_id" value="{{ todo_id }}"/>
{% bootstrap_form form %}
<button type="submit" class="btn btn-primary">登録</button>
</form>
</div>
</body>
</html>


bootstrapを使えるようにapp/settings.pyを修正します。


python3/src/app/settings.py

INSTALLED_APPS = [

'todo.apps.TodoConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'bootstrap4', ## 追加
]


画面確認

これで完成です。

localhost:8000/todo にアクセスすればこんな画面が表示されるはずです。


さいごに

これで以下で同じ状態が(DBは空ですが)作れるようになりました。

便利ですね。

git clone https://github.com/ken-araki/todo-python-docker

cd todo-python-docker
docker-compose run python ./manage.py migrate
docker-compose up -d --build

ちなみにこの時点のソースはこちらにコミットされています。

https://github.com/ken-araki/todo-python-docker


参考

最後に参考にさせていただいたリンクを載せておきます。