ngx_mrubyの紹介 ならびに nginx+mruby+Redisによる動的なリバースプロキシの実装案

ようやくngx_mrubyでもmod_mrubyのように動的なリバースプロキシ設定ができるようになりました。タイトルは完全に@hibomaさんによる「lua-nginx-module の紹介 ならびに Nginx+Lua+Redisによる動的なリバースプロキシの実装案」をパク … inspireしたものになっています。

今回の工夫点としては、

  1. ngxin内部の変数をうまく使う
  2. redisとのセッションはnginx起動時に一度だけ行なって、そのオブジェクトを使いまわす
  3. proxy_passを使って汎用的な設定にする

の3点です。

1に関しては、前回の「ngx_mrubyでnginxの内部変数を操作する」で紹介しました。

2に関しては、リクエストのたびにredisにつなぎにいっていてはコストが大きいので、nginxの起動時にRedisに接続しておき、その時のコンテキストオブジェクトをuserdataに保存しておいて、リバースプロキシする際に再度userdataから取り出すようにしました。そのために、「mrb_stateを共有しているRubyコード間でuserdataを読み書きできるmruby-userdata作った」においてmruby-userdataというmrbgemを作りました。これで、mrb_stateを共有している複数にRubyコードから安全にオブジェクトを保存したり取り出したりできます。

3に関しては、nginxのconfの設定との兼ね合いから、proxy_passに渡すurlをnginxの変数として、それをmrubyコードから決定するようにしました。

lua-nginx-module の紹介 ならびに Nginx+Lua+Redisによる動的なリバースプロキシの実装案」で紹介されているような内容に近い事がRubyでもできるようになってきました。

IIJさんのMOGOKのスライドでも下記のように言及して頂いていたnginxとmrubyによるリバースプロキシ機能をようやくお見せする事ができます。

では、これらを実現するnginx.confを見てみましょう。

ngx_mrubyによるリバースプロキシ設定

confは以下のようになります。要らない所は省略しています。

[program lang=’ruby’ escaped=’true’]

(snip)

http {

(snip)

    mruby_init_code '

        userdata = Userdata.new "redis_data_key"
        userdata.redis = Redis.new "192.168.12.251", 6379

    ';

(snip)

    server {

        location /proxy {

          mruby_set_code $backend '

              backends = [
                "test1",
                "test2",
                "test3",
              ]

              userdata = Userdata.new "redis_data_key"
              redis = userdata.redis
              redis.get backends[rand(backends.length)]

          ';

          proxy_pass   http://$backend;
        }
    }

(snip)

}

[/program]

このように、起動時に実行されるmruby_init_codeで指定のredisサーバと接続を完了しておき、userdataにそのコンテキストオブジェクトを保存しておきます。

そして、リクエスト毎に実行されるmruby_set_codeによって、その第二引数のRubyコードの実行結果が第一引数のnginx変数、上記の例では$backendに格納されます。内容は、適当にランダムにホストキーを選んで、そのキーに対応したホストをRedisから取ってくるような処理です。

そして、最終的にproxy_passによってリバースプロキシされます。

とても簡単ですね。

Rubyをファイルに書く場合

上記のようにコードをnginx.confに書くと、起動時にコンパイルまで行うので処理は高速ですが、場合によってはRubyコードをファイルに書いておいて、nginx起動後でもファイルのコードを変更したら、即座にその変更が反映されるような利便性を優先する状況もあるでしょう。その場合は、上記のコードをそれぞれファイルに書いておいて、以下のようなnginx.confにしておきます。

[program lang=’ruby’ escaped=’true’]

(snip)

http {

(snip)

    mruby_init "/path/to/connect_redis.rb";

(snip)

    server {

        location /proxy {

          mruby_set $backend "/path/to/proxy.rb";
          proxy_pass   http://$backend;

        }
    }

(snip)

}

[/program]

そうすることで、このようにとても綺麗にconfを書くことができます。ただし、都度コードをコンパイルするので、その分パフォーマンスは低下します。

最後に

遅くなりましたが、ようやくngx_mrubyでもそれなりにリバースプロキシを書けるようになりました。

数あるmrbgemと組み合わせる事で、その他様々な動的処理が可能になるのではないかと思います。「ngx_mrubyでnginxの内部変数を操作する」あたりを考慮しながらうまくnginx.confを書けば、もっと柔軟な振る舞いを設定できるようになると思います。

nginxを使っている人はぜひぜひ試してみて下さい!

そろそろ、次は同時接続数やそういう運用系の制限をngx_mrubyでうまく記述する方法を吟味していこうと考えています。