Nginx に uWSGI + Django アプリ を組み込む

はじめに

 Django アプリを uWSGI でサービスとして起動して、Nginx でアクセスできるようにする手順をまとめました。
 Nginx のインストールはこちらの投稿を、Python の venv 環境を構築できるようにする手順はこちらの投稿を参照してください。
 なお、Linux ユーザーはホストの設定を操作する、wheel グループに属するユーザー user1 と、Webアプリケーションを管理する、nginx グループに属するユーザー webmaster の 2 つのアカウントを切り替えながら作業を進めます。これらのユーザーは下記コマンドで登録済みであるものとします。
 ホスト管理者 (user1) の領分と、アプリケーション管理者 (webmaster) の領分について、私なりの切り替え方が参考になれば幸いです。

[root]# useradd user1 -G wheel
[root]# useradd webmaster -g nginx

uWSGI アプリケーションサーバーの構築

 Django フレームワークを使って開発した Web アプリケーションは、WSGI で動作するよう設計されています。Web アプリケーションを 1 つのアプリケーションサーバーとして起動、停止ができるように構成します。

デプロイ用Webアプリのディレクトリ構成

 開発したアプリケーションのディレクトリ構造は下記のようになっていることを想定します。

webapp/      ← django のプロジェクトルート (django-admin startproject webapp で作成)
  main/      ← django のメインアプリケーション (python manage.py startapp main で作成)
  static/    ← 静的ファイルを格納するディレクトリ
  webapp/    ← django のプロジェクト内共通設定など

デプロイが完了したときのディレクトリ構成

 デプロイ先のディレクトリは /var/www/webapppackage として、この中にアプリケーションを格納したいと思います。
 この /var/www/webapppackage には、ホスト側の環境設定ファイルなども格納していきます。

/
  var/
    www/
      webapppackage/           ← デプロイ先ディレクトリ
        uwsgi-webapp.service   ← systemctl コマンドで WSGI サーバーを操作するためのサービス設定ファイル
        uwsgi.ini              ← Web アプリケーションを WSGI サーバーとして起動する際に指定する設定ファイル
        venv/                  ← 本番環境用の venv を格納するディレクトリ
        webapp/                ← django のプロジェクトルート
          main/
          static/
          webapp/
[user1]$ sudo mkdir -p /var/www/webapppackage
[user1]$ sudo chown root:nginx /var/www/webapppackage
[user1]$ sudo chmod g+w /var/www/webapppackage

 /var/www/webapppackage の直下に django で開発したアプリケーションのディレクトリを webmaster ユーザーでアップロードしてください。

本番環境用の venv 整備

 Web アプリケーションに必要な Python パッケージを本番環境用にインストールします。
 このとき、OS のグローバルな環境にはインストールせず、この Web アプリケーション専用の環境用にインストールするようにします。
 こうしておくと、2つ目、3つ目の別の Web アプリケーションをインストールすることを考えたとき、互いに独立した環境で動作させることができるようになります。

[webmaster]$ cd /var/www/webwebapppackage
[webmaster]$ /opt/python3.5/bin/pyvenv-3.5 venv
[webmaster]$ source venv/bin/activate

(venv)[webmaster]$ pip install --upgrade pip
(venv)[webmaster]$ pip install django uwsgi
(他に必要なパッケージがあればここでインストールする)

uWSGI プロセス用ディレクトリ作成

 Web アプリケーションがデプロイできたら、次はこれを WSGI を利用して起動できるようにします。
 まずは、WSGI で起動したときのプロセス情報 (pid) を格納するディレクトリを用意します。

[user1]$ sudo mkdir -p /var/run/uwsgi.webapppackage
[user1]$ sudo chown root:nginx /var/run/uwsgi.webapppackage
[user1]$ sudo chmod g+w /var/run/uwsgi.webapppackage
[user1]$ sudo ls -l /var/run/

drwxrwxr-x  2 root nginx    40 Jan 28 10:26 uwsgi.webapppackage

uwsgi コマンドによる WSGI サーバーの動作確認

 uwsgi コマンドで動作確認します。本番環境で実行する事を考慮して、nginx ユーザーとして起動します。
 また、この段階ではまだ Web サーバー (nginx) と連携していないため、一時的に 8000 番ポートを使います。

[webmaster]$ source /var/www/webapppackage/venv/bin/activate
(venv)[webmaster]$ uwsgi --home=/var/www/webapppackage/venv \
                         --uid=nginx \
                         --gid=nginx \
                         --chdir=/var/www/webapppackage/webapp \
                         --module='webapp.wsgi:application' \
                         --env DJANGO_SETTINGS_MODULE=webapp.settings \
                         --master \
                         --vacuum \
                         --pidfile=/var/run/uwsgi.webapppackage/master.pid \
                         --http=127.0.0.1:8000

 home は venv 環境のディレクトリ指定します。
 uid, gid は nginx を指定します。chdir は django のプロジェクトルートディレクトリを指定します。
 module と env には、それぞれ wsgi.py と settings.py までの (python で使うような) パスを指定します。
 master オプションを指定すると、アプリケーションサーバーとして起動したとき、ソケットを閉じずに再起動したりできるようにします。一度ソケットが閉じられると、このソケットを参照している他のプログラムで再接続が必要になります。nginx と連携する環境では WSGI サーバーを再起動しても nginx を再起動しなくてすむように、このオプションを付けます。詳しくは uWSGI マニュアルの Reloading the server を参照してください。
 vacuum オプションは WSGI サーバーとして起動するとき、前回異常終了した、などで pid ファイルや socket ファイルが残っているとき、これらをクリアします。
 pidfile はそのまま pid ファイルを置く場所を指定します。このディレクトリは先の手順で作成したものを指定しています。
 http は WSGI 単体で Web サーバーとして起動するときに指定します。今回は動作確認のために指定しますが、最終的にはフロントエンドの Web サーバー nginx に連携させるため、ここは socket オプションに差し替えます。

 この状態で http://127.0.0.1:8000/ にアクセスして、トップページが表示されれば成功です。WSGI サーバーの停止は 「CTRL + c」を押下してください。

uWSGI 設定ファイル作成

 先の WSGI コマンドをもとに、オプションをファイルに書き出します。

/var/www/webapppackage/uwsgi.ini (webmaster で作成)
[uwsgi]
uid = nginx
gid = nginx

chdir = /var/www/webapppackage/webapp
module = webapp.wsgi:application

master = true
vacuum = true
pidfile = /var/run/uwsgi.webapppackage/master.pid
#socket = /var/run/uwsgi.webapppackage/master.sock
http = 127.0.0.1:8000

processes = 5
die-on-term = true

 home オプションと env オプションを削除しました。この 2 つは systemctl コマンドを使って WSGI サーバーをサービスとして起動する際に指定します。
 次のコマンドを実行して、WSGI 単体で起動すること、Web アプリケーションにアクセスできること の 2 点を確認してください。

(venv)[webmaster]$ uwsgi --ini /var/www/webapppackage/uwsgi.ini

systemd 向けサービス設定ファイル作成と動作確認

 WSGI サーバー単体での起動ができるようになりました。次は、手動で起動、停止していたものを、systemctl コマンドを使って起動、停止できるようにします。
 起動スクリプトは /var/www/webapppackage に置き、systemctl コマンド用の設定ファイルを格納するディレクトリにシンボリックリンクを張って適用します。

/var/www/webapppackage/uwsgi-webapp.service (webmaster で作成)
[Unit]
Description=uWSGI service for webapp

[Service]
ExecStartPre=/bin/bash -c 'mkdir -p /var/run/uwsgi.webapppackage; chown root:nginx /var/run/uwsgi.webapppackage; chmod g+w /var/run/uwsgi.webapppackage'
ExecStart=/bin/bash -c 'source /var/www/webapppackage/venv/bin/activate; DJANGO_SETTINGS_MODULE=webapp.settings; uwsgi --ini /var/www/webapppackage/uwsgi.ini'

[Install]
WantedBy=multi-user.target

 pid ファイルと socket ファイルを格納するディレクトリがなかった場合に備えて、ExecStartPre にディレクトリを作成するコマンドを指定します。
 ExecStart に (1) venv 環境を有効にして (2) 環境変数を設定し、(3) WSGI サーバーを起動する コマンドを指定します。uwsgi.ini を作成するときに除外した home オプションと env オプションの内容は、(1) と (2) に組み込みました。
 このサービス設定ファイルをインストールします。

[user1]$ cd /etc/systemd/system
[user1]$ sudo ln -s /var/www/webapppackage/uwsgi-webapp.service uwsgi-webapp.service

 systemctl コマンドで WSGI サーバーの起動と停止ができるか確認します。

[user1]$  sudo systemctl start uwsgi-webapp
[user1]$  sudo systemctl status uwsgi-webapp
uwsgi-webapp.service - uWSGI service for webapp
   Loaded: loaded (/var/www/webapppackage/uwsgi-webapp.service; linked)
   Active: active (running) since Thu 2016-01-28 14:07:12 JST; 4s ago
  Process: 23809 ExecStartPre=/bin/bash -c mkdir -p /var/run/uwsgi.webapppackage; chown root:nginx /var/run/uwsgi.webapppackage; chmod g+w /var/run/uwsgi.webapppackage (code=exited, status=0/SUCCESS)
 Main PID: 23815 (bash)
   CGroup: /system.slice/uwsgi-webapp.service
           |-23815 /bin/bash -c source /var/www/webapppackage/venv/bin/activate; DJANGO_SETTINGS_MODULE=webapp.settin...
           |-23816 uwsgi --ini /var/www/webapppackage/uwsgi.ini
           |-23818 uwsgi --ini /var/www/webapppackage/uwsgi.ini
           |-23819 uwsgi --ini /var/www/webapppackage/uwsgi.ini
           |-23820 uwsgi --ini /var/www/webapppackage/uwsgi.ini
           |-23821 uwsgi --ini /var/www/webapppackage/uwsgi.ini
           |-23822 uwsgi --ini /var/www/webapppackage/uwsgi.ini
           `-23823 uwsgi --ini /var/www/webapppackage/uwsgi.ini

Jan 28 14:07:12 dos-vps-srv61 bash[23815]: *** Operational MODE: preforking ***
Jan 28 14:07:14 dos-vps-srv61 bash[23815]: WSGI app 0 (mountpoint='') ready in 2 seconds on interpreter 0xc7bef0... app)
Jan 28 14:07:14 dos-vps-srv61 bash[23815]: *** uWSGI is running in multiple interpreter mode ***
Jan 28 14:07:14 dos-vps-srv61 bash[23815]: spawned uWSGI master process (pid: 23816)
Jan 28 14:07:14 dos-vps-srv61 bash[23815]: spawned uWSGI worker 1 (pid: 23818, cores: 1)
Jan 28 14:07:14 dos-vps-srv61 bash[23815]: spawned uWSGI worker 2 (pid: 23819, cores: 1)
Jan 28 14:07:14 dos-vps-srv61 bash[23815]: spawned uWSGI worker 3 (pid: 23820, cores: 1)
Jan 28 14:07:14 dos-vps-srv61 bash[23815]: spawned uWSGI worker 4 (pid: 23821, cores: 1)
Jan 28 14:07:14 dos-vps-srv61 bash[23815]: spawned uWSGI worker 5 (pid: 23822, cores: 1)
Jan 28 14:07:14 dos-vps-srv61 bash[23815]: spawned uWSGI http 1 (pid: 23823)
Hint: Some lines were ellipsized, use -l to show in full.

 トップページにアクセスできることを確認してください。うまく表示できたら、次は Nginx と連携させるべく、WSGI の http サーバーではなく、socket を開くよう設定を変更します。

[user1]$ sudo systemctl stop uwsgi-webapp
/var/www/webapppackage/uwsgi.ini (webmaster, user1 どちらでも可。手順の流れ上 user1 がオススメ)
[uwsgi]
uid = nginx
gid = nginx

chdir = /var/www/webapppackage/webapp
module = webapp.wsgi:application

master = true
vacuum = true
pidfile = /var/run/uwsgi.webapppackage/master.pid
socket = /var/run/uwsgi.webapppackage/master.sock
#http = 127.0.0.1:8000

processes = 5
die-on-term = true

 ここでもう一度サービスの状態を確認しましょう。
 http で起動したときとよく似ていますが、1点、最後から2行目から「spawned uWSGI http 1」の行がなくなったことを確認してください。

[user1]$ sudo systemctl start uwsgi-webapp
[user1]$ sudo systemctl status uwsgi-webapp
uwsgi-webapp.service - uWSGI service for webapp
   Loaded: loaded (/var/www/webapppackage/uwsgi-webapp.service; linked)
   Active: active (running) since Thu 2016-01-28 14:10:19 JST; 3s ago
  Process: 23840 ExecStartPre=/bin/bash -c mkdir -p /var/run/uwsgi.webapppackage; chown root:nginx /var/run/uwsgi.webapppackage; chmod g+w /var/run/uwsgi.webapppackage (code=exited, status=0/SUCCESS)
 Main PID: 23846 (bash)
   CGroup: /system.slice/uwsgi-webapp.service
           |-23846 /bin/bash -c source /var/www/webapppackage/venv/bin/activate; DJANGO_SETTINGS_MODULE=webapp.settin...
           |-23847 uwsgi --ini /var/www/webapppackage/uwsgi.ini
           |-23849 uwsgi --ini /var/www/webapppackage/uwsgi.ini
           |-23850 uwsgi --ini /var/www/webapppackage/uwsgi.ini
           |-23851 uwsgi --ini /var/www/webapppackage/uwsgi.ini
           |-23852 uwsgi --ini /var/www/webapppackage/uwsgi.ini
           `-23853 uwsgi --ini /var/www/webapppackage/uwsgi.ini

Jan 28 14:10:20 dos-vps-srv61 bash[23846]: mapped 436608 bytes (426 KB) for 5 cores
Jan 28 14:10:20 dos-vps-srv61 bash[23846]: *** Operational MODE: preforking ***
Jan 28 14:10:21 dos-vps-srv61 bash[23846]: WSGI app 0 (mountpoint='') ready in 1 seconds on interpreter 0x1026a4... app)
Jan 28 14:10:21 dos-vps-srv61 bash[23846]: *** uWSGI is running in multiple interpreter mode ***
Jan 28 14:10:21 dos-vps-srv61 bash[23846]: spawned uWSGI master process (pid: 23847)
Jan 28 14:10:21 dos-vps-srv61 bash[23846]: spawned uWSGI worker 1 (pid: 23849, cores: 1)
Jan 28 14:10:21 dos-vps-srv61 bash[23846]: spawned uWSGI worker 2 (pid: 23850, cores: 1)
Jan 28 14:10:21 dos-vps-srv61 bash[23846]: spawned uWSGI worker 3 (pid: 23851, cores: 1)
Jan 28 14:10:21 dos-vps-srv61 bash[23846]: spawned uWSGI worker 4 (pid: 23852, cores: 1)
Jan 28 14:10:21 dos-vps-srv61 bash[23846]: spawned uWSGI worker 5 (pid: 23853, cores: 1)
Hint: Some lines were ellipsized, use -l to show in full.

 pid ファイルとともに、socket ファイルができていることを確認します。

[user1]$ ls -l /var/run/uwsgi.webapppackage/
total 4
-rw-r--r-- 1 root  root  6 Jan 28 14:10 master.pid
srwxr-xr-x 1 nginx nginx 0 Jan 28 14:10 master.sock

 ここまでの手順で、アプリケーションサーバーとして起動、停止ができるサービス uwsgi-webapp を構成できました。

Nginx との連携

 バックエンドとなるアプリケーションサーバーの準備ができました。あとは、ブラウザーなどのクライアントと直接やりとりをするフロントエンド、Web サーバー Nginx を構成します。
 今回、「 http://webapp.example.com/ 」にこの Web アプリをマッピングしたいと思います。

/etc/nginx/conf.d/webapp.example.com.conf (user1 で作成)
server {
    listen       80;
    listen  [::]:80;
    server_name  webapp.example.com;

    access_log  /var/log/nginx/webapp.example.com-access_log  main;
    error_log  /var/log/nginx/webapp.example.com-error_log;

    location / {
        include  uwsgi_params;
        uwsgi_pass  unix:/var/run/uwsgi.webapppackage/master.sock;
    }

    location /static/ {
        root  /var/www/webapppackage/webapp;
    }

    location ~ /\.ht {
        deny  all;
    }
}

 ルートディレクトリ / に直接 Web アプリケーションをマップするため、ドキュメントルートを指定する root の設定を省略しました。
 ルートディレクトリ / に来るアクセスはバックエンドであるアプリケーションサーバー (WSGI サーバー) に転送します。
 また、静的ファイルはアプリケーションサーバーではなく Web サーバーの機能で提供したいので、フロントエンドである Nginx で /static/ をハンドルして返すようにします。

Nginx の起動と動作確認

 Nginx を起動して動作確認します。

[user1]$ sudo systemctl start nginx

 http://webapp.example.com/ にアクセスして、トップページが表示されること、http://webapp.example.com/static/xxx.txt などにアクセスして、静的ファイルが取得できることを確認してください。

自動起動設定

 以上で、Django フレームワークを使った Web アプリケーションを本番環境に適用することができます。

[user1]$ sudo systemctl enable uwsgi-webapp
[user1]$ sudo systemctl enable nginx

おわりに

 手順は少し複雑ですが、Web サーバーとアプリケーションサーバーを明確に分離できるため、コネクション数やプロセス数の調整がしやすくなります。  今回、Web サーバーとアプリケーションサーバーを 1 つホストに構成しましたが、ローカルの unix socket の代わりに TCP socket を使用するよう構成すれば、簡単にホストを分離することもできます。
 作業は 2 ユーザーに分かれて実施しました。基本的な考え方は、アプリケーションのデプロイ先ディレクトリ (/var/www/webapppackage) 以下については webmaster で作業、それ以外は user1 で作業、となります。
 もし /var/www/webapppackage の中でどちらのユーザーも編集する可能性があるファイル (本投稿の中では uwsgi.ini と uwsgi-webapp.service) があれば、webmaster でファイルを作成してください。user1 は wheel グループに所属しているため、webmaster で作成したファイルでも sudo コマンドを使って編集できますが、逆はできないためです。

Nginx に uWSGI + Django アプリ を組み込む」への1件のフィードバック

  1. ピンバック: Ubuntu + Nginx + MySQLでDjangoアプリをデプロイ(構築編) - IT記事まとめ

コメントを残す

メールアドレスが公開されることはありません。