はじめに
nginxにPythonが動作する環境を作ります。つまりPythonでウェブサービスを作る基盤を作ります。Django・Flask・Bottleといったフレームワークは使いません。
本稿はCentOSを対象としています。Raspbianをご利用の場合は ラズパイ編 もご覧ください。
執筆時の「さくらのVPS」のCentOSのバージョンは7.5でした。
# cat /etc/redhat-release
CentOS Linux release 7.5.1804 (Core)
必要なものをインストール
nginxとPython、そして両者を繋ぐ役割を担うuwsgiをインストールします。
# yum update
# yum -y install nginx
# yum search python3
# yum -y install python34-devel
# yum -y install python34-setuptools
# easy_install-3.4 pip
# pip3 install uwsgi
各バージョンは次のようになりました。
# nginx -v
nginx version: nginx/1.12.2
# python3 --version
Python 3.4.8
# uwsgi --version
2.0.17
uwsgi 設定
必要なディレクトリを作成します。
# mkdir -p /etc/uwsgi/vassals
uwsgiの設定をemperor.ini
に記述します。
[uwsgi]
emperor = /etc/uwsgi/vassals
uid = nginx
gid = nginx
logto = /var/log/uwsgi/uwsgi.log
touch-logreopen = /var/log/uwsgi/touch-logreopen
master = true
vacuum = true
ignore-sigpipe = true
ignore-write-errors = true
disable-write-exception = true
上記のignore-sigpipe
ignore-write-errors
disable-write-exception
は、/var/log/uwsgi/uwsgi.log
に頻繁に現れるOSError
を消すのに有効でした。
続いてバーチャルホストごとの設定を vassals/ホスト名.ini
に記述します。原理的にはこの設定ファイルをバーチャルホストの数だけ用意する必要があります。しかしuwsgiには「設定ファイル内で%n
と記述することで自身のファイル名を参照できる」という便利な機能があるため、個々のファイル内に具体的なホスト名を記述する必要がなく、実質的にはテンプレートを1個だけ作り、これにホスト名のシンボリックリンクを張ることで設定ファイルを容易に量産できます。まずはそのテンプレートをvhosts.ini
として作成します。
[uwsgi]
chdir = /home/app/%n/wsgi
module = wsgi:application
socket = /var/run/uwsgi/%n.sock
chmod-socket = 644
reload-mercy = 1
processes = %k
die-on-term = true
py-autoreload = 1
enable-threads = true
threads = 8
上記のpy-autoreload
は、Pythonスクリプトの内容を変更する度にuwsgiを再起動する必要を無くすための便利な設定です。またenable-threads
およびthreads
は、nginxの受信した複数のリクエストを同時に処理させるために必要な設定です。この設定がないとuwsgiは先のリクエストを処理し終わるまで次のリクエストの処理を始めてくれません。
上記のテンプレートにホスト名のシンボリックリンクを作成します。こちらの例ではexample.com
としています。これをバーチャルホストの数だけこれを作成します。
# cd /etc/uwsgi/vassals
# ln -s ../vhosts.ini example.com.ini
uwsgiのサービス化
uwsgiが常に動作し続けるようにsystemdでサービス化します。
[Unit]
Description=uWSGI Emperor
After=syslog.target
[Service]
ExecStartPre=-/bin/mkdir -p /var/log/uwsgi
ExecStartPre=-/bin/chown -R nginx:nginx /var/log/uwsgi
ExecStartPre=-/bin/mkdir -p /var/run/uwsgi
ExecStartPre=-/bin/chown -R nginx:nginx /var/run/uwsgi
ExecStart=/usr/bin/uwsgi --ini /etc/uwsgi/emperor.ini
RuntimeDirectory=uwsgi
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all
[Install]
WantedBy=multi-user.target
uwsgiを起動させます。start
で起動したことをstatus
で確認し、今後は自動的に起動されるようにenable
を指定します。
# systemctl start uwsgi
# systemctl status uwsgi
# systemctl enable uwsgi
uwsgiの循環ログ
uwsgiのログが際限なく巨大化してしまうことをlogrotateによって予防しておきます。
/var/log/uwsgi/uwsgi.log {
compress
daily
missingok
notifempty
rotate 7
sharedscripts
size 1M
postrotate
touch /var/log/uwsgi/touch-logreopen
endscript
}
nginx 設定
バーチャルドメインexample.com
の設定例です。
server {
listen 443 ssl;
server_name example.com;
root /home/app/example.com;
location / {
try_files /resource$uri @uwsgi;
}
location @uwsgi {
include uwsgi_params;
uwsgi_pass unix:///var/run/uwsgi/example.com.sock;
uwsgi_connect_timeout 600s;
uwsgi_read_timeout 600s;
}
ssl_certificate /etc/nginx/ssl/self_signed.crt;
ssl_certificate_key /etc/nginx/ssl/self_signed.key;
}
try_files
の行には、リクエストされたファイルが/home/app/example.com/resource
内に存在すればこれを直接出力し、実在しなければ代わりにuwsgiのPythonへ処理を繋ぐようにする指示です。したがって例えば画像ファイルなどはresource
に配置しておきます。
uwsgi_connect_timeout
とuwsgi_read_timeout
は/var/log/nginx/error.log
に頻出していたupstream timed out
を消すのに効果がありました。
オレオレ証明書
前述のexample.com.conf
に記述したSSLの証明書を作成します。本題から逸れる内容なので本稿ではオレオレ証明書で済ませますが、実際の運用に際してはLet's Encryptなどで証明書を得るのが良いでしょう。
# mkdir /etc/nginx/ssl
# chmod 700 /etc/nginx/ssl
# cd /etc/nginx/ssl
# openssl genrsa 2048 > self_signed.key
# openssl req -new -key self_signed.key > self_signed.csr
---- 必要情報を入力 ----
# openssl x509 -days 3650 -req -signkey self_signed.key < self_signed.csr > self_signed.crt
# chmod 600 /etc/nginx/ssl/*
Python配置
example.com
のドキュメントルートとなるディレクトリを作成して、動作試験用のPythonスクリプトを配置します。
# su app
$ chmod 711 /home/app
$ mkdir -p /home/app/example.com/resource
$ mkdir -p /home/app/example.com/wsgi
上の例ではユーザーappのホームにバーチャルホスト名のディレクトリを作成していますが、/etc/nginx/conf.d/example.com.conf
のroot
と一致していれば、全く別のディレクトリでもかまいません。nginxとuwsgiからも参照できるようにパーミッションを変更します。
# -*- coding: utf-8 -*-
def application(environ, start_response):
try:
body = {k + ': ' + str(v) for k, v in environ.items()}
start_response('200 OK', [('Content-type', 'text/plain')])
return ['\n'.join(body).encode()]
except:
import traceback
start_response('500 Internal Server Error', [('Content-type', 'text/plain')])
return [traceback.format_exc().encode()]
このスクリプトの保存ディレクトリ/home/app/example.com/wsgi/
と、ファイル名のwsgi
、および関数名のapplication
は、先のuwsgiの設定のvhosts.ini
の中で指定した文字列と一致させています。言い換えると、これらに一貫性のある変更を加えれば別の文字列にも変更できます。
nginx 起動
start
で起動したことをstatus
で確認し、今後は自動的に起動されるようにenable
を指定します。
# systemctl start nginx
# systemctl status nginx
# systemctl enable nginx
動作確認
確認前に念のため nginx と uswgi を再起動します。
# systemctl restart nginx
# systemctl restart uwsgi
ブラウザでhttps://example.com/
へアクセスします。オレオレ証明書を使用しているためブラウザが警告を出しますが、ページの表示を強行します。wsgi.py が出力する環境変数の一覧がブラウザに表示されれば成功です。
結果が好ましくない場合は/var/log/nginx/error.log
および/var/log/uwsgi/uwsgi.log
に記されたエラーの内容から解決の糸口が得られるかもしれません。
終わりに
本稿は各項の詳細を深く理解して合理的に導いた結果ではなく、ネットに散在する情報の断片を元に試行錯誤を重ねて経験的に導いた現時点での解決策を紹介しています。誤りや改善点ご指摘いただくと助かります。
本稿と同様の内容の ラズパイ編 もご覧ください。