at posts/single.html

tDiary を ruby1.9 + nginx + fcgi で動かしてみた

tDiary + ruby1.9 + fcgi で動かそうとして挫折した記録 - HsbtDiaryより。 そういえばうちのサーバも VPS なので、 FastCGI を使えるんだった。

環境

  • CentOS 5.2
  • nginx 0.6.39
  • ruby 1.9.1-p378
  • tdiary 2.3.3.20100326

Nginx のインストール

NginxはWebサーバの一種。Apacheより軽いらしい。 EPELに用意されているCentOS用のRPMを使ってインストール。

$ wget http://download.fedora.redhat.com/pub/epel/5/i386/epel-release-5-3.noarch.rpm
$ sudo rpm -Uvh epel-release-5-3.noarch.rpm
$ sudo yum install nginx

設定ファイルは /etc/nginx/nginx.conf にインストールされる。 起動と停止は init.d で。

sudo /etc/init.d/nginx start
sudo /etc/init.d/nginx stop

spawn-fcgi のインストール

公式ドキュメントを読んでFastCGIの使い方を理解。 Nginx は FastCGI 経由でプロセスを呼び出すことはできるけど、プロセスを起動することはできないらしい。 そのため、 FastCGI で呼び出されるプロセスは、事前に起動しておく必要がある。 このプロセス起動に使うコマンドが spawn-fcgi。

sudo yum install spawn-fcgi

/usr/bin/spawn-fcgi にインストールされる。使い方は以下の通り。

Usage: spawn-fcgi [options] -- <fcgiapp> [fcgi app arguments]

spawn-fcgi v1.4.22 - spawns fastcgi processes

Options:
 -f <fcgiapp> filename of the fcgi-application
 -a <addr>    bind to ip address
 -p <port>    bind to tcp-port
 -s <path>    bind to unix-domain socket
 -C <childs>  (PHP only) numbers of childs to spawn (default 5)
 -F <childs>  numbers of childs to fork (default 1)
 -P <path>    name of PID-file for spawed process
 -n           no fork (for daemontools)
 -v           show version
 -h           show this help
(root only)
 -c <dir>     chroot to directory
 -u <user>    change to user-id
 -g <group>   change to group-id

通信にポートを使うなら、-p コマンドでポート番号を指定する。UNIXソケットなら -s 。

spawn-fcgi -p <ポート番号> -F <起動プロセス数> -f <コマンド名>

Ruby 側の環境を整える

Ruby側にもFastCGI用のモジュールが必要。 ここでは、すでに ruby 1.9.1 を /usr/local/bin にインストール済みで、PATHにも通っているという前提。

sudo yum install fcgi-devel
sudo gem install fcgi

これで require 'fcgi' が可能になる。

Pure Ruby のモジュールもある模様。どっちを使うのがいいのかは分からない。

sudo /usr/local/bin/gem install ruby-fcgi

Hello World

Ubuntu+Apache+Rack+FastCGIな環境でRubyを動かしてみるに載っていたサンプルをお借りして動作テスト。

#!/usr/bin/env ruby
require 'rack'
app = Proc.new do |env|
  Rack::Response.new.finish do |res|
    res.write "Hello, Rack!"
  end
end
Rack::Handler::FastCGI.run app

spawn-cgiを使って起動してみると、子プロセスがエラーコード127を返して終了してしまった。

spawn-fcgi -p 10003 -f hello.fcgi 
spawn-fcgi.c.230: child exited with: 127

ファイル名だけで、パスを指定していなかったのが原因。 UNIXのコマンドと同じく、パスを明記しないといけない。内部で exec しているのかな。

spawn-fcgi -p 10003 -f ~/work/fcgi/hello.fcgi
spawn-fcgi.c.208: child spawned successfully: PID: 17931

起動に成功すると、プロセスが起動しているのが分かる。 プロセスの終了には kill コマンドを使うしかないのかな。

$ ps -ef | grep 17931
machu    17931     1  0 04:03 ?        00:00:00 ruby /home/machu/work/fcgi/hello.fcgi
$ kill -TERM 17931

-Fオプションを指定すれば、複数のプロセスが起動する。

$ spawn-fcgi -p 10003 -F3 -f ~/work/fcgi/hello.fcgi
spawn-fcgi.c.208: child spawned successfully: PID: 18061
spawn-fcgi.c.208: child spawned successfully: PID: 18065
spawn-fcgi.c.208: child spawned successfully: PID: 18066

Nginx側の設定

とりあえず最低限の設定をする。.fcgiのファイルが呼ばれた場合に、10003ポートで待ち受けているFastCGIプロセスと通信するように設定している。 実際に使うにはもう少し設定が必要。(.html -> ?date=$1への変換など)

    server {
        listen       8080;
        server_name  _;
        root   /var/www/www.machu.jp/html;

        location /diary/ {
            index  index.fcgi;
        }

        location ~ \.fcgi$ {
            fastcgi_index  index.fcgi;
            fastcgi_pass   127.0.0.1:10003;
            include        fastcgi_params;
        }

このとき、FastCGIプロセス側へCGIの環境変数を渡してあげないといけない。 渡す値は /etc/nginx/fastcgi_params に記述されているけど、1つだけ足りなかったので追記した。

--- /etc/nginx/fastcgi_params.default   2010-02-16 14:35:27.000000000 +0900
+++ /etc/nginx/fastcgi_params   2010-04-30 02:51:15.000000000 +0900
@@ -5,6 +5,7 @@
 fastcgi_param  CONTENT_LENGTH     $content_length;
 
 fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
+fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
 fastcgi_param  REQUEST_URI        $request_uri;
 fastcgi_param  DOCUMENT_URI       $document_uri;
 fastcgi_param  DOCUMENT_ROOT      $document_root;

SCRIPT_FILENAMEはtDiaryのindex.fcgiで使われているので、適切に渡してあげないと tDiary 側でエラーになる。

dir = File::dirname( cgi.env_table["SCRIPT_FILENAME"] )

この状態でHello Worldが表示されることを確認しておく。 大丈夫だったら、代わりにtDiaryのindex.fcgiを起動する。-F や -p オプションの値はお好みで。

spawn-fcgi -F4 -p 10003 -f /var/www/www.machu.jp/html/diary/index.fcgi

これで Ruby 1.9 + FastCGI + Ningx で tDiary が動いた。

速度比較

abを使って比較してみた。

Apache + CGI
Requests per second:    0.81 [#/sec] (mean)
Time per request:       1239.777 [ms] (mean)

 Connection Times (ms)
               min  mean[+/-sd] median   max
Connect:       16   16   0.4     16      17
Processing:   916 1224 625.1    974    2339
Waiting:      606  911 618.0    665    2014
Total:        932 1240 625.5    989    2356
Nginx + FastCGI
Requests per second:    1.18 [#/sec] (mean)
Time per request:       849.381 [ms] (mean)

 Connection Times (ms)
               min  mean[+/-sd] median   max
Connect:       16   24  15.7     18      52
Processing:   733  825 112.0    819    1008
Waiting:      407  455  54.1    450     546
Total:        749  849 108.7    836    1028

3割くらい速くなった。

課題

日記の表示画面はFastCGI化できた。あとは更新画面 (update.rb) をどうするか。 自分しか使わないからFastCGIを使わずに通常のCGIでいいんだけど、Nginxは直接CGIを起動できないという制約がある(FastCGI経由で起動することは可能)。

所感

速度面では FastCGI のほうが有利。 ただし、NginxでのFastCGI はプロセスの起動を自分で管理しないといけない。 Nginx+FastCGIを使う場合は、

  • プロセスが常駐するため、メモリを消費する
  • プロセスが異常終了した場合に、自動で再起動する仕組みが必要

あたりがデメリットか。

Apache付属のmod_fastcgiやfcgidはプロセスを自動生成してくれる…のかな? Railsが流行りだした頃にApacheとFastCGIの組み合わせは推奨されないと言われていたけど、今ではそうでもないんだろうか。

いずれにしても、こうやって別プロセスで管理するのなら、FastCGIを使うよりもリバースプロキシ + HTTPサーバ (Thin, Mongrel) + Rackを使った方がデバッグが楽だろうなぁ。

関連する日記