【Django×Gunicord×Nginx】WSLでWebアプリのデプロイの練習

この記事でやること

Djangoで作成したWebアプリをサーバーにデプロイする作業について、実際にVPS上等で行うのではなく、練習としてお手持ちのPCで実施できるように紹介しています。

実際のデプロイ時も同様の作業でできますので、その際も本記事を参考にしていただければと思います。

この記事でやらないこと

ローカルで作業するので以下の作業はこの記事で扱いません。

・VPS・ドメインのセットアップ

・SSH周りの設定

・DNSの設定

開発環境で必要なライブラリ

pip install Django

pip install python-dotenv

WSLのセットアップ

WSLのインストール

サーバー環境を模して実施するため、Windows11上で手軽にLinux環境を構築できるWSL(Windows Subsystem for Linux)を使用します。

コマンドプロントを管理者権限で開き、以下のコマンドを実行します。

何も指定せずにインストールすると、ディストリビューションとしてUbuntuがインストールされます。

wsl --install

インストールが始まるのでしばらく待ちます。途中でユーザーアカウント制御が出てきたら「はい」を選択します。

完了すると、再起動を求められるのでPCを再起動します。

ユーザー情報の設定

PCの再起動後、自動的にWSLのUbuntuが起動します。

起動しない場合はタスクバーの検索からUbuntuを探して実行してください。

指示の通りにユーザー名とパスワードを設定します。

完了すると以下のようにシェルが起動します。

環境構築

ディストリビューションのパッケージ更新

以下の2つのコマンドを順に実行します。最初のsudu時に先ほど設定したユーザーのパスワードが求められます。

sudo apt update

sudo apt upgrade

sudo apt upgradeの途中で以下のように表示された場合は「y」+エンターで続行します。

Python系のセットアップ

UbuntuではデフォルトでPythonの実行環境が入っています。以下のコマンドでバージョン確認すると、Python3.12.3であることが確認できます。

しかしPythonのパッケージ管理ツールであるpipは標準でインストールされておらず、基本的にvenv等の仮想環境上で実施する前提になっています。

なのでvenvでPyhotnの仮想環境を構築します。

venvのインストール

まずはvenvをインストール

apt install python3.12-venv

venvがインストールできたら以下のコマンドでPythoの仮想環境を作成します。以下の例では「testenv」という名前の仮想環境が作成されます。

python3 -m venv testenv

完了すると、現在作業しているディレクトリ(デフォルトで「/home/<ユーザー名>/」)直下にtestenvフォルダができています。

testenvディレクトリに移動し、ディレクトリの中身を見てみると、色々なディレクトリやファイルが作成されています。

pipを含めたPythonの本体はbinディレクトリの中にあります。

Pythonの仮想環境に入る

一旦作業ディレクトリをtestenv直下に戻します。(戻さなくてもいいですが)

仮想環境内で作業するには「アクティベート」をする必要があります。仮想環境にしたフォルダがある階層で以下のコマンドを実行。(相対パス指定なのでコマンドを実行している階層が違う場合は適宜読み替えてください)

source bin/activate

すると、シェルの左側に(testenv)のように表示されます。この状態の間は仮想環境の中で作業していることになります。

仮想環境から抜ける場合はdeactivateコマンドを使います。

deactivate

Githubのセットアップ

※この章はPythonとは関係ないため、Python仮想環境外で実施してもOKです。

このあとサーバーにDjangoで作成したWebアプリケーションを載せますが、その際にGithubを使用して開発環境–(プッシュ)–>Github–(クローン・プル)–>サーバーのような形でアップします。

通常Githubのリポジトリはプライベートにしておきますので、サーバーからGithubリポジトリを取得する際に認証が必要になります。そのための設定をここで行います。

gitのセットアップ

まずはgitをインストールします。

sudo apt install git

インストールしたらgitのユーザー名の設定

git config --global user.name <ユーザー名>

次にgitのメールアドレスの設定

git config --global user.email <メールアドレス>

Githubとの連携

作業ディレクトリをホームディレクトリに戻してください。(「cd ~」でもOKです。)

Githubとの連携(認証を通す)には公開鍵認証方式を使用します。そのための鍵ペアは自分で生成する必要があります。

ssh-keygen -t rsa -C <メールアドレス>

指示に従って、保存場所(デフォルトでOKなら何も入力せずエンター)→パスワード→パスワードの確認を入力します。

ここで設定するパスワードはサーバーにリポジトリをクローン・プル際に毎回入力するものになります。

生成されたファイルを確認しましょう。

「.」(ドット)から始まる隠しディレクトリ「.ssh」が新規作成され、その中に入っています。(「ls」コマンドでは表示されないので「ls -al」を使用します。)

.sshの中にid_rsa(秘密鍵)とid_rsa.pub(公開鍵)があります。

公開鍵の方をGithubにアップロードするため、クリップボードにコピーします。

cat id_rsa.pub | clip.exe

Githubへ行き、自分のアイコンをクリック、出てきた項目の「settings」をクリックします。

左側の項目から、「SSH and GPG keys」を選択

「New SSH key」を選択

「title」に後で見てわかる名前、「Key type」は「Authentication Key」、「Key」に先ほどクリップボードにコピーした公開鍵を貼り付け、「Add SSH key」します。その後、2FA認証を有効にしている方は画面の遷移に従ってログイン操作します。

最後に、Ubuntuに戻って認証が通ることを確認します。

途中、「Are you sure you want to continue connecting (yes/no/[fingerprint])?」と聞かれたら「yes」と入力、パスワードを聞かれるので、先ほど鍵ペアを生成した際に設定したパスワードを入力します。

ssh -T [email protected]

上記のような表示になればOKです。

Djangoの用意

ここからはデプロイ作業に直接関係はありませんが、実際にWebアプリを用意したいため、デモアプリの用意を行います。

Windows側に戻り、作業用のフォルダを作成、そのフォルダ内でコマンドを立ち上げてプロジェクトとアプリの作成をします。

python -m django startproject project

cd project

python manage.py startapp app

作成できたら、コード編集に移るのでVSCodeでprojectフォルダを開き

・settings.pyのINSTALLED_APPSにappを追加

・settings.pyでDjangoの言語をjaに、タイムゾーンをAsia/Tokyoに変更

・settings.pyのSECRET_KEYを.envに分離

シークレットキーの類をプログラム中にハードコーディングするのはよくないため、.envというファイルに分離し、後ほど行うgit管理でもignoreします。

settings.pyの既存のSECRET_KEY='<シークレットキー>’の部分を削除し、以下のように記述します。

import os
from dotenv import load_dotenv

load_dotenv()
SECRET_KEY = os.environ.get("SECRET_KEY")

次に、.envファイルをproject>projectの中(settings.pyと同じ階層)に作成します。

ここに新しいシークレットキーを記述します。manage.pyと同じ階層でコマンドを開き、以下を実行してシークレットキーを生成します。

python manage.py shell

インタラクティブモードになるので以下を順に実行します。

>>> from django.core.management.utils import get_random_secret_key
>>> get_random_secret_key()
'<生成されたキー>'

>> quit() #インタラクティブモードを抜ける

生成されたキーをもとに、.envに以下のように記述します。

SECRET_KEY='<生成されたキー>'

・ページを作成します。

project>appの中にtemplatesフォルダを作成

その中にindex.htmlを作成し、以下のように記述します。

<!DOCTYPE html>
<html lang="ja">
<head>
    {% load static %}
    <meta charset="UTF-8">
    <title>こんにちはページ</title>
    <link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
</head>
<body>
    <h1>こんにちは!</h1>
    <p>ページ本文です。</p>
</body>
</html>

・CSSを作成します

project>appの中にstaticを作成

さらにstaticフォルダの中にcssフォルダを作成、その中にstyle.cssを作成して以下のように記述します。

p {
    color: red;
}

・views.pyの記述

ページ自体はできたので、次は表示するバックエンド側を作成します。

project>app>views.pyにて以下のように記述します。

def top(request):
    return render(request, 'index.html')

・プロジェクト側のurls.pyを編集

project>project>urls,pyを以下のように変更します。

from django.contrib import admin
from django.urls import path, include #includeを追加

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include("app.urls")), # 追加
]

・アプリ側のurls.pyを新規作成・記述

project>appの中にurls.pyを新規作成&以下のように記述します。

from django.urls import path
from . import views

urlpatterns = [
    path('', views.top, name=''),
]

ここまででページが完成したので、動作確認をします。

manage.pyが存在する階層でコマンドを開き、(VSCodeの場合は「Ctrl」+「Shift」+「@」でコマンドパレットを開いてmanage.pyの階層に移動)runserverします。

python manage.py runserver

以下のように表示されていればOKです。

デプロイ用の設定

settings.pyにデプロイのための設定を行います。

・DEAUG = TrueをFalseに、ALLOWED_HOSTSを127.0.1にします。(実際にはドメイン名を設定するので、あくまでWSL環境用のものです)(「localhost」を指定すると後ほど作業するNginxにてエラーが発生します。)

・静的ファイルを集める場所のパスの設定

Nginxの設定時にもこのパスが必要となります。<分かりやすい名前>の部分は通常ドメイン名にしておくと分かりやすいです。

STATIC_ROOT = "/usr/share/nginx/html/<わかりやすい名前>/static"

記述場所はsettings.pyの中ならどこでもいいですが、ファイル最後のほうにSTATIC関連の設定がデフォルトで記述されている箇所があるので、その付近に書いておくと分かりやすいです。

Githubにアップロード

Gitのローカルリポジトリを追加

Djangoのprojectフォルダと同じ階層でコマンド開き、

以下のコマンドでローカルリポジトリを作成します。

git init

Github Desktopに追加

Github Desktopを開き、「Add local repository」を選択

「Choose」を選択

.gitやprojectフォルダの1つ上の階層(最初にDjango作業用で作成したフォルダ)を選択します。

.gitignoreの追加

そのままだと.pycのようなキャッシュファイルやdb.sqlite3のように開発環境とサーバー環境で同期したくないファイルもgit管理されてしまうので、.gitと同じ階層で「.gitignore」ファイルを新規作成し、以下のように記述してください。

.env
*.pyc
__pycache__/
db.sqlite3

requirements.txtの追加

Djangoを動かすのに必要なPythonライブラリ一覧を記述するrequirements.txtを新規作成、以下のように記述します。改装はどこでもよいですが、今回は.gitと同じ階層に作成しました。

以下の記述内容は必要最低限のもので、Webアプリの開発の中で種々のライブラリを使用した場合はすべてここに記述しておきます。

python-dotenvは前述の作業でDjangoのsettings.pyのSECRET_KEYを.envファイルに分離する部分に使用しています。

Django==5.1.3
gunicorn==23.0.0
python-dotenv==1.0.1

コミット・Githubリポジトリ作成

Github Desktopに戻り、ひとまずステージングされているものをすべてコミットします。

その後、「Publish repository」します。

リポジトリ名などを設定し、他はデフォルト設定で「Publish repository」をクリック。

Github側でもリポジトリが作成されているか確認します。

サーバー側でGithubリポジトリをクローン

Githubのリポジトリのページで「Code」>「SSH」を選択し、git@~から始まるリンクをコピーしてください。

ここからはサーバー側(Ubuntu)での作業です。

先ほど作成したPython仮想環境のtestenvディレクトリ内に移動し、アクティベートして仮想環境内に入ってください。(gitはPythonではないのでまだ仮想環境に入らなくてもいいのですが説明の都合上そうしています)

cd <仮想環境名>
source bin/activate

リポジトリをクローンします。

git clone <先ほどコピーしたgit@~から始まるリンク>

ディレクトリを確認すると、Githubリポジトリ名がついたディレクトリが追加されています。

必要なPythonリポジトリのインストール

この作業は必ず仮想環境内で行います。

requitements.txtに記述したPythonライブラリをインストールします。requirements.txtが存在する階層に移動し、以下のコマンドを実行します。

pip install -r requirements.txt

Django用の.envを作成

前述の作業でsettings.pyのSECRET_KEYは.envに分離し、git管理から除外していました。そのため今サーバーにクローンされているリポジトリは.envの無い不完全な状態になっています。そこで.envの作成とサーバー用のSECRET_KEYの生成を行います。

まずSECRET_KEYを生成します。manage.pyの存在する階層に移動し、先ほど同様に以下を実行します。

python manage.py shell
>>> from django.core.management.utils import get_random_secret_key
>>> get_random_secret_key()
<生成されたシークレットキー>

>>> quit()

次に、project>project(settings.pyと同じ階層)に移動し、.envファイルを新規作成、シークレットキーを記述します。

sudo nano .env
SECRET_KEY='<生成されたシークレットキー>'

Gunicornの設定

NginxとDjangoの橋渡しをする役割であるGunicornの設定を行います。

socketファイルの作成

NginxやDjangoと通信するためのソケットファイルを作成します。以下のコマンドでファイルを作成します。(パスワードを求められたらサーバーのユーザー設定時に設定したパスワードを入力)

sudo nano /etc/systemd/system/<わかりやすい名前>.socket

ファイル編集画面になるので、以下のように記述します。

[Unit]
Description=gunicorn socket //ソケットファイルの説明(自由に記述)

[Socket]
ListenStream=/home/<ユーザー名>/<仮想環境名>/<リポジトリ名>/project/<socketファイル名と同じ名前>.sock //パスはDjangoのprojectフォルダ直下になるよう適宜修正して記述

SocketUser=<ユーザー名>

[Install]
WantedBy=sockets.target

「Ctrl」+「S」で保存し、「Ctrl」+「X」で編集画面を閉じます。

serviceファイルの作成(systemctlへの登録)

デーモンとして登録します。

サービスファイルの記述について、「–workers」と「–bind」はコピペするとエラーが出る可能性が高いので手打ちしてください。

sudo nano /etc/systemd/system/<わかりやすい名前>.service
[Unit]
Description=gunicorn daemon //ソケットファイルの説明(自由に記述)
Requires=<先ほど決めたソケットファイル名>.socket
After=network.target

[Service]
User=<ユーザー名> //ルートでないユーザー
Group=<ユーザー名>
WorkingDirectory=/home/<ユーザー名>/<仮想環境名>/<リポジトリ名>/project/ //プロジェクトの絶対パス(manage.pyがある階層)
ExecStart=/home/<ユーザー名>/<仮想環境名>/bin/gunicorn –-workers 3 –-bind /home/<ユーザー名>/<仮想環境名>/<リポジトリ名>/<先ほど決めたソケットファイル名>.sock <プロジェクト名>.wsgi:application

[Install]
WantedBy=multi-user.target

Gunicornの起動

まず、デーモンを再度読み込みます。

sudo systemctl daemon-reload

Gunicornを起動します。

sudo systemctl start <先ほど決めたサービスファイル名>.service

Gunicornがちゃんと起動できているかステータスを確認します。socketファイルやserviceファイルの記述が正しくないとActiveになりません。

sudo systemctl status <プロジェクト名>.service

ご覧のようにステータスが「active」になっていればOKです。

こんな時は

サービスのステータス確認で以下のようなエラーが出た場合はサービスファイルの記述内容を記事からコピペしたことにより「--workers」と「--bind」のハイフンが正しく張り付けられていない可能性があります。見た目上問題なくてもこの部分についてはコピペではなく手打ちで書いてください。

「ModuleNotFoundError: No module named ‘–-workers’」

「gunicorn.errors.HaltServer: <HaltServer ‘Worker faild to boot.’ 3>」

サービスファイルの記述を修正し、以下のコマンドでサービスを再起動します。

sudo systemctl restart <サービスファイル名>.service

Gunicornのサービスの自動起動設定

今のままではサーバー(WSL)の再起動するとGunicornは停止した状態のままになるため、サーバーの起動と同時にGunicornのサービスの自動起動設定を行います。

sudo systemctl enable <サービスファイル名>.service

Nginxのインストールと設定

インストール

sudo apt install nginx

設定の作成

Nginxの設定(confファイル)を作成して記述します。設定ファイルの名前は何でもよいですが、ドメイン名にすると複数のサイトを公開する際に一目瞭然なのでおすすめです。

ちなみにsites-availableディレクトリ内に配置する設定ファイルは待機状態のもので、この後の作業でsites-available配下にシンボリックリンクを張ることで設定を有効にする形をとります。

sudo nano /etc/nginx/sites-available/<ドメイン名などわかりやすい名前>.conf
server {
  listen 80;
  server_name <ドメイン名>;

  location /static {
    alias <Djangoのsettings.pyのSTATIC_ROOT設定と同じパス>;
  }

  location / {
    proxy_pass http://unix:/home/<ユーザー名>/<仮想環境名>/<リポジトリ名>/<プロジェクト名>/<ソケットファイル名>.sock;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

記述が終わったら設定ファイルの構文チェックを行います。以下のコマンドでチェックできます。

このような表示となればOKです。

シンボリックリンクを張る

シンボリックリンクを張り、設定を有効化します。

cd /etc/nginx/sites-enabled
sudo ln -s ../sites-available/<Nginxの設定ファイルの名前>.conf ./

Nginxをリロード・再起動・ステータス確認

sudo systemctl restart nginx
sudo systemctl status nginx

以下のようにステータスが「active」であればOKです。

collectstaticの実施

Nginx関連の作業として、Django側でcollectstaticを実施します。これによってcssやjsなどの静的ファイルがSTATIC_ROOTで設定したパス配下に集められ、Nginx管轄で配信されます。

実施しないと静的ファイルがページに適用されません。

仮想環境に入った状態でmanage.pyの階層に移動し、以下のコマンドを実行します。sudoで実施する必要があるのですが、絶対パスでvenvのpythonを指定する必要がある点に注意してください。

sudo /home/<ユーザー名>/<仮想環境名>/bin/python manage.py collectstatic

「○○ static files copied to ‘/usr/share/nginx/html/deploy-test/static’.」と表示されればOKです。

動作確認

これで一通りの構築は完了しました。

ブラウザからhttp://127.0.0.1にアクセスし、以下のように表示されればOKです。

ただし、おそらく502エラーが出ると思うので、以下を参照して設定を変更してください。

こんな時は

アクセスすると「502 Bad Gateway」になる場合、Nginxのエラーログを確認します。

sudo nano /var/log/nginx/error.log

例えば以下のようなsockファイルへのパーミッションエラーが出ていた場合、sockファイルのアクセス権を確認します。

ls -l /home/saba/testenv/webapp-class-deploy-test/project/deploy-test.sock

アクセス権がrootとなっており、Nginxのデフォルトの実行ユーザーは「www-data」なのでNginxからソケットファイルへアクセスできないためエラーとなっています。

そこで、sockファイルとNginxの実行ユーザーを現在作業しているユーザー(この記事の場合saba)に変更します。

まずソケットファイルのパーミッションを以下のようなコマンドで変更します。

ひな形はこちら

sudo chown <ユーザー名>:<ユーザー名(グループ)> /home/saba/testenv/webapp-class-deploy-test/project/deploy-test.sock

私の場合は以下のようになります。ユーザー名とそのユーザーの属するグループを指定します。デフォルトではどちらも同じ値です。

sudo chown saba:saba /home/saba/testenv/webapp-class-deploy-test/project/deploy-test.sock

次にNginxの実行権限を変更します。

sudo nano /etc/nginx/nginx.conf

ファイルの一番上が以下のような記述であればwww-dataが実行ユーザーとなっています。

user www-data;

これを今操作しているユーザーに変更します。私の場合は以下のようになります。

user saba;

これで以上です。

Nginxの実行ユーザーは「必ずroot以外」を設定してください。何も考えなくてもrootを設定すればパーミッションエラーは回避できますが、クラッキングでサーバーが侵害された際に攻撃者がすべての操作を行えてしまうので、セキュリティ上良くありません。

余談:プログラムを更新した際の反映方法

開発環境で変更をコミット、プッシュしたのち、サーバー側の.gitが存在する階層でプルします。

git pull

パスワードは毎回求められるので、序盤で鍵ペアを生成した際に設定したパスワードを入力します。

プル後、デーモン化しているGunicornのプロセス(この記事の例ではdeploy-test)を再起動します。

sudo systemctl restart deploy-test