さくらの VPS (Ubuntu 20.04) で Django アプリを作る(2/3)

Ubuntu 環境(さくらのVPS )で Django アプリケーションを作るまでに実際に辿ったステップ。こちらの記事の続編です。

今回は実際に Django のプロジェクト・アプリケーションを作って公開するところまで。Web サーバーとして Nginx、アプリケーションサーバーとして Gunicorn の設定も行います。

また、今後同じサーバーで複数の Django プロジェクトを作っても大丈夫な様に設定していきます。

Django + Nginx + Gunicorn での設定

全体はとりあえずこんな感じのイメージです。

  1. サイトをホストするディレクトリの作成
    • ルートディレクトリ作成
    • 管理者の変更
  2. Django を動かす
    • Python 仮想環境作成
    • Django インストール
    • Django プロジェクトと Django アプリケーションを作成
    • settings.py の設定
    • Django の動作確認
  3. Web サーバーの設定(Nginx)
    • Web サーバー: Nginx
    • 設定ファイル(nginx.conf)
  4. アプリケーションサーバーの設定(Gunicorn)
    • アプリケーションサーバー: Gunicorn
    • 仮想環境内で pip install gunicorn
    • systemd .socket ファイル
    • systemd .service ファイル
    • Nginx の設定ファイルでソケットを使用する様設定する
  5. 静的ファイルの設定

1. サイトをホストするディレクトリの作成

サーバー上にサイトホスト用ディレクトリを作成しますが、今後を見越し、複数のドメインを同じサーバー上でホスト出来る様に配慮して作業を進めていきます。

ルートディレクトリ作成

サイトをホストするルートディレクトリを作成します。

Nginx はデフォルトで「/var/www/html」というディレクトリを作っていますが、今回は /var/www/ の配下にドメイン名のディレクトリ、そしてその配下に html というディレクトリを作ります。(イメージ:/var/www/example.com/html)

vpsadmin@xx1-234-56789:/var/www$ sudo mkdir -p example.com
[sudo] password for vpsadmin: 
vpsadmin@xx1-234-56789:/var/www$ ls
html  meatthezoo.org
vpsadmin@xx1-234-56789:/var/www$ cd example.com
vpsadmin@xx1-234-56789:/var/www/example.com$ sudo mkdir -p html

管理者の変更

ディレクトリの管理者を変更します。

vpsadmin@xx1-234-56789:/var/www$ sudo chown -R $USER:$USER /var/www/example.com/html

2. Django を動かす

Python 仮想環境作成

ルートディレクトリ(/var/www/example.com/html)直下で「python3 -m venv 仮想環境名」を実行しPython の仮想環境を作成します。

$ python3 -m venv djangovenv

Django インストール

仮想環境を起動し、「pip install django」で Django をインストールします。

% cd djangovenv
% source bin/activate
(djangovenv) % pip install django

Django プロジェクトと Django アプリケーションの作成

Django プロジェクトと Django アプリケーションを作成していきます。

(djangovenv) % django-admin startproject djangoprod
(djangovenv) % cd djangoprod
(djangovenv) % python manage.py startapp djangoapp

settings.py の設定

settings.py の ALLOWED_HOSTS にサイトのドメイン名を登録し、INSTALLED_APS にアプリケーションを追加します。

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

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'djangoapp.apps.DjangoAppConfig' # 追記分
]

Django の動作確認

コマンド「python manage.py runserver 0.0.0.0:8000」を実行し、ブラウザの URL バーに「ドメイン名:8000」と打ち込んでみます。

Django デフォルトのロケットの画面が出たら大丈夫です。ちなみにこの画面は settings.py の DEBUG = True の時だけ表示されます。

Djangoのデフォルトエラーページが DEBUG=Falseだと見れないのはなんでか調べた

今現在、Django の runserver を起動した場合に限り、ポート 8000 でなら Django が動くという状態です。

というわけで Web サーバー(Nginx)をアプリケーションサーバー(Gunicorn)を使って下記を解決していきます。

問題解決方法
URL バーでポート 8000 を指定しないとアクセスできないNginx の設定ファイルで、該当するドメイン名にアクセスされたら Django に繋ぐ様変更する
runserver を起動していないとアクセスできないGunicorn を使う

3. Web サーバーの設定(Nginx)

設定ファイル(nginx.conf)

デフォルトの設定ファイルは「/etc/nginx/nginx.conf」

初めから下記の二行が書いてあればこのファイルはノータッチで良さそうです。

  • include /etc/nginx/conf.d/*.conf;
  • include /etc/nginx/sites-enabled/*;

複数ドメイン対応

一つのサーバーで複数のウェブサイトを運営しようとした場合、一つの IP アドレスに複数のドメイン名が紐づく事になります。

そのため、リクエストされた URL に応じて適切なサイトの情報を返せる様設定していきます。

  1. sites-available ディレクトリにドメイン毎の設定ファイルを置く
  2. 各ドメインの設定ファイルでドメインとルートディレクトリを紐付け
  3. それらファイルのシンボリックリンクを sites-enabled ディレクトリに作成
  4. sites-enabled を nginx.conf 本体に include

設定ファイルの複製と編集

「sites-available」ディレクトリの「default」ファイルを個別サイト用にコピーします。

sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/example.com

コピーした方のファイルを確認します。下の方に Virtual Host のための設定というところがあるのでそちらを使います。上半分は Default Server のための設定なので全てコメントアウトします。

server_name と location / の部分を編集しました。

example.com ファイルの Virtual Host 設定部分
# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#

server {
        listen 80;
        listen [::]:80;

        server_name www.example.com;
        return 301 http://example.com$request_uri;
}


server {
        listen 80;
        listen [::]:80;

        server_name example.com;

        root /var/www/example.com/html;
        index index.html;

        location / {
                try_files $uri $uri/ =404;
                include proxy_params;
                proxy_pass http://127.0.0.1:8000;
        }
}

初めの部分は「www.example.com」へのアクセスを自動的に「example.com」へリダイレクトする設定です。

シンボリックリンクを作成します。

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/

Nginx を再起動します。

sudo systemctl restart nginx

そして runserver を実行して、反映を確かめます。

python manage.py runserver 0.0.0.0:8000

これで一つ解決です。ポート 8000 を URL に含めなくても Django に飛ぶ様になりました。

次は runserver を起動していないとアクセスできないという部分を、Gunicorn を使って解決します。

問題解決方法
runserver を起動していないとアクセスできないGunicorn を使う

4. アプリケーションサーバーの設定(Gunicorn)

Nginx から飛んできたリクエストを、待ち受けているソケットから Django アプリケーションに伝える様に設定します。

言い方を変えると Nginx からのリクエストを .socket ファイルが受け、.service ファイルを起動し Gunicorn の処理を実行します。

Gunicorn のインストール

仮想環境内で pip install gunicorn を実行します。

pip install gunicorn

.socket と .service の作成

「/etc/systemd/system/」ディレクトリに .socket と .service を作成します。今回はファイル名をそれぞれ「sample_django.socket」、「sample_django.service」としています。

systemd .socket ファイル

Nginx の設定ファイルで指定したポートでリクエストを待ち受け、 リクエストがあったら指定されているサービスに接続を渡します。

# sample_django.socket

[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/sample_django.sock
[Install]
WantedBy=sockets.target
項目メモ
Descriptionログ出力の際などに使われる
ListenStreamポートの指定。Nginx 設定ファイルの pass_proxy で指定
WantedBysockets.target

systemd .service ファイル

サービスの依存関係や実際の実行内容を定義します。

# sample_django.service

[Unit]
Description=gunicorn daemon
Requires=sample_django.socket
After=network.target
[Service]
User=root
Group=root
WorkingDirectory=/var/www/example.com/html/djangovenv/djangoprod
ExecStart=/var/www/example.com/html/djangovenv/bin/gunicorn --workers 3 --bind unix:/run/sample_django.sock djangoprod.wsgi:application
[Install]
WantedBy=multi-user.target
項目メモ
Descriptionログ出力の際などに使われる
Requires対応する .socket ファイル
After
User
Group
WorkingDirectorymanage.py があるディレクトリのフルパス?
ExecStartsystemctl start した時に実行するコマンド。
.sock と wsgi を bind。
venv 内の gunicorn のフルパス(?)を指定
WantedBy大抵 multi-user.target で大丈夫

サービスを起動します。

systemctl start sample_django.socket
systemctl start sample_django.service

Nginx 設定ファイルの変更

Nginx 設定ファイルの proxy_pass の部分を下記の様に変更しました。.socket ファイルの ListenStream で設定したポートを指定しています。

location / {
        try_files $uri $uri/ =404;
        include proxy_params;
        # proxy_pass http://127.0.0.1:8000;
        proxy_pass http://unix:/run/sample_django.sock;
}

Nginx の設定を検証し再起動します。

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$ sudo systemctl restart nginx

これで runserver を実行しなくても URL を叩けば Django アプリケーションへアクセスできる様になったはずです。

大雑把ですがなんとなく全体像は下記の様なイメージです。

Django + Nginx + Gunicorn での設定

*作業時は何かにつけて「pkill gunicorn」で gunicorn を終わらせた方が良いです。エラーが起きていくらコードを修正しても治らないまま数時間経って、「pkill gunicorn」一発で治ったこともありました。

静的ファイルの設定

実はこの時点では CSS や JavaScript のいわゆる静的ファイルはうまく反映されません。ドメイン名/admin にブラウザでアクセスすると CSS が抜けている状態だと思います。

Django で静的ファイルとうまくやる

本番環境でちゃんと表示される様、settings.py と Nginx の設定ファイルを編集します。

settings.py の編集

settings.py 内では STATICFILES_DIRS と STATIC_ROOT を設定します。

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]
STATIC_ROOT = '/var/www/example.com/html/static'
項目メモ
STATICFILES_DIRScollectstatic が静的ファイルを追加検索する対象のディレクトリ
STATIC_ROOTcollectstatic 実行時に静的ファイルの集約先となるディレクトリ

collectstatic を実行します。

python manage.py collectstatic

Nginx の設定ファイルの編集

server ディレクティブの中に STATIC_ROOT 指定したパスを追記しました。

        location /static {
                alias /var/www/example.com/html/static;
        }

これでドメイン名/admin を再度確認すると CSS が反映されていると思います。

他の行程へ

▶︎まずは2週間無料でお試し♪さくらのVPS

【Django】Nginx + Gunicorn でサブドメインを設定する方法

「example.com」というドメイン名ですでに運用しているところに追加で「sub.example.com」も使いたいという想定で書きます。

また、Django の処理には Gunicorn を使っています。

  1. ネームサーバーでサブドメインの設定をする
  2. サブドメイン用のディレクトリを作る
  3. 仮想環境と Django プロジェクトを作成する
  4. Gunicorn を設定する
  5. Nginx の設定ファイルにサブドメインの処理を追記する

1. ネームサーバーでサブドメインの設定をする

ネームサーバーでサブドメインを登録します。

さくらインターネットを使っている場合は会員メニュー > ドメイン > ゾーン表示の画面で登録できます。

フィールド
エントリ名sub
種別別名 (CNAME)
@
DNSチェックする
TTLの設定なし

2. サブドメイン用のディレクトリを作る

任意の場所にサイトをホストするためのディレクトリを作成します。

例えば「example.com」というディレクトリの配下にさらにサブドメイン用に「sub」ディレクトリ、その配下に「html」と作る場合は下記の様な形。

$ cd var/www/example.com
$ mkdir sub
$ cd sub
$ mkdir html

ディレクトリの管理者を変更します。

$ sudo chown -R $USER:$USER /var/www/example.com/sub/html/

注意

一度上記の管理者の変更を飛ばした際、後で Django の「python manage.py startapp アプリ名」を実行した時に「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?」というエラーが出ました。

「pip install django」を実行した際に django モジュールが仮想環境内に上手くインストールされなかったことが原因です。

3. 仮想環境と Django プロジェクトを作成する

Python の仮想環境を作成、起動します。

$ cd var/www/example.com/sub/html/
$ python3.9 -m venv dj_proj_venv
$ cd dj_proj_venv
$ source bin/activate

Django を pip install し、プロジェクトとアプリケーションを作成します。

(dj_proj_venv) $ pip install django
(dj_proj_venv) $ django-admin startproject dj_proj
(dj_proj_venv) $ cd dj_proj
(dj_proj_venv) $ python manage.py startapp dj_app

settings.py の INSTALLED_APPS にアプリケーションを追記します。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'dj_app.apps.DjAppConfig', # 追記分
]

settings.py の ALLOWED_HOSTS にサブドメインを含むドメインを記述します。

ALLOWED_HOSTS = ['sub.example.com']

4. Gunicorn を設定する

Gunicorn をインストールします。

$ pip install gunicorn

.socket ファイルの作成

サブドメイン用に「example_sub.socket」というファイルを作成します。

# example_sub.socket
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/example_sub.sock
[Install]
WantedBy=sockets.target
項目メモ
Descriptionログ出力の際などに使われる
ListenStreamポートの指定。Nginx 設定ファイルの pass_proxy で指定
WantedBysockets.target

.service ファイルの作成

サブドメイン用に「example_sub.service」というファイルを作成します。上記の「example_sub.socket」と対応しています。

# example_sub.service
[Unit]
Description=gunicorn daemon
Requires=example_sub.socket
After=network.target
[Service]
User=root
Group=root
WorkingDirectory=/var/www/example.com/sub/html/dj_proj_venv/dj_proj
ExecStart=/var/www/example.com/sub/html/dj_proj_venv/bin/gunicorn --workers 3 --bind unix:/run/sample_django.sock dj_proj.wsgi:application
[Install]
WantedBy=multi-user.target
項目メモ
Descriptionログ出力の際などに使われる
Requires対応する .socket ファイル
After
User
Group
WorkingDirectorymanage.py があるディレクトリのフルパス?
ExecStartsystemctl start した時に実行するコマンド。
.sock と wsgi を bind。
venv 内の gunicorn のフルパス(?)を指定
WantedBy大抵 multi-user.target で大丈夫

socket の待ち受けを開始します。

$ systemctl start example_sub.socket
$ systemctl start example_sub.service

5. Nginx の設定ファイルにサブドメインの処理を追記する

「sub.example.com」と「example.com」の処理が記述されています。

それぞれの proxy_pass の記述を見るとわかりますが、「sub.example.com」へのリクエストの場合は example_sub.sock のソケット、「example.com」へのリクエストの場合は example.sock のソケットへ処理が回されます。

server {
        listen 80;
        listen [::]:80;

        server_name sub.example.com;

        root /var/www/example.com/sub/html;
        index index.html;

        location / {
                proxy_set_header Host $http_host;
                proxy_set_header X-Forwarder-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_pass http://unix:/run/example_sub.sock;
        }

        location /static {
                alias /var/www/example.com/sub/html/static;
        }

}

server {
        listen 80;
        listen [::]:80;

        server_name example.com;

        root /var/www/example.com/html;
        index index.html;

        location / {
                proxy_set_header Host $http_host;
                proxy_set_header X-Forwarder-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_pass http://unix:/run/example.sock;
        }

        location /static {
                alias /var/www/example.com/html/static;
        }

}

ローカル環境の Django を本番環境(Ubuntu)へデプロイする方法

Mac のローカル環境で作成した Django プロジェクトをさくらのVPSで用意した Ubuntu 環境へ反映する際の流れをメモしています。

▶︎ Django のおすすめ VPS(仮装専用サーバー)はOSから決めるべし

下記が完了していることを想定しています。

  • Web サーバーの設定が完了済み(筆者は Nginx を使用)
  • 仮想環境を作るディレクトリが決めてある
  1. データベースを本番環境にコピーする
    • ローカル環境での作業
    • 本番環境での作業
  2. ローカル環境のパッケージを本番環境で再現
    • requirements.txt の作成
    • requirements.txt を本番環境へコピー
    • 本番環境で Python 仮想環境を作成
    • requirements.txt によるパッケージインストール
  3. settings.py の編集
    • ファイルを分割する必要性
    • 開発環境の runserver 実行時に settings_dev.py を指定
    • settings.py(本番環境用)の編集
  4. Django ソースコードを本番環境に配置
    • 本番環境への git のインストール
    • リモートリポジトリから本番環境へファイルをコピー
  5. gunicorn のインストール
  6. データベースのログイン情報をサーバーの環境変数に記述
  7. 静的ファイルの配置

1. データベースを本番環境にコピーする

検証環境で準備したデータベース、テーブル、そしてデータを本番環境でも反映します。

ここはうまくやれば一行スクリプト書いて終了する方法もある様ですが、色々とエラーが出て逆にめんどくさそうだったのでステップバイステップでやっていきます。

ローカル環境での作業

まずローカル環境でコマンド「mysqldump データベース名 > dump.txt」を実行し、dump ファイルを作成します。

% mysqldump graffuhs > dump.txt                                                              
%

それを FTP か何かで本番環境へアップロードします。

本番環境での作業

本番環境に接続し、本番環境の MySQL でデータベースを作成します。

mysql> create database データベース名;

そしてコマンド「sudo mysql データベース名 < dump.txt」を実行します。

$ sudo mysql データベース名 < dump.txt
$

これで本番環境の MySQL にもローカル環境と同じデータベース、テーブル、そしてデータがコピーされました。

2. ローカル環境のパッケージを本番環境で再現

ローカル環境の Python 関連パッケージを本番環境でも再現するため、ローカル環境で requirements.txt ファイルを作成し、それを元に本番環境でパッケージインストールをさせます。

requirements.txt の作成

ローカル環境の Python 仮想環境へ移り、コマンド「pip freeze > requirements.txt」を実行します。

% pip freeze > requirements.txt

requirements.txt を本番環境へコピー

コマンド「scp requirements.txt 本番環境のユーザー名@本番環境のホスト名(もしくは IP アドレス):保存先のディレクトリ」を実行します。

だいぶ長いですが下記の例だとすると。。。

  • 本番環境のユーザー名: vpsadmin
  • 本番環境のホスト名: xx1-234-56789.vs.sakura.ne.jp
  • 保存先のディレクトリ: /var/www/example.com/html

下記の様になります。

scp requirements.txt vpsadmin@xx1-234-56789.vs.sakura.ne.jp:/var/www/example.com/html

無事本番環境に requirements.txt がコピーされました。

$ ls
requirements.txt

本番環境で Python 仮想環境を作成

本番環境のディレクトリへ移動し、Python の仮想環境を作成します。

$ python3.9 -m venv pythonvenv
$ ls
pythonvenv  requirements.txt

requirements.txt によるパッケージインストール

本番環境の Python 仮想環境に入った状態で、コマンド「pip install -r requirements.txt」を実行します。

(pythonvenv) $ pip install -r requirements.txt

すると requirements.txt の内容を元に Django を含めローカル環境で使っていたものと同じパッケージがインストールされます。

3. settings.py の編集

ファイルを分割する必要性

settings.py は本番環境と開発環境で内容が異なる部分が出てくるので、下記の様にファイルを分ける必要があります。(ファイル名は任意)

  • settings_common.py:開発環境と本番環境で共通の部分を記述
  • settings.py:本番環境でのみ適用する部分を記述
  • settings_dev.py:開発環境でのみ適用する部分を記述

settings.py と settings_dev.py それぞれに「from .settings_common import *」と記述し、共通部分を settings_common.py から読み込む様設定します。

開発環境の runserver 実行時に settings_dev.py を指定

runserver 実行時、デフォルトでは settings.py が設定ファイルとして読み込まれますが、今後開発環境(ローカル環境)では settings_dev.py を使う必要があります。

そのため、今後開発環境で runserver を実行する際は settings_dev.py を「runserver --settings=プロジェクトディレクトリ名.settings_dev」の形式で渡して実行します。

(pythonvenv) % python manage.py runserver --settings=some_project.settings_dev

settings.py(本番環境用)の編集

DEBUG の変更

False に変更

DEBUG = False

ALLOWED_HOSTS の変更

ドメイン名を設定。

ALLOWED_HOSTS = ['example.com']

Web サーバーで www.example.com から example.com のリダイレクトを行っていれば example.com のみを設定するだけで大丈夫です。

静的ファイルの本番環境での配置場所

STATIC_ROOT = '/var/www/examlpe.com/html/static'

4. Django ソースコードを本番環境に配置

本番環境への git のインストール

本番環境でコマンド「sudo apt update」を実行しパッケージリストを更新、その後「sudo apt install git」で git をインストールします。

$ sudo apt update
$ sudo apt install git
Reading package lists... Done
Building dependency tree       
Reading state information... Done
git is already the newest version (1:2.25.1-1ubuntu3.1).
git set to manually installed.
0 upgraded, 0 newly installed, 0 to remove and 18 not upgraded.

リモートリポジトリから本番環境へファイルをコピー

プロジェクトフォルダをリポジトリに設定しているので、プロジェクトフォルダを配置したい場所へ移動した上で git clone を実行します。

$ git clone https://github.com/ユーザー名/リポジトリ名.git

これで本番環境にもプロジェクトフォルダがコピーされました。

ローカル環境からリモートリポジトリへファイルを反映させる方法はこちら↓

Github で開発ローカル → リモートリポジトリ → 本番ローカルに反映させる手順

5. gunicorn のインストール

本番環境でのみ必要となるものとして、アプリケーションサーバーがあります。今回は gunicorn を使います。

Python 仮想環境内でアプリケーションサーバーの gunicorn をインストールします。

(pythonvenv) $ pip install gunicorn

socket の待ち受けを開始します。

(pythonvenv) $ systemctl start example_sub.socket
(pythonvenv) $ systemctl start example_sub.service

今後作業する際は何かにつけて「pkill gunicorn」で gunicorn を終わらせた方が良いです。エラーが起きた時にいくらコードを修正しても治らないまま数時間経って、「pkill gunicorn」一発で治ったこともありました。

6. データベースのログイン情報をサーバーの環境変数に記述

MySQL のデータベースを使用していて、settings.py ではユーザー名とパスワードを環境変数から取る様に設定しています。

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'データベース名',
        'USER': os.environ.get('DB_USER'),
        'PASSWORD': os.environ.get('DB_PASSWORD'),
        'HOST': '',
        'PORT': '',
    }
}

そのため、サーバーの環境変数として記述します。ホームディレクトリの .bashrc ファイルの最下部に下記を追加しました。

export DB_USER='ユーザー名'
export DB_PASSWORD='パスワード'

7. 静的ファイルの配置

コマンド「python manage.py collectstatic」を実行し、settings.py で STATIC_ROOT に設定したパスに静的ファイルを集約します。

$ python manage.py collectstatic

これでとりあえず本番環境でも動くと思います。

▶︎ Django のおすすめ VPS(仮装専用サーバー)はOSから決めるべし