ちょっと話題の記事

Apacheによるバーチャルホスト構築レシピ mod_mruby編

2013.12.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ども、大瀧です。前回に続き、Apache httpd(以下Apache)によるバーチャルホストの構築例として、mod_mruby編をお送りします。
Apacheの再起動が不要なバーチャルホストの構築方法として、前回はmod_vhost_aliasモジュールの活用とmod_luaモジュールでLuaスクリプトによるApacheの動作のカスタマイズをご紹介しました。今回は、mod_luaモジュールと同様のアプローチで軽量版Ruby実装のmrubyを実行する、mod_mrubyモジュールでApacheの動作をカスタマイズ、バーチャルホスト構成として動かしてみようと思います。

[2014/03/04更新]
本ブログで執筆当時未実装と記した機能は実装済みと、作者のMATSUMOTO Ryosukeさんのツイートでありました。以下の説明はちょっと情報が古いことをご承知置きください。

mod_mrubyモジュールとは

mod_mrubyモジュールはMATSUMOTO Ryosukeさんが開発する、Webサーバー拡張としてmrubyを実行するソフトウェアです。Apahce httpd版のほか、Nginx版のngx_mruby もあるようです *1

mruby自体が新しい実装なのでまだ開発途上のものですが、ChefやCapistranoなどでRubyを触ったことのある方であれば、Luaよりも馴染みやすいと思います。また、キャッシュ機構やチューニングが固まっていけばLua/mod_luaモジュールを凌ぐパフォーマンスを発揮する仕組みになるかもしれません。ただ、パフォーマンスを求める要件であればApache自体を捨てNginxを選択することが根本的なパフォーマンスアップに繋がると思うので、Apache+mod_mruby/mod_luaの構成は"従来のWebサーバーの構成+αで手軽にApacheの挙動を拡張する手段"というようなニーズにマッチする感じがします。

mod_mrubyのインストール

動作確認した環境は、前回のエントリーのmod_luaのものと同様です。

  • OS : Fedora 20 64bit
  • Apache : 2.4.7 (ソースからインストール)

公式WebのHow to useの手順に沿ってインストールしましたが、いくつか依存するコンポーネントがあるので、先にインストールしておきます。

$ sudo yum install -y ruby bison hiredis-devel

あとは、ApacheエクステンションをビルドするapxsコマンドがあればOKです。今回はAuto Buildというmod_mrubyに同梱されるシェルスクリプトでまとめてビルドしました。

$ git clone git://github.com/matsumoto-r/mod_mruby.git
$ cd mod_mruby
$ sh build.sh
   : 
$ sudo make install
/usr/local/apache2/bin/apxs -i -a -n 'mruby' src/.libs/mod_mruby.so
/usr/local/apache2/build/instdso.sh SH_LIBTOOL='/usr/lib64/apr-1/build/libtool' src/.libs/mod_mruby.so /usr/local/apache2/modules
/usr/lib64/apr-1/build/libtool --mode=install install src/.libs/mod_mruby.so /usr/local/apache2/modules/
libtool: install: install src/.libs/mod_mruby.so /usr/local/apache2/modules/mod_mruby.so
Warning!  dlname not found in /usr/local/apache2/modules/mod_mruby.so.
Assuming installing a .so rather than a libtool archive.
chmod 755 /usr/local/apache2/modules/mod_mruby.so
[activating module `mruby' in /usr/local/apache2/conf/httpd.conf]
$

最後の出力にあるように、mod_mrubyモジュールのロードはmake installの処理の中で、/usr/local/apache2/conf/httpd.confファイルの既存のLoadModuleディレクティブの末尾に、自動で追加されます。

$ grep -C 5 mod_mruby /usr/local/apache2/conf/httpd.conf
#LoadModule actions_module modules/mod_actions.so
#LoadModule speling_module modules/mod_speling.so
#LoadModule userdir_module modules/mod_userdir.so
LoadModule alias_module modules/mod_alias.so
#LoadModule rewrite_module modules/mod_rewrite.so
LoadModule mruby_module       modules/mod_mruby.so

<IfModule unixd_module>
#
# If you wish httpd to run as a different user or group, you must run
# httpd as root initially and it will switch.

Apacheの設定

基本的な設定は、mod_luaとほとんど変わりません。httpd.confないしconf/*.confファイル内にmruby*ディレクティブを追加してフックするタイミングと対応するmrubyスクリプトを定義、mrubyスクリプトにカスタマイズするコードを記述します。また、mod_luaではハンドラ関数を定義していましたが、mod_mrubyではmrubyスクリプトがそのまま実行されます。

/usr/local/apache2/conf/httpd.comf (末尾を編集)

#Include conf/extra/httpd-lua.conf
Include conf/extra/httpd-mruby.conf

/usr/local/apache2/conf/extra/httpd-mruby.conf

mrubyTranslateNameMiddle  /usr/local/apache2/conf/mruby/vhost.rb
mrubyLogTransactionMiddle /usr/local/apache2/conf/mruby/vhost-log.rb
<Directory "/usr/local/apache2/vhosts/*/html">
    Require all granted
</Directory>

ディレクティブにはそれぞれのフックするタイミングごとに*First/*Middle/*Lastが用意されており、mod_luaよりもフックするタイミングを細かく制御できるようです。複数のmrubyスクリプトでフックするときに利用しそうな感じがします。今回はタイミングには特にこだわらないので、*Middleを使いました。詳細は、GitHub Pagesにあります。

mrubyスクリプトの記述例(バーチャルホストの実装)

mod_luaと同等の処理を目指しました。以下の設定でバーチャルホストを実装してみます。

  • ドキュメントルート : /usr/local/apache2/vhosts/<ドメイン名>/html/
  • ログディレクトリ : /usr/local/apache2/vhosts/<ドメイン名>/logs/

mod_mrubyでは、Apacheクラスのクラスメソッドでリクエスト情報の参照やApacheの挙動を制御します。

/usr/local/apache2/conf/mruby/vhost.rb

# coding: utf-8

r = Apache::Request.new

if ["example.com", "example.jp"].include?(r.hostname) then
    r.filename = "/usr/local/apache2/vhosts/" + r.hostname + "/html/" + r.uri
    Apache::return(Apache::OK)
else
    Apache::return(Apache::DECLINED)
end

mod_luaにあったset_document_rootメソッドがmod_mrubyには無いようなので、リクエストのパス名(r.uri)とバーチャルホストのドキュメントルートを結合、レスポンスとなるファイル名を生成、Apache::OKで処理を終了しています。

/usr/local/apache2/conf/mruby/vhost-log.rb

# coding: utf-8

r = Apache::Request.new
c = Apache::Connection.new

if ["example.com", "example.jp"].include?(r.hostname) then
    if r.user == "null" then
        r.user = "-"
    end
    if r.headers_in['Referer'].nil? then
        r.headers_in['Referer'] = "-"
    end
    open("/usr/local/apache2/vhosts/" + r.hostname + "/logs/access_log", "a") do | f |
        f.write(
            c.remote_ip + " - " +
            r.user +
            " [" + Time.now.to_s + "] \"" +
            r.the_request + "\" " +
            r.status.to_s + " " +
            r.headers_out.all.size.to_s + " \"" +
            r.headers_in['Referer'].to_s + "\" \"" +
            r.headers_in['User-Agent'].to_s + "\"" + "\n"
        )
    end
    Apache::return(Apache::OK)
else
    Apache::return(Apache::DECLINED)
end

Client IP、Referer、User-Agentの取り方をどなたか知っていたら教えてください!作者のMATSUMOTO Ryosukeさんからコメントで教えていただきました!あと、Time.strftimeがmrubyだとundefined methodになってしまいました。思わぬところで、CRubyとmrubyの違いを実感できました(苦笑)。エラーログの取り方もmod_lua同様見つけられず。エラーログはあまりフックするニーズがないのでしょうか...。

あとは、apachectl restartでApacheを再起動すればOKです。アクセスログは以下のような感じで出ます。

$ cat /usr/local/apache2/vhosts/example.com/logs/access_log
10.0.2.2 - - [Wed Jan 01 04:13:10 2014] "GET / HTTP/1.1" 304 7 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.71 Safari/537.36"
10.0.2.2 - - [Wed Jan 01 04:13:10 2014] "GET /favicon.ico HTTP/1.1" 404 4 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.71 Safari/537.36"

mod_luaモジュールとの比較

前回ご紹介したmod_luaモジュールとの"手触り感"で比べると、スクリプトを書く感触はRubyを触ったことがあるのでmod_mrubyの方が良いですね。ただ、手間を省くためAuto Buildを用いてインストールしたためか、mod_mrubyをビルドするために必要なコンポーネント(CRubyやbisonなど)が多かったのはちょっと残念です。mod_luaモジュールであれば、既にApacheのソースにバンドルされており最低限のソフトウェアで済み、かつApache標準のインストール手順を踏むことができるので、上述した"従来のWebサーバーの構成+αで手軽にApcheの挙動を拡張する手段"というニーズを満たせると思います。もちろん、mod_mrubyモジュールの動作要件は今後の開発が進むにつれ変わっていくところだと思うので、現時点での話です。

あと、mod_mrubyではエラーログにエラーが発生したスクリプトの行を出してくれないのが、地味にしんどいです...。

まとめ

mod_luaモジュールと同等の機能をmod_mrubyで実装してみました。Rubyの文法でApache拡張がガシガシ書けるのはなかなか感動モノなので、今後に期待します!

脚注

  1. 検証したいものが次から次へとあるのは幸せですね(しろめ)