Apache 2.4.1のmod_luaでApacheに介入する(mod_rewriteの終焉?)

といいつつも、そこまで大したことはしていない。

luaという高速に動作する組み込み系のスクリプト言語で遊んでみたかったのと、それだったmod_luaで遊んでみればいいなと思っただけである。で、実際にmod_luaをコンパイルして遊んでみた。コンパイルオプションは以下。

./configure --prefix=/usr/local/apache2.4 --with-apr=/usr/local/apr --with-apr-util=/usr/local/apr --enable-modules=all --enable-mods-shared=all --enable-mpms-shared='prefork worker event' --enable-lua --enable-sed

後ろの方に–enable-sedとかあるが気にしない。

で、実際にmod_luaで遊んでみたところ、最初の直感としては以下のように感じた。

  • mod_luaはmod_rewriteにとってかわりそう
  • mod_luaの本質はCGIのように動的コンテンツとして扱う所にあらず
    • 動的コンテンツ内でrequest_recを弄れるのはなかなか楽しいが
  • 本質はApacheの設定からフック関数として簡単にluaのfunctionを登録できるところ
    • rewriteでやれるような処理は全部できそう
    • rewriteと比べてもやっぱり可読性が高い
    • モジュールでやるようなある程度複雑な処理も簡単に書ける
      • 基本はスクリプトとconfに書くだけなのでテストも簡単
      • rewritemapのprgで外部スクリプトをforkで処理させるよりかなり軽い
        • luaの実行はApacheのサーバプロセスが直接実行(parseを開始する)するのでforkのコストが無い
      • luaのスクリプト自体はサーバプロセスを再起動する必要なく修正が可能

だろうか。

追記:2012/03/28 ちょっとした注意点です
https://twitter.com/#!/matsumotory/status/184599479364759553

細かいmod_luaの設定等は公式のドキュメントをみてもらって、どういうことだできるか簡単なサンプルを紹介する。

mod_rewriteと似たような事

rewriteと言えば、アクセスのあったURIに対して違うURIやファイル名を紐づけるために使われる事が多いと思う。では、そういうことをmod_luaでやるとどうなるか。今回はmapper.luaというスクリプトで二つのfunctionを定義した。今回は、こんなに簡単にできるん、というのを強調したかったのでシンプルで簡単なものにした。

  • uri2file()

これは、アクセスのあったURIがphpだった場合に、アクセスのあったホスト名を含むディレクトリの中のphpに書き換える処理。複数のドメインを扱っている場合に簡単にアアクセス先を動的に振り分けられる。簡易mod_vhost_aliasの条件付きみたいなfunction

  • golden_balancer

なんとゴールデンバランサーといういかにもすごそうなfunctionである。名前が非常にかっこよくて、とまどうかもしれない。これは、言わばWebアクセスのゴールデンタイムである19時から24時までの間は、50%の確率で別のサーバにアクセスを振り分けるという、完全に名前負けしているfunctionだ。ただ、こういうのを実装したいなと思った時も、これまではApacheモジュールの複雑な実装をしてコンパイルして・・・と面倒だったが、これも非常に簡単にできてしまう。

追記:2012/03/28
このgolden_balancerをもう少し真面目に実装した機能「mod_lua でリバースプロクシ:どさにっき」が紹介されている。mod_luaシンプルで分かりやすい。

では、以下ソース。

require "apache2"

function uri2file(r)
    if string.match(r.uri, ".*\.php") then
        r.filename = "/usr/local/apache2.4/htdocs/".. r.hostname .."/".. r.uri
    end
    return apache2.DECLINED
end

function golden_balancer(r)
    ctime = os.date("%H")
    if ctime > 19 then
        if math.random() < 0.5 then
            r.headers_out["Location"] = "http://server2.example.com/".. r.uri
            return apache2.HTTP_MOVED_TEMPORARILY
        end
    else
        return apache2.DECLINED
    end
end

え!?、というくらい簡単なソースになっている。今回はコードの保守性などは全く考慮していないので、ばんばん文字列を直書きしているがそこは気にしないでほしい。

そして、これをApacheのコールバック関数として登録するためには、以下のようにconfを書くだけである。

Loadmodule lua_module modules/mod_lua.so
LuaHookTranslateName /usr/local/apache2.4/conf/extra/lua/mapper.lua uri2file
LuaHookTranslateName /usr/local/apache2.4/conf/extra/lua/mapper.lua golden_balancer

たったの2行。コールバック関数に登録するのに、ApacheモジュールにC言語で書くとそれだけでも面倒な作業になるのだが、こっちはシンプルで非常に直感的だ。ちなみに、Apacheモジュールだとこんな風に書く。

mod_process_securityを参考に)

static const command_rec process_security_cmds[] = {

    AP_INIT_FLAG("PSExAll", set_all_ext, NULL, ACCESS_CONF | RSRC_CONF, "Set Enable All Extensions On / Off. (default Off)"),
    AP_INIT_TAKE1("PSMode", set_mode, NULL, RSRC_CONF | ACCESS_CONF, "stat only. you can custmize this code."),
    AP_INIT_TAKE2("PSMinUidGid", set_minuidgid, NULL, RSRC_CONF, "Minimal uid and gid."),
    AP_INIT_TAKE2("PSDefaultUidGid", set_defuidgid, NULL, RSRC_CONF, "Default uid and gid."),
    AP_INIT_ITERATE("PSExtensions", set_extensions, NULL, ACCESS_CONF | RSRC_CONF, "Set Enable Extensions."),
    {NULL}
};

static void register_hooks(apr_pool_t *p)
{
    ap_hook_post_config(process_security_init, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_handler(process_security_handler, NULL, NULL, APR_HOOK_REALLY_FIRST);
}

module AP_MODULE_DECLARE_DATA process_security_module = {
    STANDARD20_MODULE_STUFF,
    create_dir_config,         /* dir config creater */
    NULL,                      /* dir merger */
    create_config,             /* server config */
    NULL,                      /* merge server config */
    process_security_cmds,     /* command apr_table_t */
    register_hooks             /* register hooks */
};

まぁ、なんか複雑そうで大変そうだ。それに対して、luaはすっきりしていてある程度スクリプトが書ける人なら簡単にできてしまうように思う。では、次。

ロードアベレージの値で処理するか判断する

こういった処理は本来モジュールで実装するもんだと思っている。現にmod_lalimitというモジュールを作ったことがあるが、mod_luaを使うといとも簡単にできてしまった。まずは例のごとくfunctionの説明をしておく。

  • load_checker()

読んで字のごとく、ロードアベレージのチェックを行う。phpにアクセスがあった場合、ロードアベレージが設定して閾値を超えていた場合、phpは実行するのだが出力結果の先頭に「Sorry. loadavg high!」と表示する。この出力をみて、アクセスした人は負荷を考慮してアクセスしなくなる・・・という夢のような仕組みである。というのは、冗談で、その場合は503を返して処理を中断する実装をコメントで入れているので、本来はそうした方が賢いと思われる。同時に、それらの情報をerror_logに出力するようにしておいた。

では、以下ソース。

require "apache2"

load_limit = 2

function get_load()
    file  = io.open('/proc/loadavg', 'r')
    state = file:read('*a')
    file:close()
    cload = string.match(state, '(%d+\.%d+)%s+.*')
    return cload
end

function load_checker(r)
    if string.match(r.filename, "^.*\.php$") then
        cload = tonumber(get_load())
        if cload > load_limit then
            r:warn(r.filename .. "is CGI. current load(".. cload ..") is higher than load_limit(".. load_limit ..").")
            r:puts("Sorry. loadavg high!")
            --return 503
        else
            r:debug(r.filename .. "is CGI. current load(".. cload ..") is lower than load_limit(".. load_limit ..").")
        end
    end
    return apache2.OK
end

うーん、こんなに簡単にできちゃっていいのだろうか。これも、Apacheのconfに以下のように登録する。

Loadmodule lua_module modules/mod_lua.so

LuaHookTranslateName /usr/local/apache2.4/conf/extra/lua/mapper.lua uri2file
LuaHookTranslateName /usr/local/apache2.4/conf/extra/lua/mapper.lua golden_balancer
LuaHookAccessChecker /usr/local/apache2.4/conf/extra/lua/access_checker.lua load_checker

使ってみた感想

非常にシンプルで使い易いことが分かった。また、モジュールでやりたいと思っていたようなちょっとした処理であれば、mod_luaで簡単かつ軽量に行うのが良いかもしれない。スクリプトの変更にはApacheの再起動も必要無く(rewritemapの場合は必要)、luaのfunction実行にはfork等のコストもかからないため、rewritemapで外部スクリプトを使うよりも非常に効率よく扱うことができる。

mod_rewriteの命もすぐそこまで来ているように思った。これからはmod_luaの時代かもしれない。

「Apache 2.4.1のmod_luaでApacheに介入する(mod_rewriteの終焉?)」への8件のフィードバック

  1. 1点だけご指摘を。

    > mod_rewriteはmod_luaにとってかわりそう

    この言い方だと、mod_rewriteが、mod_luaの変わりとして使える、という意味になり、文の内容と逆の意味になってしまいます。
    この部分は修正した方が良いかと。
    単なる誤字脱字じゃなく、意味そのものがおかしくなるので、ご指摘いたしました。
    このコメントは削除していただいても構いません。

コメントは受け付けていません。