「Nginxをダウンタイム・ゼロで入れ替える方法」で書いたように、/usr/local/nginx/ のNginxを version 1.0.13 に更新しました。これとは別に、catyというユーザーアカウントのホームディレクトリ内にNginxをインストールして、Nginx+uWSGIとCatyの実験をしようとしています。
Nginx+uWSGIは、Catyに限らず一般的なWSGIアプリケーションを稼働させる良い環境を提供します。簡単に紹介しましょう。
内容:
WSGIとuWSGI
WSGI(Web Server Gateway Interface)*1は、Pythonで書かれたWebアプリケーションとアプリケーションサーバーのあいだのインターフェースを規定した仕様(PEP333)です。WSGI仕様に従ったWebアプリケーションを乗せることができるWebサーバーをWSGIサーバーと呼ぶことにします。
uWSGI(http://projects.unbit.it/uwsgi/)は、WSGIサーバーですが、既存のWebサーバーに機能を追加する形になっています。「WSGIサーバー = Webサーバー + WSGIコンテナ」とするなら、WebサーバーとしてNginx、WSGIコンテナとしてuWSGIを使った組み合わせが Nginx+uWSGI ということになります。
WSGIサーバーを実現する他の組み合わせもあって、Nginxのモジュールとして Nginx WSGI modul(http://wiki.nginx.org/NgxWSGIModule)があります。これは、Nginxのコンパイル時に組み込まれます。一方uWSGIは、Nginxとは別プロセスで走り、uwsgi(全部小文字)プロトコルにより通信します。TomcatとApacheがajpプロトコルで通信するのと似たようなものです。Apacheのmod_jk、mod_proxy_ajpに相当する機能はNginxに最初から組み込まれています。
cherokee+uWSGIという組み合わせもあります。cherokee(http://www.cherokee-project.com/)も軽量WebサーバーでuWSGIと共にWSGIサーバー機能を提供します。
gunicorn(http://gunicorn.org/)というWSGIサーバーも有名みたいです。gunicornだけでも動作するようですが、Nginxを手前(Web側)に置く構成も推奨されています(http://gunicorn.org/deploy.html)。
なぜ Nginx+uWSGI にしたのか
cherokeeは設定や管理がすべてブラウザーGUIから出来るのがウリなんですが、僕には便利とは思えません。Nginxならば、nginx.conf(全体またはその一部)を晒せば済むことが、メニュー選択の手順やら画面キャプチャやらで示さなくてはなりません(その一例はこのページとか)。パラメータに基づいて設定ファイルを生成するとか、いくつかの設定ファイルを切り替えながら実験するとかはcherokeeでは困難です。
gunicornは以前Kuwataさんが試してみたのですが(http://return0.info/note/2010-10.html#id2010-10-09 僕は触ってません)、指定したプロセス数だけCatyの初期化処理が走ってしまうので、もともと長いCatyの起動時間がさらに数倍になってしまいます。起動終了を頻繁に繰り返す実験目的では辛いものがあります。
結局、現状では Nginx+uWSGI が我々の目的にかなう唯一の組み合わせのようです。
uWSGIについて少し
前述のごとくuWSGIは、WSGIアプリケーションの走行環境を提供しますが、マルチプロセス処理をしてくれるので、(サーバー環境に依存しますが)何もしなくてもアプリケーションパフォーマンスが数倍になることがあります。特にメニーコアのコンピュータではその効果が顕著です。
もともとuWSGIはPythonアプリケーションを対象としていましたが、現在では、Perl、Ruby、Luaにも対応し、Erlangクラスタに参加できて、CGIスクリプト/PHPスクリプトも実行できるそうです。JVMとmonoへの対応作業も始まっていますね。hirataraさんの記事 http://d.hatena.ne.jp/hiratara/20110718/1310950381 には、PerlとPythonを混ぜた構成が書いてあります。
Webサーバーは、Apache2, Nginx, cherokee, lighttpd に対応しています。WebサーバーとuWSGIとのあいだのプロトコルは、uwsgi, http, fastcgi が使えます。http://projects.unbit.it/uwsgi/wiki/Mongrel2 に、 Mongrel2やZeroMQと一緒に使う方法が書いてありますが、僕はコレよくわかりません。
uWSGIは、WSGIコンテナ機能だけではなくて、なんだか色々な機能を持っています。順不同で並べてみると:
- 設定がLDAPから取れる http://projects.unbit.it/uwsgi/wiki/useLDAP
- クラスタリングのサポート http://projects.unbit.it/uwsgi/wiki/Clustering
- 複数インスタンスの管理 http://projects.unbit.it/uwsgi/wiki/Emperor
- RPCの直接的サポート http://projects.unbit.it/uwsgi/wiki/RPC
- リクエストルーティングの設定 http://projects.unbit.it/uwsgi/wiki/CustomRouting , http://projects.unbit.it/uwsgi/wiki/InternalRouting
- 高速なメモリ内ストレージ http://projects.unbit.it/uwsgi/wiki/CachingFramework , http://projects.unbit.it/uwsgi/wiki/QueueFramework
- グリーンスレッド http://projects.unbit.it/uwsgi/wiki/uGreen
- UDPでロギング http://projects.unbit.it/uwsgi/wiki/UdpLogging
- 低水準なシグナル機能 http://projects.unbit.it/uwsgi/wiki/SignalFramework
- シグナルを利用した自前のcron的機能 http://projects.unbit.it/uwsgi/wiki/CronInterface
- Pythonデコレーター http://projects.unbit.it/uwsgi/wiki/Decorators
必要そうなこと/面白そうなことをドンドン取り込んでいるようです。なかなかに魅力的。アプリケーションでuWSGI固有の機能を使ってしまうと、他のWSGIサーバーで動かせなくなってしまうのですが、「ロックインされてもいいや」という気さえします。
インストール
僕は、最新安定版の http://projects.unbit.it/downloads/uwsgi-1.0.4.tar.gz をダウンロードしてmakeでビルドしました。http://projects.unbit.it/uwsgi/wiki/Install にも書いてあるように、実際に走るのはPythonスクリプトuwsgiconfig.pyです。
uWSGIのバイナリが出来上がった後で、なにげに easy_install uwsgi としてみてビックリ。ダウンロードとmakeとかは実は不要でして、easy_install uwsgi 一発で全部やってくれるのでした。pip install uwsgi でも同じく完全にインストールしてくれます。コンパイルの後に、次のようなメッセージが出ました。
*** uWSGI is ready, launch it with /home/caty/python2.6.4/bin/uwsgi ***
zip_safe flag not set; analyzing archive contents...
Adding uWSGI 1.0.4 to easy-install.pth fileInstalled /home/caty/python2.6.4/lib/python2.6/site-packages/uWSGI-1.0.4-py2.6.egg
Processing dependencies for uwsgi
Finished processing dependencies for uwsgi$
とりあえず動かしてみる
uWSGIは、簡易なWebサーバー機能を自分で持っているので、WSGIアプリケーションが動くかどうかテストするだけなら、NginxのようなWebサ−バーは不要です。例えば、次のようにします。
$ uwsgi --http :9090 --wsgi-file wsgicaty.py --callable main_app
*** Starting uWSGI 1.0.4 (CGI mode) (32bit) on [Fri Mar 9 23:57:27 2012] ***
compiled with version: 3.4.6 20060404 (Red Hat 3.4.6-10) on 09 March 2012 23:37:44
current working directory: /home/caty/dev
detected binary path: /home/caty/python2.6.4/bin/uwsgi
*** WARNING: you are running uWSGI without its master process manager ***
your memory page size is 4096 bytes
spawned uWSGI http 1 (pid: 11631)
HTTP router/proxy bound on :9090
uwsgi socket 0 bound to TCP address 127.0.0.1:44441 (port auto-assigned) fd 3
Python version: 2.6.4 (r264:75706, Feb 12 2010, 01:46:29) [GCC 3.4.6 20060404 (Red Hat 3.4.6-10)]
Python main interpreter initialized at 0x8ade6b0
your server socket listen backlog is limited to 0 connections
*** Operational MODE: single process ***
...(省略)...
WSGI application 0 (mountpoint='') ready on interpreter 0x9937688 pid: 11655 (default app)
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI worker 1 (and the only) (pid: 11655, cores: 1)
オプションの意味は次のとおり。
- --http :9090 : 組み込みのWebサーバーでポート9090をリッスンします。
- --wsgi-file wsgicaty.py : WSGIアプリケーションのファイルとしてwsgicaty.pyを読み込みます。
- --callable main_app : WSGIのコールバック関数の名前です(デフォルトはapplication)。
この例ではCatyを動かしてますが、http://projects.unbit.it/uwsgi/wiki/Quickstart にある次の短いPythonプログラム(WSGIアプリケーション)で試すことができます。
# hello.py def application(env, start_response): start_response('200 OK', [('Content-Type','text/html')]) return "Hello World"
これを動かすには --wsgi-file hello.py とします(--callableは不要です)。
ブラウザでポート9090にアクセスするか、別なターミナル(コンソールウィンドウ)でcurlやwgetを使って動作を確認できます。起動コマンドラインに & を付けてバックグランドにしておけば別なターミナルを開く必要はありません。が、僕はkillでuwsgiプロセスを止めるとき、間違って別なuwsgiプロセスを殺してしまったり、実験用に起動したプロセスを放置してしまったりしたので、とりあえず動かすだけならフォアグラウンド起動してキーボード割り込みで止めたほうが間違いが少ないと思います。
Pythonインタプリタのプロセス数を --processes 4 のように指定してマルチプロセス処理もできるはずです(http://projects.unbit.it/uwsgi/wiki/HTTPserver)が、簡易Webサーバーではうまくいきませんでした(僕の見落としがあるかもしれません)。
ソレナリに動かしてみる
今度は、uWSGIをNginxと組み合わせてソレナリに動かしてみます。まず、Nginxの設定ファイル(nginx.conf)のserverブロックを次のように書きます。
# # caty.caty-sites.net # server { listen 8888; server_name caty.caty-sites.net; access_log logs/caty.caty-sites.net.access.log main; location / { include uwsgi_params; uwsgi_pass 127.0.0.1:3031; } }
実験に使うURLは、http://caty.caty-sites.net:8888 に決めました。このサイトのルート(/)の指定が location / のところです。uwsgi_pass 127.0.0.1:3031 の意味は、ローカルホスト(127.0.0.1)のポート3031で待機しているuWSGIプロセスにリクエストをパスするという指定です。
本番稼働用ではなくて実験用なのでソレナリの設定ですが、次のようなシェルスクリプトでuWSGIを起動することにしました。
#!/bin/sh uwsgi --master --grunt --close-on-exec \ --socket 127.0.0.1:3031 \ --processes 2 \ --pidfile /home/caty/nginx/logs/uwsgi.pid \ --wsgi-file wsgicaty.py --callable main_app \ --daemonize /home/caty/nginx/logs/uwsgi.log
起動オプションの意味は次のとおりです。
- --master : マスタープロセスとして起動します。いまいち意味がわからないのですが、これを指定しないと *** WARNING: you are running uWSGI without its master process manager *** という警告が出ます。
- --grunt : PythonからuWSGIを制御する時に必要らしいです。Kuwataさんが使ってますが(http://return0.info/note/2012-02.html#id2012-02-15)、僕はよくわかりません。
- --close-on-exec : これもKuwataさんが指定したもの。uWSGIソケットにclose-on-execフラグをセットするらしく、付けたほうが無難なようです。
- --processes : Pythonインタプリタのプロセス数を指定します。(ここで指定した数 + 1)個のプロセスが稼動します。
- --pidfile : マスタープロセスのPIDを書き込むファイルを指定します。Nginxのlogsディレクトリを拝借。
- --daemonize : uWSGIをデーモンとして起動します。指定されたファイルにstdout/stderrを書き込みます。
uWSGIには山のようなオプションがあります →http://projects.unbit.it/uwsgi/wiki/Doc 。意味がわからないものや期待したようには動いてくれないものもありますが、試してみると役立つオプションが見つかるかもしれません。
プロセスの制御など
uWSGIをフォアグラウンドで起動した場合はキーボード割り込み(通常はCtrl-C)で止められますが、nohupコマンドや--daemonizeオプションで起動したときはシグナルを送る必要があります。INTまたはQUITシグナルでuWSGIが停止します。
$ kill -QUIT `cat /home/caty/nginx/logs/uwsgi.pid`
停止するときPIDファイルを消してくれないようです。psやpgrepでプロセス状況は確認したほうがいいですね。使用できるシグナルについては http://projects.unbit.it/uwsgi/wiki/uWSGISignals に書いてあります。
USR1シグナルとUSR2シグナルはそれぞれ、統計情報とワーカー状態を報告するので稼働状況を知るのに便利です。バックグランドで走っているときはレポート出力もログファイルに入るので情報を切り出す工夫は必要です。
--daemonizeオプションは便利ですが、もっとちゃんと制御したいなら supervisord(http://supervisord.org/) や daemontools(http://cr.yp.to/daemontools.html)を使うとよい、とのこと。OSの起動時にWebサーバーやuWSGIを起動するように設定することもあるでしょう。
uWSGIはシステム管理に便利な機能も備えています。UDPによるロギング(http://projects.unbit.it/uwsgi/wiki/UdpLogging)、SNMPサーバー機能(http://projects.unbit.it/uwsgi/wiki/UseSnmp)、指定ソケットに対してリアルタイムの状態報告(http://projects.unbit.it/uwsgi/wiki/StatsServer)など。
uWSGIは、複雑で大規模なサービスにも耐えられるように設計実装されていて、他にも機能が盛りだくさんです。とても一度には使い切れない感じですし、無理に使う必要もありませんが、何か使えそうな機能を探し出すのも楽しいかもしれません。