mruby cookbookを使ってngx_mrubyモジュールを導入しよう、ファイルベースアクセス制限のサンプル

先日のエントリ、ChefをつかってmrubyをCookbookから手軽に入れようでOpscode Chef用にmrubyのクックブックを作成したことを報告しました。

さて、作成に及んだ目的は Nginxのモジュール、ngx_mruby を手軽に利用できる環境を作ることでした。

ngx_mrubyについては製作者の松本亮介氏による紹介記事を参照いただくとよくわかります。

ngx_mrubyの紹介 ならびに nginx+mruby+Redisによる動的なリバースプロキシの実装案 | 人間とウェブの未来

仕事がらRuby on RailsPHPアプリケーションのフロントにNginxを置くことが多く、Nginxの処理をRubyのコードで取り回せることはおそらく役に立ちます。

mruby+nginx with ngx_mrubyのインストール

前回のChefをつかってmrubyをCookbookから手軽に入れように追加してnginxのCookbookを入れましょう、mruby cookbookのバージョンが0.3.2以上がngx_mrubyのビルドに対応しています。

chef-soloのnode.json

chef-soloに渡すjsonはこうです、どのクックブックもUbuntu,CentOS共通で利用できるのがいいですね。

node.json

{
  "run_list" : [
    "recipe[build-essential::default]",
    "recipe[mruby::ngx_mruby]",
    "recipe[nginx]"
  ],
  "mruby": {
    "force_rebuild" : true,
    "build_options" : {
       "user_gems" : [
          [":git", "https://github.com/iij/mruby-io.git"],
          [":git", "https://github.com/iij/mruby-socket.git"],
          [":git", "https://github.com/iij/mruby-pack.git"],
          [":git", "https://github.com/iij/mruby-ipaddr.git"]
        ]
    }
  },
  "nginx" : {
    "install_method" : "source",
    "version" : "1.4.2",
    "configure_flags" : [
      "--with-debug"
    ],
    "source" : {
    "modules" : [
      "http_ssl_module",
      "http_geoip_module",
      "http_realip_module",
      "http_stub_status_module",
      "http_gzip_static_module"
    ]
    }
  }
}

これを `chef-solo -j node.json` で一発完了です。

# /opt/nginx-1.4.2/sbin/nginx -V
nginx version: nginx/1.4.2
built by gcc 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) 
TLS SNI support enabled
configure arguments:
--prefix=/opt/nginx-1.4.2
--conf-path=/etc/nginx/nginx.conf
--sbin-path=/opt/nginx-1.4.2/sbin/nginx
--with-debug
--add-module=/opt/chef_mruby/ngx_mruby
--add-module=/opt/chef_mruby/ngx_mruby/dependence/ngx_devel_kit
--with-http_ssl_module
--with-http_geoip_module
--with-ld-opt='-Wl,-R,/usr/local/lib -L /usr/local/lib'
--with-http_realip_module
--with-http_stub_status_module
--with-http_gzip_static_module

ngx_mrubyで.htaccessっぽいアクセス制限をかけてみよう

Nginxを利用していてApache httpdと比べて不便だなと言われることに.htaccessの存在があります。なかでもアクセス制限がファイル編集からオンザフライでできるのはうれしいですね、ngx_mrubyで真似してみましょう。

仕様

いきなり全機能(Orderとか)は苦しいので、非常に簡素ですがこんな仕様で。

  • ドキュメントルートに.ht_ngx_accessファイルをおく
  • 一行に1つIPアドレス
  • リストにマッチするクライアントは遮断(403)
  • マッチしなければ標準のコンテンツを返す(200)

設定ファイルなど

/etc/nginx/sites-availabl/default

nginxのコンフィグです、ここにRubyのコードを仕込むことができます。
とりあえずやってみたという所なので、折角入れたmruby-ipaddr等が使えていませんね。

server {
  listen   80;
  server_name  mruby-nginx;

  access_log  /var/log/nginx/localhost.access.log;
  root   /var/www/nginx-default;
  index  index.html index.htm;

  location / {
    mruby_set_code $deny '
      r = Nginx::Request.new()
      filename = File.join(r.var.document_root, ".ht_ngx_access")
      if File.exists?(filename)
        deny_list = Array.new
        File.open(filename) do |file|
          while line = file.gets
            deny_list << line.chomp
          end
        end
        if deny_list.include?(r.var.remote_addr)
          Nginx.errlogger Nginx::LOG_ERR, "ACL: Client Matched!!"
          "denied"
        else
          Nginx.errlogger Nginx::LOG_ERR, "ACL: Client Unmatched"
          ""
        end
      else
        ""
      end
    ';

    if ($deny) {
      return 403;
    }

  }
}
&#91;/code&#93;

<p><strong>/var/www/nginx-default/.ht<em>ngx</em>access</strong></p>
<p>IPアドレスのリストを書きます、編集すると即反映されます。</p>

[code language="text"]
192.168.2.1
192.168.2.2

/var/www/nginx-default/index.html

正常なアクセス用のコンテンツです。

Hello ngx_mruby!

制限の様子

動作確認のためログレベルNGX_LOG_ERRでログに出力しました、マッチしたクライアントには403、そうでなければ200ですね。

* error_log *

2013/09/25 08:27:52 [error] 22417#0: *4 ACL: Client Matched!!, client: 192.168.2.2, server: mruby-nginx, request: "GET /index.html HTTP/1.1", host: "xxx.xxx.xxx.xxx"
2013/09/25 08:28:14 [error] 22417#0: *5 ACL: Client Unmatched, client: 192.168.2.3, server: mruby-nginx, request: "GET /index.html HTTP/1.1", host: "xxx.xxx.xxx.xxx"

* access_log *

192.168.2.2 - - [25/Sep/2013:08:27:52 +0000] "GET /index.html HTTP/1.1" 403 168 "-" "curl/7.21.3 (x86_64-pc-linux-gnu) libcurl/7.21.3 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18"
192.168.2.3 - - [25/Sep/2013:08:28:14 +0000] "GET /index.html HTTP/1.1" 200 24 "-" "curl/7.21.3 (x86_64-pc-linux-gnu) libcurl/7.21.3 OpenSSL/0.9.8o zlib/1.2.3.4 libidn/1.18"

もちろんファイルを編集すれば対象を変更することができます。

パフォーマンスレポート

mruby_set_codeを解することで処理速度にどのくらい差が出るでしょうか? 軽くapache benchで比較してみました。

mruby_set_codeが無いケース

Server Software:        nginx/1.4.2
Server Hostname:        xxx.xxx.xxx.xxx
Server Port:            80

Document Path:          /index.html
Document Length:        24 bytes

Concurrency Level:      20
Time taken for tests:   1.242 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      254000 bytes
HTML transferred:       24000 bytes
Requests per second:    805.13 [#/sec] (mean)
Time per request:       24.841 [ms] (mean)
Time per request:       1.242 [ms] (mean, across all concurrent requests)
Transfer rate:          199.71 [Kbytes/sec] received

mruby_set_code あり、.ht_ngx_accessファイルなしのケース

Server Software:        nginx/1.4.2
Server Hostname:        xxx.xxx.xxx.xxx
Server Port:            80

Document Path:          /index.html
Document Length:        24 bytes

Concurrency Level:      20
Time taken for tests:   1.246 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      254000 bytes
HTML transferred:       24000 bytes
Requests per second:    802.84 [#/sec] (mean)
Time per request:       24.912 [ms] (mean)
Time per request:       1.246 [ms] (mean, across all concurrent requests)
Transfer rate:          199.14 [Kbytes/sec] received

mruby_set_code あり、.ht_ngx_accessファイルありのケース

Server Software:        nginx/1.4.2
Server Hostname:        xxx.xxx.xxx.xxx
Server Port:            80

Document Path:          /index.html
Document Length:        24 bytes

Concurrency Level:      20
Time taken for tests:   1.254 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      254000 bytes
HTML transferred:       24000 bytes
Requests per second:    797.41 [#/sec] (mean)
Time per request:       25.081 [ms] (mean)
Time per request:       1.254 [ms] (mean, across all concurrent requests)
Transfer rate:          197.79 [Kbytes/sec] received

20並列の1,000リクエスト程度ではほぼ差が見られませんでした、ngx_mrubyありの方は余計にログもだしているんですが結構意外です。これは頼りになる。

感想

ngx_mruby、無限の可能性。

Author Profile

sawanoboly@higanworks

sawanoboly@higanworksについて

HiganWorks LLCの代表、クラウドを利用したインターネットアプリケーションのプラットフォーム構築・運用の自動化をテーマに活動しています。OpsCode承認コントリビュータ等、サービスに関連するオープンソースコミュニティにも参加しています。