Help us understand the problem. What is going on with this article?

Nginx + uWSGI + Python(Django)の環境をdockerで作成する

この記事はウェブクルー Advent Calendar 2019の16日目の記事です。
昨日は@Hideto-Kiyoshima-wcさんのScalaのOption/Either/Try超入門でした。

はじめに

株式会社ウェブクルーに新卒で入社して、2年目の@yagiyuuuuです。
現在、Nginx + uWSGI + Python(Django)のアプリ環境をDockerで作成して、開発をしています。
これから、Djangoでアプリ開発をする人の助けになればと思い、この記事を書きました。

Docker for Windowsのインストール

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

Djangoを動かす環境構築

ディレクトリ構成

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

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アプリを作成していく上で、自分好みの環境にしていってください!!

明日の記事は@yuko-tsutsuiさんです。
よろしくお願いします。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした