Django2実践例 - 本番環境へのデプロイ

目次

Django2実践例 第一章 ブログアプリケーションの作成 Django2実践例 第二章 ブログ機能の拡張 Django2実践例 第三章 ブログ機能の追加 Django2実践例 第四章 ソーシャルネットワークの構築 Django2実践例 第五章 コンテンツ共有機能 Django2実践例 第六章 ユーザー行動の追跡 Django2実践例 第七章 ECサイトの構築 Django2実践例 第八章 支払いと注文の管理 Django2実践例 第九章 ストア機能の拡張 Django2実践例 第十章 オンライン教育プラットフォームの作成 Django2実践例 第十一章 コンテンツのレンダリングとキャッシュ Django2実践例 第十二章 APIの作成 Django2実践例 第十三章 本番環境へのデプロイ

第十三章 本番環境へのデプロイ

前章では、他のアプリケーションが私たちのWebアプリケーションと連携するためのRESTful APIを作成しました。本章では、Webサイトを正式に公開するための本番環境を構築する方法を学びます。主な内容は以下の通りです:

  • 本番環境の設定
  • カスタムミドルウェアの作成
  • カスタム管理コマンドの実装
  1. 本番環境の構築

ここでDjangoプロジェクトを本番環境にデプロイする時期です。以下の手順に従ってサイトを本番環境にデプロイします:

  1. 本番環境用のプロジェクト設定
  2. PostgreSQLデータベースの使用
  3. uWSGIとNGINXによるWebサーバーの構築
  4. 静的リソースの管理
  5. SSLを使用したサイトセキュリティの強化

1.1 複数環境向けの設定管理

実際のプロジェクトでは、異なる環境に対応する必要があります。通常、少なくともローカル開発環境と本番環境があり、テスト環境やステージング環境などの他の環境もあるかもしれません。異なる環境によって、一部の設定は共通ですが、一部は環境ごとに異なります。プロジェクト構造を変更せずに、異なる環境に適用できるようにプロジェクトを設定しましょう。

educa/educa/ディレクトリにsettingsディレクトリ(パッケージ)を作成し、settings.pyと同じレベルにします。次にsettings.pyファイルをbase.pyにリネームしてsettingsディレクトリに移動し、他のファイルを作成します。settings/ディレクトリは以下のようになります:

settings/
    __init__.py
    base.py
    local.py
    pro.py

これらのファイルの用途は以下の通りです:

  • base.py:基本設定ファイル。共通の設定を含み、元のsettings.pyです
  • local.py:ローカル環境のカスタム設定
  • pro.py:本番環境のカスタム設定

settings/base.pyを編集し、以下の行を見つけます:

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

これを以下の行に置き換えます:

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(os.path.join(__file__, os.pardir))))

settings.pyファイルをさらに一階層下に移動したため、BASE_DIRが正しいパスを指すようにする必要があります。そのため、親ディレクトリを指すos.pardirを使用して最終的なパスが依然として元のプロジェクトルディレクトリであるようにします。

settings/local.pyを編集し、以下のコードを追加します:

from .base import *

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

これはローカル環境を表す設定ファイルです。ここではbase.pyのすべての設定内容をインポートし、その後にDEBUGDATABASESの2つの設定を記述しています。これらの設定はbase.pyの設定を上書きし、このファイル内の設定になります。DEBUG設定とDATABASES設定は各設定ファイルで変更されるため、これらの設定をbase.pyから削除することもできます。

次にsettings/pro.pyを編集します:

from .base import *

DEBUG = False
ADMINS = (
    ('Antonio M', 'email@mydomain.com'),
)
ALLOWED_HOSTS = ['*']
DATABASES = {
    'default': {
    }
}

これは本番環境の設定ファイルです。内容を詳しく見ていきましょう:

  • DEBUGDEBUGFalseに設定することは、本番環境では必須です。これをオフにしないと、エラートレースや機密設定情報がすべての人に漏洩します。
  • ADMINSDEBUGFalseに設定されている場合、ビューが例外をスローすると、すべての情報がメール形式でADMINS設定にリストされているすべての人に送信されます。ここにある情報を自分の名前とメールアドレスに変更する必要があります(SMTPサーバーの設定も必要です)。
  • ALLOWED_HOSTS:Djangoはこの設定内のアドレスまたはホスト名にのみWebサービスを提供します。これはセキュリティ対策です。ワイルドカード*を使用して、すべてのホスト名またはIPアドレスで使用できるようにしています。後の設定でより詳細に制限します。
  • DATABASES:本番環境のデータベース設定。今は空にしておき、後で設定します。本番環境のデータベースは非本番環境のデータベースとは通常は分離されており、本番環境のデータベースは本番環境にいる場合にのみアクセスできることがあります。そのため、この項目は個別に設定する必要があります。

複数の環境に対応する必要がある場合、基本設定ファイルを作成し、各環境用に個別の設定ファイルを作成します。特定の環境用の設定ファイルは、基本設定を継承し、環境固有の設定を上書きするだけです。

現在設定ファイルを元のsettings.pyの位置に置いていないため、manage.pyを実行できません。settingsモジュールのパスを指定する必要があります。つまり、--settingsパラメータを使用するか、環境変数DJANGO_SETTINGS_MODULEを設定します。

システムのコマンドラインウィンドウを開き、以下を入力します:

export DJANGO_SETTINGS_MODULE=educa.settings.pro

このコマンドは、現在のセッションウィンドウにDJANGO_SETTINGS_MODULE環境変数を設定します。シェルを起動するたびにこのコマンドを実行したくない場合は、.bashrc.bash_profileなどのシェル設定ファイルにこのコマンドを追加できます。

システムに対して何も設定したくない場合は、サイトを起動する際に--settingsパラメータを追加する必要があります。以下のように:

python manage.py migrate --settings=educa.settings.pro

これで多環境対応の基本的な設定が完了しました。

1.2 PostgreSQLデータベースの使用

本書の大部分ではPythonに標準で付属しているSQLiteデータベースを使用してきました。ブログ全文検索ではPostgreSQLデータベースを推奨しただけです。SQLiteは軽量で使いやすいですが、本番環境にはあまりに簡素すぎるため、PostgreSQLやMySQL、Oracleのようなより強力なデータベースが必要です。PostgreSQLのインストールは第3章で既に説明したため、ここでは繰り返しません。

アプリケーション用のPostgreSQLユーザーを作成しましょう。システムのコマンドラインで以下のコマンドを入力します:

su postgres
createuser -d educa

システムはユーザーパスワードと権限の入力を求めます。パスワードを入力し、ユーザーに権限を与えた後、以下のコマンドを使用して新しいデータベースを作成します:

createdb -E utf8 -U educa educa

これにより、新しいデータベースが作成され、educaユーザーに割り当てられます。その後、settings/pro.pyを編集し、データベース設定を以下のように変更します:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'educa',
        'USER': 'educa',
        'PASSWORD': '',
    }
}

パスワード部分をeducaユーザーに設定したパスワードに置き換えます。新しいデータベースは空であるため、以下を実行します:

python manage.py migrate

次にスーパーユーザーを作成します:

python manage.py createsuperuser

1.3 デプロイ前チェック

Djangoはcheckコマンドを提供しており、いつでもプロジェクトをチェックできます。通常のチェックプロセスには、登録されたすべてのアプリケーションのチェック、エラーと警告情報の出力が含まれます。--deployパラメータを含むと、本番環境向けの追加チェックも実行されます。

システムのターミナルを開き、以下のコマンドを実行してチェックします:

python manage.py check --deploy --settings=educa.settings.*

サイトが正しく作成されている場合、エラー出力はありませんが、いくつかの警告情報が表示されます。これはサイトがチェックに合格したことを示していますが、これらの警告情報は処理する必要があります。サイトをより安全にするためです。本書ではこの内容には深入りしませんが、正式にデプロイする前には必ずデプロイ前チェックを実行することを忘れないでください。

1.4 WSGIプログラムによるDjangoサービス提供

Djangoの主要なデプロイプラットフォームはWSGIです。WSGIはWeb Server Gateway Interfaceの略称で、PythonプログラムにWebサービスを提供するための標準形式です。DjangoもPythonプログラムであるため、WSGIを介して外部サービスを提供する必要があります。

startprojectコマンドで新しいプロジェクトを作成すると、Djangoはプロジェクトディレクトリ内にwsgi.pyを作成します。このファイルには、Djangoアプリケーションにインターフェースを提供するWSGI呼び出し可能関数が含まれています。以前に使用したローカル環境の8000ポート開発サーバーであれ、正式な本番環境であれ、このインターフェースを介する必要があります。WSGIの詳細については、https://wsgi.readthedocs.io/en/latest/およびPythonのPEP333を参照してください。

1.5 WSGIのインストール

これまでの章まで、すべての開発はローカル環境で実行されているdjango開発サーバーで行ってきました。本番環境では、djangoサービスをデプロイするために実際のWebサーバーが必要です。

uWSGIは非常に高速なPythonアプリケーションWSGIサーバーで、WSGI標準を使用してPythonアプリケーションと通信します。uWSGIはHTTPリクエストをDjangoプログラムが処理できる形式に変換します。

uWSGIをインストールします:

pip install uwsgi==2.0.17

pipのインストール後、uWSGIがビルド(コンパイルインストール)されます。GCCやclangのようなCコンパイラが必要です。Linux環境では、以下のコマンドを入力できます:apt-get install build-essential

MacOS Xの場合、Homebrewを使用してインストールできます。コマンドを実行します:brew install uwsgi。Windowsにインストールするには、Cygwin https://www.cygwin.comが必要です。uWSGIのインストールはUNIXベースのオペレーティングシステムを推奨します。

UNIX環境でSuccessfully built uwsgiと表示されれば、uWSGIのインストールは成功です。uWSGIのドキュメントはhttps://uwsgi-docs.readthedocs.io/en/latest/で見つけることができます。

1.6 uWSGIの設定

コマンドラインでuWSGIを設定できます。システムのコマンドラインモードでeducaプロジェクトのルートディレクトリに移動し、以下を入力します:

sudo uwsgi --module=educa.wsgi:application --env=DJANGO_SETTINGS_MODULE=educa.settings.pro --master --pidfile=/tmp/project-master.pid --http=127.0.0.1:8000 --uid=1000 --virtualenv=/home/env/educa/

su権限が必要です。このコマンドにより、マシン上のuWSGIに以下の内容が設定されます:

  1. educa.wsgi:applicationを呼び出しインターフェースとして使用する
  2. 本番環境の設定ファイルをロードする
  3. virtualenvで設定された仮想環境を使用する。/home/env/educa/を実際の仮想環境のパスに置き換えます。仮想環境を使用していない場合は、この設定は不要です。

このコマンドをプロジェクトディレクトリ内で実行しない場合は、プロジェクトディレクトリを指定する追加のパラメータ--chdir=/path/to/educa/が必要です。/path/to/educa/をeducaプロジェクトの実際のパスに置き換えます。

ブラウザでhttp://127.0.0.1:8000/にアクセスすると(djangoサービスを起動する必要はありません)、サイトの内容が表示されますが、CSSスタイルは表示されず、画像も表示されません。これはuWSGIが静的ファイルサービスを提供するように設定されていないためです。

uWSGIは.ini設定ファイルを使用してカスタム設定を行うことができ、コマンドラインを使用するよりもはるかに便利です。educaプロジェクトのルートディレクトリに以下を作成します:

config/
    uwsgi.ini

uwsgi.iniを編集し、以下のコードを追加します:

[uwsgi]
# variables
projectname = educa
base = /home/projects/educa

# configuration
master = true
virtualenv = /home/env/%(projectname)
pythonpath = %(base)
chdir = %(base)
env = DJANGO_SETTINGS_MODULE=%(projectname).settings.pro
module = educa.wsgi:application
socket = /tmp/%(projectname).sock

この.iniファイルでは2つの変数を定義しています:

  • projectname:Djangoプロジェクトの名前、educaです
  • baseeducaプロジェクトの絶対パス、実際のプロジェクトパスに置き換えます

上記で定義したこれらの変数はカスタム変数であり、組み込みの名前と競合しない限り、任意の他の変数を定義できます。次に、具体的な設定の説明をします:

  • master:マスタープロセスを有効にする
  • virtualenv:仮想環境のアドレス、実際のパス(bin/activateを含まない)に置き換えます
  • pythonpath:Python PATHに追加するアドレス、通常はプロジェクトのルートディレクトリです
  • chdir:プロジェクトの実際のアドレス。uWSGIはアプリケーションをロードする前にこのパスに作業ディレクトリを変更します
  • env:環境変数、DJANGO_SETTINGS_MODULEに設定し、具体的なパスは本番環境の設定ファイルを指します
  • module:使用するWSGIモジュール、プロジェクト内のwsgi.pyの呼び出し関数を指します。applicationはプロジェクト内でのデフォルトの名前です
  • socket:このサービスをバインドするソケット。(ファイルソケットで、NGINXとの通信に使用します)

socketソケットはサードパーティのルーティングソフトウェア(NGINXなど)との通信に使用されます。コマンドラインモードで使用した--http 127.0.0.1:8000は、uWSGIがHTTPリクエストを直接受け取り、それらのリクエストをルーティングするように指定します。uWSGIをsocketとして起動する必要があります(.iniファイルの設定では--httpパラメータが設定されていません)、なぜなら私たちはNGINXをWebサーバーとして使用するためです。NGINXは先ほど設定したファイルソケットを介してuWSGIと通信します。

uWSGIの詳細な設定については、https://uwsgi-docs.readthedocs.io/en/latest/Options.htmlを参照してください。

これで設定ファイルを使用してuWSGIを起動できます(以前に実行していたuWSGIサービスを先に停止してください):

uwsgi --ini config/uwsgi.ini

これを実行すると、http://127.0.0.1:8000/にブラウザからアクセスできなくなります。なぜなら、この時点ではuWSGIがHTTPポートではなくファイルソケットをリッスンしているためです。本番環境の設定をさらに完成させる必要があります。

1.7 NGINXの設定

Webサービスを起動する際、明らかに動的コンテンツサービスを提供する必要がありますが、CSS、JavaScriptファイル、画像などの静的ファイルサービスも必要です。uWSGIが静的ファイルを管理すると、HTTPリクエストに不必要なオーバーヘッドが追加されるため、uWSGIの前にNGINXのようなWebサーバーを追加するのが最善です。

NGINXは高同時接続、低メモリ使用量のWebサーバーであり、リバースプロキシ機能も備えています。つまり、HTTPリクエストを受け取り、それを異なるバックエンドにルーティングします。通常、CSS、JavaScript、画像などの静的ファイルを高速かつ効率的に提供するためのWebサーバー(NGINX)が必要であり、動的リクエストをuWSGIに転送します。NGINXを使用することで、リバースプロキシ機能を設定してより良いWebサービスを提供することもできます。

NGINXをインストールするには、以下のコマンドを使用できます:

sudo apt-get install nginx

MacOS Xを使用している場合は、brew install nginxでインストールできます。WindowsのNGINXはhttps://nginx.org/en/download.htmlからダウンロードできます。

1.8 本番環境の構造

以下の図は、最終的に設定する本番環境の構造を示しています:

ブラウザがHTTPリクエストを開始すると、以下のことが起こります:

  1. NGINXがHTTPリクエストを受け取る
  2. リクエストが静的ファイルの場合、NGINXが直接サービスを提供する。動的ページの場合、NGINXはSOCKを介してuWSGIと通信し、リクエストをuWSGIに処理させる
  3. uWSGIはリクエストをDjangoバックエンドに処理させ、返されたレスポンスはNGINXに渡され、NGINXはブラウザに返します。

1.9 NGINXの設定

config/ディレクトリにnginx.confファイルを作成し、以下のコードを追加します:

# the upstream component nginx needs to connect to
upstream educa {
    server unix:///tmp/educa.sock;
}
server {
    listen 80;
    server_name www.educaproject.com educaproject.com;
    location / {
        include /etc/nginx/uwsgi_params;
        uwsgi_pass educa;
    }
}

これはNGINXの基本設定です。educaという名前のupstreamを作成し、uWSGIが使用するソケット名を指定します。次にserverディレクティブを使用し、その設定は以下の通りです:

  • listen 80はNGINXが80ポートをリッスンすることを示します
  • ホスト名をwww.educaproject.comeducaproject.comに設定します。NGINXはこれらのホストアドレスにサービスを提供します
  • locationパラメータを設定し、'/'パスの下にあるすべてのURLを上記のupstream educa(つまりuWSGIのソケット)に転送します。また、NGINXに付属のuwsgiと連携するためのパラメータ設定も含まれています。

NGINXには多くの複雑な設定があり、ドキュメントはhttps://nginx.org/en/docs/を参照してください。

NGINXの主な設定ファイルは/etc/nginx/nginx.confにあり、このファイルは/etc/nginx/sites-enabled/の下にあるすべての設定ファイルを含みます。NGINXが先ほど作成した設定ファイルを使用するようにするには、システムのコマンドラインウィンドウでソフトリンクを作成します:

sudo ln -s /home/projects/educa/config/nginx.conf /etc/nginx/sites-enabled/educa.conf

/home/projects/educa/を実際の絶対パスに置き換えます。/sites-enabled/ディレクトリがない場合は、先に手動で作成する必要があります。

まだuWSGIを実行していない場合は、システムのコマンドラインウィンドウでeducaプロジェクトのルートディレクトリでuWSGIを先に実行します:

uwsgi --ini config/uwsgi.ini

現在のウィンドウはuWSGIによって占有されるため、別のコマンドラインウィンドウを開き、以下を実行します:

service nginx start

カスタムドメインを使用しているため、/etc/hostsを変更する必要があります。以下の2行を追加します:

127.0.0.1 educaproject.com
127.0.0.1 www.educaproject.com

これにより、これらのドメインはすべてローカルループアドレスにルーティングされます。私たちは本機から本機にアクセスしているため、HOSTSを変更する必要があります。実際の本番環境では、この変更は不要です。なぜなら、本番環境には固定のIP、ドメイン、および対応するDNS解決があるからです。

ブラウザを開き、http://educaproject.com/と入力すると、サイトが表示されますが、すべての静的ファイルはまだ読み込まれていません。心配しないでください、本番環境の設定はすぐに完了します。

システムがCentOS 7の場合、ここでは502エラーが表示されます。/var/log/nginx/error.logを確認し、その中のエラーが[crit] 4036#4036: *1 connect() to unix:///tmp/educa.sock failed (13: Permission denied)である場合、まず/usr/sbin/sestatusを実行してSELINUXの状態を確認します。有効になっている場合は、SELINUXの設定を編集して無効にします。以下のように:

vi /etc/selinux/config

#SELINUX=enforcing
SELINUX=disabled

その後rebootでシステムを再起動する必要があります。その後、サイトが正常に表示されるはずです。

セキュリティのために、settings/pro.pyに戻り、ALLOWED_HOSTSをNGINX設定ファイルの2つのドメ名に設定します:

ALLOWED_HOSTS = ['educaproject.com', 'www.educaproject.com']

これでDjangoはこれらのホスト名にのみサービスを提供します。ALLOWED_HOSTSの詳細については、https://docs.djangoproject.com/en/2.0/ref/settings/#allowed-hostsを参照してください。

1.10 NGINXによる静的ファイルとメディアリソースの提供

NGINXは静的ファイルの提供が非常に高速です。以前はすべてのアドレス転送をuWSGIに任せていました。今では、すべての静的ファイルをNGINXによって提供するようにします。つまり、すべてのCSS JSファイルとユーザーがアップロードしたメディアファイルをNGINXにプロキシさせます。

settings/base.pyを編集し、以下の行を追加します:

STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

この行はサイトの静的ファイルを格納するアドレスを示します。以前にpython manage.py collectstaticを使用したことを覚えていますか?今、すべての静的ファイルをこのディレクトリに収集する必要があります。コマンドラインで以下を入力します:

python manage.py collectstatic --settings=educa.settings.pro

以下の出力が表示されます:

160 static files copied to '/educa/static'.

静的ファイルディレクトリの設定が完了したので、このディレクトリをNGINXに設定する必要があります。config/nginx.confを編集し、serverディレクティブの後の中括弧内に以下の内容を追加します:

location /static/ {
    alias /home/projects/educa/static/;
}
location /media/ {
    alias /home/projects/educa/media/;
}

/home/projects/educa/static//home/projects/educa/media/をプロジェクトの実際のstaticおよびmediaディレクトリの絶対パスに置き換えます。これらの2つのパラメータの説明は以下の通りです:

  • /static/:これはDjangoで設定されたSTATIC_URLパスです。NGINXが/static/パスのリクエストを見ると、この設定に対応するパスから必要なファイルを探します。
  • /media/:これはDjangoで設定されたMEDIA_URLパスです。NGINXが/media/パスのリクエストを見ると、この設定に対応するパスから必要なファイルを探します。

設定ファイルが有効になるように、NGINXサービスを再起動します:

service nginx reload

ブラウザでhttp://educaproject.com/を開くと、サイト全体と静的リソースが正しく表示されていることがわかります。サイトの静的ファイルリクエストに対して、NGINXはuWSGIをバイパスし、ファイルを直接ブラウザに返します。

これで本番環境が基本的に設定されました。サイトは現在、本番環境で実行されていると言えます。

1.11 SSLセキュア接続の使用

基本的な本番環境の設定が完了した後、次のトピックはサイトのセキュリティです。Secure Sockets Layerは、Webセキュア接続サービスを提供するための規格として徐々に標準化されています。正式なWebサイトにはHTTPSプロトコルを使用することを強くお勧めします。今すぐNGINXでSSL認証を設定してサイトをより安全にしましょう。

1.11.1 SSL認証の作成

educaプロジェクトのルートディレクトリにsslディレクトリを作成し、opensslを使用してSSL証明書を生成します:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ssl/educa.key -out ssl/educa.crt

このコマンドで365日有効な2048ビットのSSL証明書を生成し、システムはいくつかの情報の入力を求めます:

Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []: educaproject.com
Email Address []: email@domain.com

最も重要なのはCommon Nameです。ここにはホストドメイン名を入力する必要があります:educaproject.comを使用します。

その後、ssl/ディレクトリに2つのファイルが生成されます。educa.keyは秘密鍵で、educa.crtは実際のSSL証明書です。

1.11.2 NGINXでのSSL使用の設定

config/nginx.confを編集し、server設定に以下の内容を追加します:

server {
listen 80;
listen 443 ssl;
ssl_certificate /home/projects/educa/ssl/educa.crt;
ssl_certificate_key /home/projects/educa/ssl/educa.key;
server_name www.educaproject.com educaproject.com;
# ...
}

パスをSSL証明書の実際の絶対パスに変更します。

この設定により、NGINXは80ポート(HTTPプロトコル)と443ポート(HTTPSプロトコル)の両方をリッスンし、SSL認証情報ssl_certificateと対応する秘密鍵ssl_certificate_keyを指定します。

これでNGINXサービスを再起動し、https://educaproject.com/にアクセスすると、以下のような警告が表示されます:

この警告はブラウザによって異なります。現在のサイトが信頼できる検証方法を使用していないため、ブラウザがサイトが安全かどうかを判断できないという警告です。これは、使用しているSSL証明書が私たち自身で発行したものであり、信頼できる機関(Certification Authority)から取得したものではないためです。実際の公開ドメインを取得したら、信頼できる証明書発行機関にSSL証明書を申請できます。そうすれば、ブラウザはサイトのHTTPS認識を確認できます。

実際のサイトに証明書を申請するには、Linux財団のLet's Encryptプロジェクトを使用できます。これは無料でSSL証明書を取得および更新することに取り組む計画で、そのサイトはhttps://letsencrypt.org/にあります。

「Add Exception」ボタンをクリックすると、ブラウザにこのサイトを信頼できることを知らせることができます。この時、ブラウザの表示は以下のようになるかもしれません:

小さな鍵ボタンをクリックすると、SSLの詳細情報が表示されます。

1.12 DjangoでのSSL使用の設定

DjangoにもSSL対応の設定があります。settings/pro.pyを編集し、以下のコードを追加します:

SECURE_SSL_REDIRECT = True
CSRF_COOKIE_SECURE = True

これらの設定の意味は以下の通りです:

  • SECURE_SSL_REDIRECT:すべてのHTTPリクエストがHTTPSにリダイレクトされる必要があるかどうか
  • CSRF_COOKIE_SECURE:CSRF攻撃を防ぐために暗号化されたcookieを設定するかどうか

これで、効率的にWebサービスを提供する本番環境の設定が完了しました。

  1. カスタムミドルウェア

以前にミドルウェアMIDDLEWAREの設定について学びました。この設定にはプロジェクトで使用されるすべてのミドルウェアが含まれます。ミドルウェアについて、リクエスト/レスポンスの過程でフックを提供する低レベルのプラグインシステムと考えることができます。各ミドルウェアは特定の動作を担当し、HTTPリクエストとレスポンスの過程で実行されます。

オーバーヘッドの大きいミドルウェアを追加しないでください。なぜなら、ミドルウェアはプロジェクトのすべてのリクエストとレスポンスの過程で実行されるからです。

HTTPリクエストが入ってくる場合、ミドルウェアはMIDDLEWARE設定で上から下の順序で実行され、HTTPレスポンスが生成されて送信される過程では、設定で下から上の順序で実行されます。

標準に準拠した関数は、settings.pyに登録されたミドルウェアとして機能できます。以下のような関数がミドルウェアとして登録できます:

def my_middleware(get_response):
    def middleware(request):
        # 各HTTPリクエストに対して、ビューとその後のミドルウェアが実行される前のコード
        response = get_response(request)
        # 各HTTPリクエストとレスポンスに対して、ビュー実行後のコード
        return response
    return middleware

ミドルウェアファクトリ関数はget_response呼び出し可能オブジェクトを受け取り、ミドルウェア関数を返します。ミドルウェアはリクエストを受け取り、レスポンスを返します。これはビューに似ています。ここでのget_responseは次のミドルウェアか、自分がミドルウェアリストの最後の場合はビュー名になります。

いかなるミドルウェアもget_response呼び出し可能オブジェクトを呼び出す前にレスポンスを返した場合、ミドルウェアチェーンの処理がショートします:その後のミドルウェアは実行されなくなり、このレスポンスは同レベルのミドルウェアから上に返し始めます。

したがって、MIDDLEWARE設定内のミドルウェアの順序は非常に重要です。なぜなら、ミドルウェアは上下のミドルウェアのデータに依存して動作するからです。

MIDDLEWAREにミドルウェアを追加する際、必ず正しい位置に配置する必要があります。繰り返しになりますが、ミドルウェアはHTTPリクエストが入ってくる際には上から下に実行され、HTTPレスポンスが出ていく際には下から上に実行されます。

2.1 カスタムミドルウェアの作成

カスタムミドルウェアを作成し、カスタムのサブドメインを介してコースリソースにアクセスできるようにします。例えば、コースを表示するURL:https://educaproject.com/course/django/は、サブドメインdjango.educaproject.comを介してアクセスできます。これにより、ユーザーはサブドメインをショートカットとして使用してコースにすばやくアクセスでき、このパスを覚えやすくなります。このサブドメインへのすべてのリクエストは、実際のeducaproject.com/course/django/URLにリダイレクトされます。

ビュー、モデル、フォームなどのコンポーネントと同様に、ミドルウェアもプロジェクト内のどこにでも書くことができます。ミドルウェアを書くには、アプリケーションディレクトリ内にmiddleware.pyファイルを作成することをお勧めします。

coursesアプリケーションディレクトリ内にmiddleware.pyファイルを作成し、以下のコードを記述します:

from django.urls import reverse
from django.shortcuts import get_object_or_404, redirect
from .models import Course

def subdomain_course_middleware(get_response):
    """
    コース用のサブドメインを提供
    """

    def middleware(request):
        host_parts = request.get_host().split('.')
        if len(host_parts) > 2 and host_parts[0] != 'www':
            # 指定されたサブドメインでコースオブジェクトをクエリ
            course = get_object_or_404(Course, slug=host_parts[0])
            course_url = reverse('course_detail', args=[course.slug])
            # サブドメインリクエストを実際のURLにリダイレクト
            url = '{}://{}{}'.format(request.scheme, '.'.join(host_parts[1:]), course_url)
            return redirect(url)
        response = get_response(request)
        return response
    return middleware

HTTPリクエストが入ってくる際、このミドルウェアは以下のタスクを実行します:

  1. このHTTPリクエストのドメインを取得し、それをいくつかの部分に分割します。例えばmycourse.educaproject.comはリスト['mycourse', 'educaproject', 'com']に分割されます
  2. このドメインにサブドメインが含まれているかどうかを確認し、分割後のドメインが2つ以上の要素を含むかどうかを判断します。含まれている場合は最初の要素(サブドメイン)を取り出し、そのドメインがwwwでない場合は、slugでクエリして対応するコースオブジェクトを取得します
  3. 対応するコースが見つからない場合は404エラーを返し、見つかった場合は対応する正規化されたURLにリダイレクトします

settings/base.pyを編集し、カスタムミドルウェアをMIDDLEWARE設定に追加します:

MIDDLEWARE = [
    # ......
    'courses.middleware.subdomain_course_middleware',
]

ALLOWED_HOSTSのドメイン設定も確認する必要があります。ここでは、任意のeduproject.comのサブドメインを許可するように設定します:

ALLOWED_HOSTS = ['.educaproject.com']

ALLOWED_HOSTS.で始まるドメイン、例えば.educaproject.comは、educaproject.comおよびすべてのeducaproject.comのサブドメイン(例:course.educaproject.comdjango.educaproject.com)に一致します。

2.3 NGINXのサブドメイン設定

config/nginx.confを編集し、以下の行:

server_name www.educaproject.com educaproject.com;

以下のように変更します:

server_name *.educaproject.com educaproject.com;

ワイルドカード設定を追加することで、NGINXがすべてのサブドメインをプロキシできるようにします。ミドルウェアをテストするには、etc/hostsに関連するコンテンツを設定する必要もあります。例えば、サブドメインdjango.educaproject.comをテストするには、以下の行を追加します:

127.0.0.1 django.educaproject.com

次にサイトをhttps://django.educaproject.com/で起動すると、ミドルウェアがこれをhttps://educaproject.com/course/django/にリダイレクトしていることがわかります。

  1. カスタム管理コマンドの実装

Djangoは、アプリケーションがmanage.py管理ツールにカスタム管理コマンドを登録することを許可します。管理コマンドとは、manage.pyを使用して実行されるコマンドのことです。例えば、第9章で使用したmakemessagescompilemessagesコマンドです。

管理コマンドは、Pythonモジュールで構成され、そのモジュールにはCommandクラスが含まれています。このCommandクラスはdjango.core.management.base.BaseCommandまたはBaseCommandのサブクラスを継承しています。引数とオプションを含む単純なカスタムコマンドを作成できます。

INSTALLED_APPS内に登録されている各アプリケーションについて、Djangoはアプリケーションディレクトリの下のmanagement/commands/ディレクトリを検索し、見つかった各コマンドモジュールは同名のコマンドとして登録されます。

カスタム管理コマンドの詳細については、https://docs.djangoproject.com/en/2.0/howto/custom-management-commands/を参照してください。

学生に少なくとも1つのコースを選択するように促すコマンドを作成します。このコマンドは、登録してから一定期間経過したがまだいずれかのコースに登録していない学生全員にメールを送信します。

studentアプリケーションの下に以下のディレクトリとファイル構造を作成します:

management/
    __init__.py
    commands/
        __init__.py
        enroll_reminder.py

enroll_reminder.pyを編集し、以下のコードを追加します:

import datetime
from django.conf import settings
from django.core.management.base import BaseCommand
from django.core.mail import send_mass_mail
from django.contrib.auth.models import User
from django.db.models import Count

class Command(BaseCommand):
    help = 'Sends an e-mail reminder to users registered more than N days that are not enrolled into any courses yet'

    def add_arguments(self, parser):
        parser.add_argument('--days', dest='days', type=int)

    def handle(self, *args, **options):
        emails = []
        subject = 'Enroll in a course'
        date_joined = datetime.date.today() - datetime.timedelta(days=options['days'])
        users = User.objects.annotate(course_count=Count('courses_joined')).filter(course_count=0,
                                                                               date_joined__lte=date_joined)
        for user in users:
            message = "Dear {},\n\n We noticed that you didn't enroll in any courses yet. What are you waiting for?".format(
                user.first_name)
            emails.append((subject, message, settings.DEFAULT_FROM_EMAIL, [user.email]))
        send_mass_mail(emails)
        self.stdout.write('Sent {} reminders'.format(len(emails)))

これはenroll_reminderコマンドです。説明は以下の通りです:

  • CommandクラスはBaseCommandクラスを継承します
  • Commandクラスにはhelpプロパティが含まれ、コマンドにヘルプ情報を提供します。python manage.py help enroll_reminderを実行すると、この情報が表示されます
  • add_arguments()は使用可能なパラメータを設定するために使用されます。ここでは--daysパラメータを設定し、そのタイプを整数型に指定します。このパラメータは、メールを送信する学生をフィルタリングするために使用されます
  • handle()メソッドはコマンドの実際のビジネスロジックを定義します。ここではコマンドラインから解析されたdaysプロパティを取得し、登録時間がその日数を超過するユーザーをクエリし、グループ化計算を使用してこれらのユーザーの選択数を計算し、未選択コースのユーザーを選び出します。次に、emailsリストを使用して送信するすべてのメールを記録し、最後にsend_mass_mail()メソッドを使用してメールを送信します。これにより、1つのSMTP接続で大量のメールを送信でき、毎回新しいSMTP接続を開く必要がありません。

上記のコードを記述した後、システムのコマンドラインでコマンドを実行します:

python manage.py enroll_reminder --days=20

まだSMTPサーバーを設定していない場合は、第2章の内容を参照してください。実際にSMTPサーバーがない場合は、settings.pyに以下を追加できます:

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

これにより、Djangoはメール内容をコンソールに表示し、実際にメールを送信しません。

このコマンドを毎朝8時に実行させることもできます。UNIXベースのオペレーティングシステムを使用している場合は、システムのコマンドラインモードでcrontab -eと入力してcrontabを編集し、以下の行を追加します:

0 8 * * * python /path/to/educa/manage.py enroll_reminder --days=20 --settings=educa.settings.pro

/path/to/educa/manage.pyを実際のmanage.pyの絶対パスに置き換えます。cronの使用に慣れていない場合は、http://www.unixgeeks.org/security/newbie/unix/cron-1.htmlを参照してください。

Windowsを使用している場合は、システムのタスクスケジュール機能を使用できます。詳細はhttps://docs.microsoft.com/ja-jp/windows/desktop/TaskSchd/task-scheduler-start-pageを参照してください。

もう1つの方法は、Celeryを使用して定期的にタスクを実行することです。第7章でCeleryを使用しました。Celery beatスケジューラを使用して定期的に実行される非同期タスクを設定できます。詳細はhttps://celery.readthedocs.io/en/latest/userguide/periodic-tasks.htmlを参照してください。

cronまたはWindowsのタスクスケジュールで実行される個別のスクリプトについては、すべてカスタム管理コマンドの方法を使用して実行できます。

Djangoは、Pythonコードで管理コマンドを実行する方法も提供しています。Pythonコードで管理コマンドを実行できます。例えば:

from django.core import management
management.call_command('enroll_reminder', days=20)

プログラムがこの行に到達すると、このコマンドが実行されます。これで、アプリケーション用の管理コマンドをカスタマイズし、計画して実行できるようになりました。

まとめ

この章では、uWSGIとNGINXを使用して本番環境を構成し、カスタムミドルウェアと管理コマンドを実装しました。

これで本書は終了です。おめでとうございます。本書は、実際のプロジェクトを作成し、他のソフトウェアをDjangoと統合する方法を通じて、Djangoを使用してWebアプリケーションを構築するために必要なスキルを学習するためのガイドでした。単純なプロジェクトプロトタイプから大規模なWebアプリケーションまで、あなたはそれらをDjangoを使用して作成する能力を備えています。

今後のDjangoの旅を楽しみにしてください!

その他の興味深い書籍

この本が役立つと感じた場合、以下の書籍にも興味があるかもしれません。

Python Programming Blueprints

Django RESTful Web Services

タグ: Django デプロイ 本番環境 uWSGI nginx

6月5日 21:06 投稿