DjangoでgunicornとNginxを使う(Nginxその3)

VPSサーバーのDjangoプロジェクトを、まずNginxだけで公開します。次にgunicornも連携させます。なお、NginxとDjango(gunicorn)はソケットではなく、httpで接続します。STATIC_URL等の意味も説明します。

実行環境はConoHaのVPSサーバーを用い、OSはUbuntu20.04 LTSです。

1.Nginxの設定の概要

この記事は、以下の記事の続編です。Nginxのインストールは完了し、Nginxの設定ファイルの記述が終了している状態であることを前提としています。

Nginxでまず静的ファイルのみ表示してみる(Nginxその2)

なお、上の記事で設定した設定ファイルは以下の通りです。ディレクトリ「/etc/nginx/sites-available/」の中にserver0、dj_testという2つの設定ファイルを用意しました。

#設定ファイル名:server0
server {
    listen 80 default_server;
    server_name _;
    #すべてのリクエストに対応する。
    retrun 444;  #ステータスコード444を返す。
}
#設定ファイル名:dj_test
server {
	listen 80;
  #ポート番号の指定http
	server_name ***.***.***.***;
  #IPアドレス(ドメイン)の指定
	root /usr/share/nginx/html;
  #ドキュメントルートの指定

	# index.html等はファイル名の指定なしで実行
	index index.html index.htm index.nginx-debian.html;
    
	location /static {
                #.htmlは省略できる
		try_files $uri $uri/ $uri.html =404;
	}

	location /media {

	}

    #以下、この記事では説明しませんが、djangoにデータを引き渡す設定です。
    location / {
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X_Forwarded-Proto $scheme;

            proxy_pass http://127.0.0.1:8000;
        }
}

2.VPSサーバーの構成の概要

VPSサーバー構成の最終目標は下図のとおりです。

なお、下の図で、クライアントとNginxの接続は最終的にhttpではなくhttpsに変更することになりますが、NginxとDjngo(gunicorn)の接続は速度を優先してhttpのまま変更しません。NginxとDjango(gunicorn)の接続はサーバー内部でのデータのやり取りであり、暗号化する必要がないためです。

01_Nginx_gunicorn_Django

3.httpによる接続とソケットによる接続

gunicornを介してNginxとgunicornを接続する場合には、httpによる接続の他にソケットにより接続する方法があります。サイト上の情報はソケットによる接続の方が多いようです。

2つの方法を比較した情報は少ないですが、以下の参考記事ではソケットの方が速いがhttpの方が構成変更の自由度が高いと解説されています。

NginxとGunicornの接続をソケットからHTTPに変更した

私の記事では設定の簡便さと構成変更の自由度を優先してhttp接続による設定方法を紹介したいと思います。

4.Djangoへの処理の引き渡し

httpで接続する方法は、gunicornなしでもNginxからDjangoに処理を引き渡すことが可能です。まずは、gunicornを介さずにNginxだけでDjangoのプロジェクトを公開したいと思います。

Djangoの設定では欲張らずに一つ一つ設定を固めていくのがコツです。

さて、Nginxの設定ファイルの中で、NginxからDjangoにデータを渡す設定部分は以下の通りです。

IPアドレス「127.0.0.1」はお馴染みの「localhost」のことであり、いずれも自分自身をあらわします。最後の行は、クライアントからのリクエストをhttp接続によりサーバー自身の8000番ポートに渡すことを意味しています。サーバー内のデータのやりとりなのでファイアフォール無効化の手続きは不要です。

server {
    location / {
            proxy_set_header Host $http_host;  #ホスト名
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;  #送信元アドレス
            proxy_set_header X_Forwarded-Proto $scheme;  #URLスキーム

            proxy_pass http://127.0.0.1:8000;
        }
}

また、接続先だけではなくホスト名($http_host)、送信元アドレス($proxy_add_x_forwarded_for)、URLスキーム($scheme)などの情報も渡す必要があります。$http_host、$proxy_add_x_forwarded_for、$schemeはそれ自身が意味を持った変数名なので、このまま記述します。

つまり、あまり深く考えず、このままコピペするだけでO.K.です。

5.Djangoのsettings.pyの設定

Djangoの設定は、Djangoプロジェクトの中にあるsetings.pyで行います。もっとも重要な設定は「DEBUG」をFalseに書き直すことです。「DEBUG」がTrueではデバッグ用の画面が表示されてしまい、サーバー内の細かい設定が全て第3者に公開されてしまいます。

なお、DEBUGをFalseにした場合は、ALLOWED_HOSTSを指定し、許可するドメイン(IPアドレス)を指定する必要があります。つまり、ALLOWED_HOSTS = []のままではエラーになります。

ALLOWED_HOSTSの指定で必須なのはVPSサーバーのドメイン(IPアドレス)です。VPSサーバーのドメイン(IPアドレス)を指定することで、Djangoのプロジェクトを公開することができます。

'localhost'と'127.0.0.1'は、それぞれブラウザから「http://localhost:8000/」や「http://127.0.0.1:8000/」にアクセスしたい場合に必要に応じて追加します。なお、'localhost'の指定だけでは「http://127.0.0.1:8000/」にアクセスできません。

#DEBUGのデフォルトはDEBUG = True
DEBUG = False  

#ALLOWED_HOSTSのデフォルトはALLOWED_HOSTS = [] 
ALLOWED_HOSTS = ['vpsサーバーのドメイン', 'localhost', '127.0.0.1']  

ALLOWED_HOSTSの設定では、例えば以下のように設定すれば、第3者に公開することなく「http://localhost:8000」により、Djangoプロジェクトの内容を見ることができます。つまり、公開前に最終的な確認を行うことができます。

DEBUG = False  
ALLOWED_HOSTS = ['localhost']  

注意しなければならないのは以下の記述です。localhostにしか許可していないので、DEBUG = Trueにしても大丈夫なように思えますが、第3者がVPSのドメインでアクセスしたときに、アクセスできない理由をデバッグ情報として表示してしまいます。サーバーの詳細な情報が開示される可能性があるので、注意が必要です。やはり、Trueの設定は危険です。

DEBUG = True 
ALLOWED_HOSTS = ['localhost']  

また、例えば上記の設定において、Nginxが起動せずに「python manage.py runserver」でDjangoを実行した場合は、第3者のサーバードメインへのアクセスに対して「申し訳ございません。このページに到達できません」と表示されるだけです。しかし、NginxをWebサーバーとしてDjangoのプロジェクトを公開したときはサーバーの詳細デバッグ情報が開示されてしまいます。

このようにDjangoだけのときの表示で安心していると、思わぬ形で情報が漏れてしまう可能性があります。

つまり、VPSサーバー上でDjangoのプロジェクトを作成したら、まず最初にsettings.pyのDEBUGをFaulseに設定しなおすことを習慣にした方がよいと思います。

6.Djangoのプロジェクト作成から公開まで

settings.pyのDEBUGとALLOWED_HOSTSの設定が終了すると、公開に必要な設定は終了です。他にも細かい設定はありますがあとで説明します。ひとまず「hello! hello!」と表示するだけの簡単なプロジェクトを作成し公開してみましょう。

ここで、私が失敗したのは、おなじみのロケットの絵を表示しようとしたことです。ロケットの絵はsettings.pyのDEBUGの設定がFaulseの場合は表示されません。

また。Djangoのプロジェクトに自作のアプリケーションが追加されている状態では、DEBUGがTrueであっても表示されません。

そこで、少し面倒ですが「Django-admin startprojyekuto ~」で作ったデフォルト状態のプロジェクトを修正し、簡単なアプリケーションを作成した上でDjangoプロジェクトを公開したいと思います。

手順は以下の通りです。まず、仮想環境でDjangoを実行している場合には仮想環境に切り替えます。次にtest_appという名称のプロジェクトを作成し、helloというアプリケーションを作成します。

$ . 仮想環境名/bin/activate  #仮想環境へ切り替え(ubuntu)
$ django-admin startproject test_app
  #プロジェクトの作成
$ cd test_app
$ python manage.py startapp hello  #アプリケーションの作成

次にtest_app/test_app/settings.pyのDEBUGとALLOWED_HOSTSの設定を書き換えます。

DEBUG = True 
ALLOWED_HOSTS = ['vpsサーバーのドメイン', 'localhost']  

Djangoプロジェクトの/test_app/hello/views.pyを以下のコードに書き換えます。

from django.shortcuts import render
from django.http import HttpResponse

def index(request):
    return HttpResponse('Hello! Hello!')

/test_app/test_app/urls.pyを以下のコードに書き換えます。これで、Djangoの自作プロジェクトは完成です。

from django.contrib import admin
from django.urls import path
import hello.views as hello

urlpatterns = [
    path('admin/', admin.site.urls),
    path('hello/' hello.index),
]

ターミナルでNginxを起動し、Djangoのプロジェクトを起動します。

$ sudo systemctl start nginx.service  #Nginxの起動
$ python3 manage.py runserver

ブラウザでDjangoのプロジェクトにアクセスします。

http://ドメイン名/hello/

すると、Djangoのプロジェクトが表示されます。

02_Django_test_app

7.STATIC_ROOT、STATIC_URL、MEDIA_ROOTの設定

settings.pyでは、STATIC_ROOT、STATIC_URL、MEDIA_ROOTにより、静的ファイル、メディアファイルの設定を行います。

STATIC_URL = '/static/'
STATIC_ROOT = '/usr/share/nginx/html/static'
MEDIA_ROOT = '/usr/share/nginx/html/media'

STATIC_ROOT、MEDIA_ROOTにおいて絶対パスで指定する場合には、一番最初の「/」を忘れないようにしましょう。

(1) STATIC_ROOT

STATIC_ROOTの設定では、Nginxの設定ファイルの「location /static { }」で指定したディレクトリと同じディレクトリを指定します。

公開前のDjangoプロジェクトは自分のPC等で開発する場合は、staticファイルはプロジェクト内のディレクトリstaticに保存します。しかし、公開するDjangoの静的ファイルはプロジェクト外のディレクトリにまとめられ、Nginxからアクセスすることにより高速化します。

つまり、プロジェクト内の静的ファイルをプロジェクト外のディレクトリに移動(複写)する必要があるのです。

プロジェクト外のディレクトリとは、具体的にはNginxの設定ファイルの「location /static { }」に関連づけられたディレクトリです。以下の例では「/usr/share/nginx/html/static/」です。

server {
	root /usr/share/nginx/html;
	location /static {
	}
}

この静的ファイルの複写を自動で行ってくれるのがcollectstaticコマンドであり、Djangoのプロジェクトの中の静的ファイルを、STATIC_ROOTで指定したディレクトリに複写します。

具体的な手順は、まず仮想環境に切り替え、次に対象とするDjangoプロジェクトのmanage.pyが存在するディレクトリに移動し、collectstaticコマンドを実行します。

$ . 仮想環境名/bin/activate  #仮想環境へ切り替え(ubuntu)
$ cd test_py
$ python manage.py collectstatic

(2) STATIC_URL

Djangoでテンプレート(拡張子が.htmlのファイル)内で、以下のような記述をみたことはないでしょうか。「{% static 'hello/css/style.css' %}」のstaticは静的なファイルであることを示し、「'hello/css/style.css'」のルートがどこであるかは具体的な記述がありません。

    <link rel = "stylesheet" type = "text/css"
        href = "{% static 'hello/css/style.css' %}" />

そして、「'hello/css/style.css'」のルートの位置を指定するのが「STATIC_URL」なのです。

STATIC_URLは、以下の1行目ようにURLを直接指定する書式も可能ですが、「http://ドメイン名」の部分は省略し、2行目のように記述することができます。

つまり、上のコードはSTATIC_URLで静的ファイルのルートURLが指定されているからこそ静的ファイルにアクセスできるのです。

STATIC_URL = 'http://ドメイン名/static/'
STATIC_URL = '/static/'

「http://ドメイン名/static/」にアクセスした場合には、Nginxの設定では「location /static { }」の設定が適用されるので、ディレクトリ「/usr/share/nginx/html/static/」のファイルにアクセスできます。

したがって、「STATIC_URL = '/static/'」のstaticと「location /static { }」のstaticは同じ名前にする必要があります。

server {
	root /usr/share/nginx/html;
	location /static {
	}
}

なお、デフォルトでは「STATIC_URL = '/static/'」となっています。つまり、「location /static { }」で設定した「/usr/share/nginx/html/static/」内の静的ファイルを検索され、Djangoプロジェクト内の静的ファイルは検索されません。

ですから「python manage.py collectstatic」を実行しなければ、CSSファイルなどの静的ファイルは有効になりません。

(3) MEDIA_ROOT

MEDIA_ROOTは、例えばurls.pyの中で「settings.MEDIA_ROOT」といった表現によってアップロードファイルを扱うディレクトリを指定できます。

MEDIA_ROOTの詳しい説明は行いませんが、STATIC_ROOTと同様にMEDIA_ROOTの設定では、Nginxの設定ファイルの「location /media { }」で指定したディレクトリと同じディレクトリをパスで指定します。

8.gunicornの設定

いよいよ、以下の最終的な構成を完成させるためにgunicornの設定を行います。新たな設定ファイルは作らないので、すぐに設定は終了します。

01_Nginx_gunicorn_Django

Nginxはubuntuのaptでインストールしましたが、gunicornはpythonのpipでインストールします。従って、まず、インストールするpythonの仮想環境に切り替える必要があります。

$ . 仮想環境名/bin/activate  #仮想環境へ切り替え
$ python3 -m pip install -U pip  #pipのアップデート
$ python3 -m pip install gunicorn  #gunicornのインストール

次にgunicornを起動し、NginxとDjangoをバインドします。

さて、Nginxは設定ファイルの「proxy_pass http://127.0.0.1:8000;」により、クライアントからのリクエストを127.0.0.1:8000に送信するように設定しています。gunicornがないときにはDjangoがこの信号を受けていました。

server {
    location / {
            proxy_set_header Host $http_host;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X_Forwarded-Proto $scheme;

            proxy_pass http://127.0.0.1:8000;
        }
}

gunicornを使う場合には、この信号をDjangoではなくgunicornが受ける必要があります。そこで、gunicorn起動時の引数の設定により、gunicornとDjangoの127.0.0.1:8000をバインドし、Nginxからの信号をgunicornが受信します。

cd /home/user_name/py1/test_app  #test_appのmanage.pyがあるディレクトリに移動
gunicorn --bind 127.0.0.1:8000 test_app.wsgi -D

また、上記コマンドの「test_app.wsgi」は、Djangoプロジェクトのtest_app/test_app/の中にあるwsgi.pyのことですが、test_app/test_app/に移動して「gunicorn --bind 127.0.0.1:8000 wsgi -D」と実行してもダメでした。

したがって、必ずtest_appのmanage.pyがあるディレクトリに移動し、そこで「gunicorn --bind 127.0.0.1:8000 test_app.wsgi -D」を実行する必要があるようです。

「-D」はデーモンモードでgunicornを実行するコマンドであり、バックグラウンドで黙々と働いてもらう指定です。また、127.0.0.1:8000の部分はlocalhost:8000でも構いません。

gunicorn --bind localhost:8000 test_app.wsgi -D

以上でgunicornの起動・設定が終了しました。ブラウザで以下のurlにアクセスすると、プロジェクトtest_appのhelloアプリの画面が出力されます。

http://ドメイン名/hello/
02_Django_test_app

なお、gunicornで接続した場合は「http://localhost:8000/hello/」「http://127.0.0.1:8000/hello/」ではアクセスできませんでした。おそらく、VS Codeがgunicornには対応していないのだと思います。

9.gunicornの主なコマンド

gunicornの主なコマンドは以下の通りです。

$ . 仮想環境名/bin/activate  #仮想環境へ切り替え(ubuntu)
$ cd Djangoのプロジェクト名  #プロジェクト内のmanage.pyがあるディレクトリに移動
$ gunicorn --bind 127.0.0.1:8000 Djangoのプロジェクト名.wsgi -D #gunicornの起動、バインド
$ pkill gunicorn  #gunicornの停止
$ ps ax | grep gunicorn  #gunicornのプロセスの確認

10.設定のまとめ

設定の手順をまとめます。まず、Djangoプロジェクトtest_pyとアプリケーションhelloを作成し、Djangoプロジェクトのsettings.pyを修正します。

#settings.pyの設定
DEBUG = False  #修正
ALLOWED_HOSTS = ['vpsサーバーのドメイン', 'localhost', '127.0.0.1']  #修正
STATIC_URL = '/static/'  #デフォルトのまま
STATIC_ROOT = '/usr/share/nginx/html/static'  #新たに追加
MEDIA_ROOT = '/usr/share/nginx/html/media'  #新たに追加

静的ファイルを、STATIC_ROOTに複写します。なお、仮想環境に切り替え、プロジェクトtest_pyのmanage.pyが存在するディレクトリに移動する必要があります。

$ . 仮想環境名/bin/activate  #仮想環境へ切り替え(ubuntu)
$ cd test_py
$ python manage.py collectstatic

Nginxを起動します。

$ sudo systemctl start nginx.service  #Nginxの起動

仮想環境に切り替えた状態でgunicornをインストールし、gunicornをバインドし、デーモンモードで起動します。なお、gunicornのバインド前にプロジェクトtest_pyのmanage.pyが存在するディレクトリに移動する必要があります。

$ python3 -m pip install -U pip  #pipのアップデート
$ python3 -m pip install gunicorn  #gunicornのインストール
$ cd /test_pyのパス/test_py  #test_pyはプロジェクト名
$ gunicorn --bind 127.0.0.1:8000 test_py.wsgi -D #test_pyはプロジェクト名

ブラウザでアクセスすると、Djangoプロジェクトのhelloアプリが表示されます。

http://ドメイン名/hello/
02_Django_test_app

なお、setting.pyを変更したときには、以下の実行で設定を有効にしてください。

$ pkill gunicorn  #gunicornの停止
$ cd /test_pyのパス/test_py  #test_pyはプロジェクト名
$ gunicorn --bind 127.0.0.1:8000 test_py.wsgi -D #test_pyはプロジェクト名

Nginxの設定を変更したときは、以下の実行で設定を有効にしてください。

$ sudo systemctl reload nginx.service  #設定ファイルの再読み込み

以上で、前回の記事とあわせてNginx、gunicorn、Djangoの設定が完了しました。

私が実際にレンタルしたVPSサーバー

私が実際にレンタルしたVPSサーバーはConoHa VPSです。私は1GBのプランを申し込みました。VPSサーバーは一般のレンタルサーバーと異なりOSやアプリケーションを自由に設定できるので、Pythonで計算した結果をサイトに表示することもできます。

なお、ConoHa VPSの特長として、サーバーのディスクイメージを丸ごとバックアップできるイメージ保存機能を無料で使用することができます。コードを変更して元に戻せなくなった場合にも安心です。

また、ConoHa VPSは途中でプランをスケールアップできるだけでなく、スケールダウンすることもできます。つまり、2Gプランを1Gプランに変更することができます。ただし、512MBプランだけはスケールアップ・ダウン機能が使用できないので注意してください。

また、初期費用なしで3日だけ借り、3日分の費用だけ払うといったことも可能なので気軽に始められます。※時間課金(月の上限額は決まっています)



その他

Twitterへのリンクです。SNSもはじめました♪

以下、私が光回線を導入した時の記事一覧です。
 (1) 2020年「光回線は値段で選ぶ」では後悔する ←宅内工事の状況も説明しています。
 (2) NURO光の開通までWiFiルーターを格安レンタルできる
 (3) NURO光の屋外工事の状況をご紹介。その日に開通!
 (4) 光回線開通!実測するとNURO光はやっぱり速かった
 (5) ネット上のNURO光紹介特典は個人情報がもれないの?