SIGMA-SE Math & Tech Library

SIGMA-SE Math & Tech Library


数学と情報技術をテーマに、書籍や教材だけではつかみにくい考え方を具体例とともにわかりやすく伝える解説サイトです。

VPSで作るDjangoサイト構築手順 - Nginx編:3/4 Python・Django・uWSGI設定

概要

VPS上にPython、Django、uWSGIを導入し、NginxからDjangoアプリケーションを呼び出すための設定を整理する。

Djangoのrunserverは開発用のサーバーであり、本番運用には使用しない。
この記事では、Nginxが受け取ったHTTP/HTTPSリクエストをuWSGIプロトコルでuWSGIへ渡し、uWSGIがDjangoのWSGIアプリケーションを実行する構成を作る。

前提環境

  • OS
    CentOS Stream 9
  • 言語
    Python 3.11
  • Webサーバー
    Nginx
  • アプリケーションサーバー
    uWSGI 2.0系
  • フレームワーク
    Django 5.2 LTS
  • ドメイン
    example.com
  • 作業ユーザー
    vpsuser
  • uWSGI実行ユーザー
    sweb(ログイン不可の専用システムユーザー)
  • Djangoプロジェクト
    /var/www/projs/sweb
  • Python仮想環境
    /var/www/venvs/sweb

この記事で扱うこと

  • Python 3.11とビルド環境のインストール。
  • venvを使った仮想環境とDjangoプロジェクトの作成。
  • 管理ユーザーとuWSGI専用ユーザーの権限分離。
  • Nginx、uWSGI、Djangoがリクエストを受け渡す仕組み。
  • systemdによるuWSGIの起動とログ確認。
  • loopback接続とSELinuxを考慮したNginx連携。

作業時の注意点

  • Pythonの実行元
    仮想環境を有効化する前はpython3.11を明示し、有効化した後は仮想環境内のpythonpipを使用する。
  • uWSGIプロトコル
    socket = 127.0.0.1:8001はHTTPではなくuWSGIプロトコルで待ち受けるため、8001番ポートへcurlで直接アクセスしない。
  • 待受け範囲
    uWSGIはloopbackだけで待ち受け、8001番ポートをVPSのパケットフィルターやfirewalldへ追加しない。
  • プロセス管理
    uWSGIをデーモン化せずsystemdの管理下でフォアグラウンド実行し、ログはjournaldで確認する。
  • 実行権限
    管理作業はvpsuser、uWSGIの実行はログインできないswebユーザーに分ける。配置作業ではumask 0027を設定し、swebにはsudo権限とソースコードの書込み権限を与えない。
  • 秘密情報
    パスワードやSECRET_KEYをsystemdのunitファイルへ直接書かない。本番用の秘密情報は次の記事で/etc/sweb.envへ分離する。

実施内容

Pythonとビルド環境のインストール

  • Python 3.11、pip、開発ファイル、Cコンパイラをインストール
    CentOS Stream 9の無印pythonpython3は別のバージョンを指す場合があるため、ここではpython3.11を明示する。python3.11-develgccは、uWSGIを仮想環境へインストールするときのビルドに使用する。

    $ sudo dnf install -y python3.11 python3.11-pip python3.11-devel gcc
    $ python3.11 --version
    $ python3.11 -m pip --version

仮想環境とDjango・uWSGIのインストール

  • uWSGI専用の実行ユーザーを作成
    管理ユーザーとアプリケーションの実行権限を分けるため、ログインできないswebシステムユーザーを作成する。vpsuserは、次の記事でswebグループの環境ファイルを読み取れるよう同じグループへ追加する。

    $ sudo useradd --system --user-group --home-dir /nonexistent --shell /sbin/nologin sweb
    $ sudo usermod -aG sweb vpsuser
    $ getent passwd sweb

    追加した補助グループは次回ログイン時から反映される。作業中のSSH接続を安全に残したまま別のターミナルから再接続し、idの結果にswebが含まれることを確認する。

    $ id
  • プロジェクト用ディレクトリを作成
    アプリケーションのソースコードとPython仮想環境を分けて配置する。所有者を管理用のvpsuser、グループを実行用のswebとし、setgidによって配下にもグループを引き継ぐ。umask 0027によって、同じグループのswebには読取り・実行だけを許可する。

    $ umask 0027
    $ sudo install -d -o vpsuser -g sweb -m 2750 /var/www/projs/sweb
    $ sudo install -d -o vpsuser -g sweb -m 2750 /var/www/venvs
  • Python 3.11で仮想環境を作成
    システム領域へsudo pip installせず、プロジェクト専用の仮想環境へPythonパッケージを導入する。

    $ python3.11 -m venv /var/www/venvs/sweb
    $ source /var/www/venvs/sweb/bin/activate
    $ python --version
    $ python -m pip install --upgrade pip
  • DjangoとuWSGIをインストール
    Djangoは5.2系、uWSGIは2.0系の範囲で更新を受け取れるように指定する。Django 5.2はLTSで、Python 3.11をサポートしている。

    $ python -m pip install "Django>=5.2,<5.3" "uWSGI>=2.0,<2.1"
    $ python -m django --version
    $ uwsgi --version

    検証済みの環境を再構築できるよう、実際に採用したバージョンは依存関係ファイルへ固定して管理する。

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

  • Djangoプロジェクトを作成
    configは設定ファイルを置くDjangoプロジェクトパッケージ、tblogはブログ機能を実装するアプリケーションとする。

    $ source /var/www/venvs/sweb/bin/activate
    $ cd /var/www/projs/sweb
    $ django-admin startproject config .
    $ python manage.py startapp tblog

    作成後の主な構成は次のようになる。

    /var/www/projs/sweb/
    ├── config/
    │   ├── settings.py
    │   └── wsgi.py
    ├── tblog/
    └── manage.py
  • アプリケーションを登録
    /var/www/projs/sweb/config/settings.pyINSTALLED_APPSへ作成したアプリケーションを追加する。

    INSTALLED_APPS = [
        "tblog.apps.TblogConfig",
        "django.contrib.admin",
        "django.contrib.auth",
        "django.contrib.contenttypes",
        "django.contrib.sessions",
        "django.contrib.messages",
        "django.contrib.staticfiles",
    ]
  • 公開ホストの最低限の設定
    同じsettings.pyで開発モードを無効にし、Nginxで公開するホスト名を限定する。SECRET_KEYやデータベースなどの本番用設定は次の記事で環境変数へ分離する。

    DEBUG = False
    ALLOWED_HOSTS = ["example.com"]
    $ python manage.py check
  • ソースコードと仮想環境の権限を確認
    配置後にグループ・その他ユーザーの書込み権限を除去する。続くfindで何も表示されなければ、実行ユーザーswebはソースコードと仮想環境を書き換えられない。

    $ chmod -R g-w,o-rwx /var/www/projs/sweb /var/www/venvs/sweb
    $ find /var/www/projs/sweb /var/www/venvs/sweb -perm /0022 -print

uWSGI設定ファイルの作成

  • Nginx、uWSGI、Djangoの役割
    リクエストは次の順序で渡される。Nginxは外部とのHTTP/HTTPS通信を担当し、uWSGIはDjangoのconfig.wsgi.applicationをワーカープロセスで実行する。

    ブラウザ ──HTTP/HTTPS──> Nginx ──uWSGIプロトコル──> uWSGI ──WSGI──> Django
  • uwsgi.iniを作成
    /var/www/projs/sweb/uwsgi.iniを次の内容で作成する。

    [uwsgi]
    chdir = /var/www/projs/sweb
    module = config.wsgi:application
    home = /var/www/venvs/sweb
    
    master = true
    processes = 2
    threads = 2
    single-interpreter = true
    
    socket = 127.0.0.1:8001
    need-app = true
    vacuum = true
    die-on-term = true

    socketはuWSGIプロトコル用となる。127.0.0.1へ限定することで、同じVPS上のNginxからだけ接続できる。元の構築記録にあったchmod-socket = 666は使用せず、uWSGIをdaemonizeまたはlogtoでバックグラウンド化もしない。

systemdサービスの作成

  • sweb.serviceを作成
    /etc/systemd/system/sweb.serviceを次の内容で作成する。uWSGIはログインできない専用のswebユーザーとして起動し、異常終了時だけsystemdが再起動する。

    [Unit]
    Description=uWSGI service for sweb
    After=network.target
    
    [Service]
    Type=simple
    User=sweb
    Group=sweb
    WorkingDirectory=/var/www/projs/sweb
    EnvironmentFile=-/etc/sweb.env
    ExecStart=/var/www/venvs/sweb/bin/uwsgi --ini /var/www/projs/sweb/uwsgi.ini
    Restart=on-failure
    RestartSec=5s
    KillSignal=SIGTERM
    TimeoutStopSec=30s
    UMask=0027
    NoNewPrivileges=true
    PrivateTmp=true
    
    [Install]
    WantedBy=multi-user.target

    EnvironmentFileのパスの先頭にある-は、/etc/sweb.envが未作成でもサービスの起動を失敗させない指定となる。次の記事で秘密情報を保存した後は、同じファイルをサービスから読み込める。

  • unitファイルを検証して起動
    uWSGIをフォアグラウンドで動かすため、標準出力と標準エラーはjournaldへ記録される。

    $ sudo systemd-analyze verify /etc/systemd/system/sweb.service
    $ sudo systemctl daemon-reload
    $ sudo systemctl enable --now sweb
    $ sudo systemctl status sweb --no-pager
    $ sudo journalctl -u sweb -n 50 --no-pager
  • 待受け先を確認
    127.0.0.1:8001だけが表示されることを確認する。0.0.0.0:8001[::]:8001で待ち受けている場合は、uwsgi.iniを見直す。

    $ sudo ss -lntp | grep ':8001'
    $ sudo firewall-cmd --list-all

    8001番ポートは外部公開しないため、firewalldやVPS側のパケットフィルターへ追加する必要はない。

NginxとSELinuxの設定

  • NginxからuWSGIへ転送
    前の記事で作成した/etc/nginx/conf.d/example.com.confのうち、HTTPS用serverブロックにあるlocation /を次の内容へ置き換える。新しいserverブロックを重複して作成しない。

    location / {
        include /etc/nginx/uwsgi_params;
        uwsgi_param HTTP_X_FORWARDED_PROTO $scheme;
        uwsgi_pass 127.0.0.1:8001;
    }

    /etc/nginx/uwsgi_paramsは、リクエストメソッドやパスなどをuWSGIへ渡すためのパラメーターを定義している。HTTP_X_FORWARDED_PROTOは、Nginxが受け取った通信のスキームで毎回上書きする。

  • SELinuxでNginxの接続を許可
    CentOS Stream 9でSELinuxが有効な場合、Nginxがloopback上のuWSGIへ接続できるよう、Red HatのNginxリバースプロキシ手順で案内されているbooleanを有効にする。

    $ getenforce
    $ sudo setsebool -P httpd_can_network_connect 1
    $ getsebool httpd_can_network_connect

    SELinuxを無効化して回避せず、拒否された場合はjournalctlや監査ログで原因を確認する。

設定確認と起動確認

  • Django、systemd、Nginxの設定を確認
    Nginxは構文確認に成功してから再読込みする。check --deployで表示されるHTTPS、Cookie、データベースなどの警告は、次の記事の本番設定で解消してから公開する。

    $ cd /var/www/projs/sweb
    $ /var/www/venvs/sweb/bin/python manage.py check
    $ /var/www/venvs/sweb/bin/python manage.py check --deploy
    $ sudo systemctl is-active sweb
    $ sudo nginx -t
    $ sudo systemctl reload nginx
  • Nginx経由でDjangoの応答を確認
    --resolveで接続先だけをloopbackへ固定し、ドメイン名とTLSのSNIはexample.comのまま確認する。実際の構築では、取得した証明書のドメインへ置き換える。

    $ curl -I --resolve example.com:443:127.0.0.1 https://example.com/admin/login/

    Djangoの応答として200またはログイン画面への302が返れば、Nginx、uWSGI、Djangoの連携を確認できる。502 Bad Gatewayの場合は、systemctl status swebjournalctl -u sweb、8001番ポートの待受け、SELinuxの拒否を順に確認する。

まとめ

  • Djangoの本番構成では、Nginxが外部通信、uWSGIがWSGIアプリケーションの実行を担当する。
  • Pythonパッケージはプロジェクト専用の仮想環境へ導入し、uWSGIも同じPython 3.11環境で動かす。
  • uWSGIは127.0.0.1:8001だけで待ち受け、外部向けのファイアウォールへ8001番ポートを追加しない。
  • uWSGIをsystemdの管理下でフォアグラウンド実行すると、起動・停止・再起動とログ確認を一元化できる。
  • 管理ユーザーとuWSGI実行ユーザーを分けると、アプリケーションから管理者権限やソースコードの書込み権限を切り離せる。
  • Nginxからloopbackへ接続する構成でも、SELinuxのhttpd_can_network_connectを確認し、設定変更後はDjango、systemd、Nginxをそれぞれ検証する。

参考文献

この記事を共有
Xで共有 Facebookで共有 LINEで共有



Copyright SIGMA-SE All Rights Reserved.
s-hama@sigma-se.jp