CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: mod_wsgi の組み込みモードとデーモンモードについて

Python で Web アプリケーションを開発する場合、WSGI という仕様に沿った形で作られているとアプリケーションのポータビリティが向上する。 WSGI の仕様に沿っているアプリケーションは、異なる実装の WSGI サーバであっても動作させることができるためだ。

今回は数ある WSGI サーバの中でも Apache httpd 上で動作することで有名な mod_wsgi について扱う。 この mod_wsgi にはふたつの動作モードがあり、それぞれ組み込みモード (embedded mode) とデーモンモード (daemon mode) という名前がついている。 今回は mod_wsgi を組み込みモードとデーモンモードのそれぞれで動作させた上で、両者がどのように異なるのかについて調べていく。

尚、今回の検証には CentOS7 を使った。

$ cat /etc/redhat-release 
CentOS Linux release 7.1.1503 (Core)
$ uname -r
3.10.0-229.el7.x86_64

下準備

まずは mod_wsgi を使った動作確認ができるところまで持っていく。

mod_wsgi を yum でインストールする。

$ sudo yum -y install mod_wsgi

動作確認用の WSGI アプリケーションを用意する。 このアプリケーションは HTTP のアクセスがあるとそれを処理したプロセス ID を出力する。 ちなみに mod_wsgi は、後述する設定ファイルで指定した .py または .wsgi ファイルの中にある 'application' というシンボルを WSGI アプリケーションとして認識する。

$ cat << EOF | sudo tee /var/www/cgi-bin/myapp.wsgi > /dev/null
# -*- coding: utf-8 -*-

import os


def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    pid = os.getpid()
    msg = b'Process ID: {pid}\n'.format(pid=pid)
    return [msg]

EOF

mod_wsgi を組み込みモードで動作させる

まずは mod_wsgi を組み込みモードで動作させてみよう。 mod_wsgi はデフォルトで組み込みモードで動作する。 そのため設定はとてもシンプルで WSGIScriptAlias にデプロイ先と WSGI アプリケーションのパスを渡すだけで良い。

$ cat << EOF | sudo tee /etc/httpd/conf.d/wsgi.conf > /dev/null
WSGIScriptAlias /myapp /var/www/cgi-bin/myapp.wsgi
EOF

Apache httpd を起動する。

$ sudo systemctl start httpd
$ sudo systemctl enable httpd

WSGI アプリケーションに curl コマンドでアクセスしてみよう。

$ curl http://localhost/myapp
Process ID: 15950
$ curl http://localhost/myapp
Process ID: 15953
$ curl http://localhost/myapp
Process ID: 15952

どうやら毎回リクエストを処理しているプロセス ID が異なるようだ。

上記のプロセス ID は Apache httpd のそれと同じになっている。 そう、つまり組み込みモードでは Apache httpd が静的ファイルなどを処理するのと同じプロセスが WSGI アプリケーションも処理することになっている。 今の Apache httpd はデフォルトではマルチプロセス (prefork) で動作するため、その場合は必然的に WSGI アプリケーションもマルチプロセスとなる。

$ ps auxww | grep [h]ttpd
root     15949  0.0  0.5 217144  5404 ?        Ss   12:27   0:00 /usr/sbin/http -DFOREGROUND
apache   15950  0.0  0.7 220352  7964 ?        S    12:27   0:00 /usr/sbin/http -DFOREGROUND
apache   15951  0.0  0.6 219188  6168 ?        S    12:27   0:00 /usr/sbin/http -DFOREGROUND
apache   15952  0.0  0.7 220352  7964 ?        S    12:27   0:00 /usr/sbin/http -DFOREGROUND
apache   15953  0.0  0.7 220352  7964 ?        S    12:27   0:00 /usr/sbin/http -DFOREGROUND
apache   15954  0.0  0.6 219188  6168 ?        S    12:27   0:00 /usr/sbin/http -DFOREGROUND

mod_wsgi をデーモンモードで動作させる

次はデーモンモードで動かしてみる。 先ほどの組み込みモードでは Apache httpd が静的ファイルを処理するのと同じプロセスで WSGI アプリケーションを処理していた。 それに対し、デーモンモードでは WSGI アプリケーションを処理するために専用のプロセスが新たに用意される。 つまり、デーモンモードでは WSGI アプリケーションへのリクエストがあった場合に Apache httpd のフロントエンドのプロセス達がデーモンプロセスに対する Proxy のように動作するイメージだ。

デーモンモードで動かす場合の設定は先ほどより多少多くなる。 WSGISocketPrefix はフロントエンドのプロセスとデーモンプロセスが通信するための UNIX ドメインソケットの場所を設定している。 これは明示的に設定しなくても問題ない場合もあるようだ。 WSGIDaemonProcess はデーモンプロセスの設定を担っている。 デーモンプロセスはデフォルトでは 1 プロセス 15 スレッドの状態で用意される。 そして、Location ディレクティブを使って動作する場所を指定した上で WSGIProcessGroup で使用するデーモンプロセスを指定する。

$ cat << EOF | sudo tee /etc/httpd/conf.d/wsgi.conf > /dev/null

WSGISocketPrefix /var/run/wsgi

WSGIDaemonProcess myapp-process
WSGIScriptAlias /myapp /var/www/cgi-bin/myapp.wsgi
<Location /myapp>
WSGIProcessGroup myapp-process
</Location>
EOF

設定できたら Apache httpd を再起動しよう。

$ sudo systemctl restart httpd

先ほどと同様に curl で WSGI アプリケーションにアクセスしてみる。

$ curl http://localhost/myapp
Process ID: 16175
$ curl http://localhost/myapp
Process ID: 16175
$ curl http://localhost/myapp
Process ID: 16175

今度は全てのリクエストが同一プロセスで処理されていることがわかる。

まとめ

今回は mod_wsgi の二つの動作モード、組み込みモードとデーモンモードを動かしてみた。 組み込みモードでは、設定する項目が少ない反面チューニングの余地も少なく、必然的にマルチプロセスになってしまう点に注意が必要といえる。 特にマルチプロセスに関しては、WSGI アプリケーションをマルチプロセスで適切に動作するようにあらかじめ想定した上で作っていないと問題となる可能性が高い。 それに対し、デーモンモードであればプロセス数やスレッド数を任意の値に設定できる。 また、mod_wsgi を動作させる場合のデフォルトが組み込みモードとなっているのは、そちらが推奨されているためではなく後方互換性を保つためという理由のようだ。 以上の理由から、特に理由がないのであれば mod_wsgi はデーモンモードを使って動かした方が無難なようだ。