はじめに

Nginx + uWSGI + Python(Django)のアプリ環境をDockerで作成する方法を本記事にまとめました。

Docker for Windowsのインストール

コントロールパネルを開いて、「プログラムと機能」→「Windowsの機能を有効化または無効化」→「Hyper-V」にチェックが入っているか確認します。
チェックが入っていなかった場合は、チェックを入れてPCを再起動させて有効化させます。
次に「Docker Desktop for Windows」のインストールをします。
インストールはここ からできます。

Djangoを動かす環境構築

ディレクトリ構成

以下、構成でDjangoアプリを動かします。

ディレクトリ構成

Infrastrcuture作成

Alpineにpython + uWSGI、Nginxをインストールします。

docker-compose.yml作成

Nginxとpython + uWSGIのコンテナを作成します。
今回はログをdjango-sample配下に出力するようにしていますが、お好きなところにログを吐き出すように設定してください。

django-sample/docker-compose.yml

version: '2'
services:
  nginx:
    build: "./Infrastructure/nginx/"
    volumes:
      - ./logs/nginx:/var/log/nginx
    ports:
      - "80:80"
    networks:
      django-sample-network:
        ipv4_address: 172.23.0.4
  python:
    build: "./Infrastructure/python/"
    volumes:
      - ./Application/django-sample:/home/work/django-sample
      - ./logs/django:/home/work/django
      - ./logs/uwsgi:/home/work/uwsgi
    ports:
      - "8000:8000"
    networks:
      django-sample-network:
        ipv4_address: 172.23.0.5
networks:
  django-sample-network:
    driver: bridge
    ipam:
     driver: default
     config:
       - subnet: 172.23.0.0/24

Dockerfile作成

Nginx

django-sample/Infrastructure/nginx/Dockerfile

FROM nginx:1.13.1-alpine
COPY work/nginx.conf /etc/nginx
RUN apk --no-cache add tzdata && \
    cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    apk del tzdata
CMD ["nginx", "-g", "daemon off;"]

uWSGI

django-sample/Infrastructure/python/Dockerfile

FROM python:3.7
ENV LANG C.UTF-8
ENV TZ Asia/Tokyo

RUN mkdir /home/work
RUN mkdir /home/work/django
RUN mkdir /home/work/uwsgi
COPY work/ /home/work
WORKDIR /home/work
RUN pip install --upgrade pip
RUN pip install -r requirements.txt

CMD ["uwsgi", "--ini", "/home/work/uwsgi.ini"]

Nginxの設定

django-sample/Infrastructure/nginx/work/nginx.conf

worker_processes auto;
error_log /var/log/nginx/error_app.log;
events {
    worker_connections 1024;
}
http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  /var/log/nginx/access_app.log  main;
    sendfile            on;
    tcp_nopush          on;
    keepalive_timeout   120;
    proxy_read_timeout  120;
    proxy_send_timeout  120;
    types_hash_max_size 2048;
    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;
    server {
        listen       80 default_server;
        server_name  _;

        fastcgi_read_timeout 60s;

        client_max_body_size 1m;

        location ~ ^/app/ {
            add_header Cache-Control no-cache;
            include uwsgi_params;
            uwsgi_pass 172.23.0.5:8000;
            uwsgi_read_timeout 60s;
        }
    }
}

uWSGI + Djangoの設定

django-sample/Infrastructure/python/work/uwsgi.ini

[uwsgi]
chdir=/home/work/django-sample
module=django-sample.wsgi
master=True
vacuum=True
max-requests=5000
socket=:8000
py-autoreload=1
logto=/home/work/uwsgi/django-app.log
buffer-size=10240
log-format=%(addr) - %(user) [%(ltime)] "%(method) %(uri) %(proto)" %(status) %(size)`` "%(referer)" "%(uagent)"

django-sample/Infrastructure/python/work/requirements.txt

django==2.2
uwsgi==2.0.17.1

requirements.txtにインストールしたいモジュールを記載します。

.envファイル作成

django-sample/.env

COMPOSE_FILE=docker-compose.yml

Application作成

ここではアプリを作成することに焦点を当てていますので、 Djangoアプリの詳細に関しては、公式サイトなどをみていただきたいです。 また、__init__.pyと__pycache__にはコードを書きませんが、作成をしてください。 作成されていないとアプリが動かなくなってしまいます。

プロジェクト作成

django-sample/Application/django-sample/manage.py

#!/usr/bin/env python
import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django-sample.settings")
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)

django-sample/Application/django-sample/django-sample/settings.py

"""
Django settings for django-sample project.
Generated by 'django-admin startproject' using Django 2.0.3.
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 json
import traceback

# ログ出力で仕様するハンドラを指定する
LOG_HANDLER = ["app"]

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'ekf!&30u3&idt-qr3250(t+j#%@(vyxr02c-7fj!a81$!)#q=('

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

# 接続を許可するサーバのIPやドメインを設定する
# 何も設定していない場合は、ローカルホスト(localhost)からの接続のみ可能な状態
ALLOWED_HOSTS = ["localhost"]

# Application definition
# 「app」を追加。これを追加しないとtemplatetagsに定義したカスタムタグが認識されない
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app',
]

ROOT_URLCONF = 'django-sample.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            os.path.join(BASE_DIR, 'templates'),
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'django-sample.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}


# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/

#LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ja'

#TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Tokyo'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/
STATIC_URL = ''

LOGGING = {
    'version': 1,
    'formatters': {
        'app': {
            'format': '%(asctime)s [%(levelname)s] %(pathname)s:%(lineno)d %(message)s'
        }
    },
    'handlers': {
        'app': {
            'level': 'DEBUG',
            'class': 'logging.handlers.TimedRotatingFileHandler',
            'filename': '/home/work/django/app.log',
            'formatter': 'app',
            'when': 'D',        # 単位 Dは日
            'interval': 1,      # 何日おきか指定
            'backupCount': 30,  # バックアップ世代数
        }
    },
    'loggers': {
        'django': {
            'handlers': ['app'],
            'level': 'DEBUG',
            'propagate': True,
        },
        'django.server': {
            'handlers': ['app'],
            'level': 'DEBUG',
            'propagate': True,
        },
        'app': {
            'handlers': LOG_HANDLER,
            'level': 'DEBUG',
            'propagate': True,
        },
    },
}

# セッションエンジンの設定
# cookieを用いたセッションを使用する
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'

# ログイン状態の有効期限(秒)
# ここに指定した有効期限(秒)を超えるまでログイン状態を保つ事ができる
# セッション自体の有効期限はSESSION_COOKIE_AGE
# 8h * 60m * 60s
LOGIN_LIMIT = 28800

# セッションの有効期間(秒)
# 利用者毎にセッション有効期間を変えたい場合は、request.session.set_expiry(value)を用いる
SESSION_COOKIE_AGE = 1800

django-sample/Application/django-sample/django-sample/urls.py

"""django-sample URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.urls import path, include

urlpatterns = [
    path('app/', include("app.urls")),
]

django-sample/Application/django-sample/django-sample/wsgi.py


"""
WSGI config for django-sample project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/
"""

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django-sample.settings")

application = get_wsgi_application()

アプリ作成

djnago-sampleプロジェクト内にアプリを作成します。

django-sample/Application/django-sample/app/urls.py


from django.urls import path

from app.views.login import view as login_view

urlpatterns = [
    path("", login_view.top, name="login_top")
]

django-sample/Application/django-sample/app/views/login/view.py

from django.http import HttpResponse
from django.http.request import HttpRequest
from django.template import loader


def top(request: HttpRequest):
    template = loader.get_template("login/index.html")
    return HttpResponse(template.render({}, request))

画面表示させるテンプレート作成

django-sample/Application/django-sample/templates/login/index.html

Hello Django!!

コンテナを立ち上げる

docker-compose.ymlがある階層で以下コマンドを叩く

コンテナのビルド、起動

$ docker-compose up --build -d

-dをつけることでバックグラウンドで起動できる

コンテナを確認する

$ docker-compose ps

コンテナを削除する

$ docker-compose down

作成したアプリにアクセスする

コンテナを起動したら、http://localhost/app/にアクセスすると Hello Django!!が表示される

終わりに

Djangoアプリを作成していく上で、自分好みの環境にしていってください!!