share facebook facebook2 twitter menu hatena pocket slack

2014.07.08 TUE

Nginx で構築したリバースプロキシを ngx_mruby で細かい制御をする試み(1)

川原 洋平

WRITTEN BY川原 洋平

Nginx 初心者のかっぱ@inokara)です。


追記(1)

ngx_mruby 作者の @matsumotory さんに以下オンようなコメントを頂きました!

ngx-mruby-nginx-script_01

有難うございます!



追記(2)

連載(笑)にしようと思いますのでタイトルに数字つけました。



はじめに

Nginx は設定に if が使えたりとデブオプスのココロを擽る Web サーバーだと思っていますが、細かい制御をしたいなと思った時に設定ファイルをグリグリ書くのはどうもなあと思っていたら mruby でイジれる ngx_mruby があるではないですか!

しかも、事例が既に載っているではないですか!

ということで、自分も試してみたいと思います。(以下、作業中の内容も含まれますのでご注意ください)



やりたいこと

  1. リバースプロキシへの接続数に応じて処理を変えたい!
    • リバースプロキシへの接続数を超えた接続はエラーページを表示させたい
  2. バックエンドへの接続数を監視したい!
    • 監視の結果は別のツールに飛ばして可視化したい


準備


環境の構築

環境の構築にあたっては ngx_mruby の作者である @matsumotory さんが既に Dockerfile を作って頂いているのでそれを利用させて頂きます。


Docker Hub

リリース依頼、あまり騒がれない感じの Docker Hub を使ってコンテナをビルドします。

ngx-mruby-nginx-script_02

既に ngx_mruby を fork させて頂いた状態からスタートです。

ngx-mruby-nginx-script_03


Docker を動かす器

待つ間にコンテナを動かす器を用意しましょう。

ngx-mruby-nginx-script_04

そして先日発表された EBS の SSD なんか使っちゃいます。


バックエンドの Web サーバーは手動で CentOS で作ってしまいます。

以下のような簡単な Dockerfile を用意して…

FROM centos:6.4
RUN yum -y update
ADD nginx.repo /etc/yum.repos.d/nginx.repo
RUN yum -y install nginx

docker build するだけです。



実践

まずやりたいことの(1)から実装してみたいと思います。


その前に

ビルドが完了したコンテナを pull してきます。

docker pull inokappa/ngx-mruby

pull してきたら run します。

docker run -t -i inokappa/ngx-mruby /bin/bash

ログインしたら以下のようにして nginx を起動しましょう。

/usr/local/nginx/sbin/nginx &

以下のようにして Detach モードで起動すると /usr/local/nginx/sbin/nginx が CMD で実行されます。

docker run -t -d inokappa/ngx-mruby

簡単ですね。


何がやりたいのか?

  • リバースプロキシとして動作している Nginx への接続数に応じて処理を変えたい!
  • 接続数を超えたアクセスに対してエラーページを表示させたい


break down

ドキュメントを見ながらやりたいことをブレイクダウンして見てみたいと思います。


nginx.conf

上記のコンテナから起動した場合には既に nginx.conf は下記のように設定されすぐに ngx_mruby は利用可能な状態になっています。

daemon off;
user daemon;
worker_processes auto;

events {
    worker_connections  1024;
}

env PROXY1_PORT_80_TCP_ADDR;
env PROXY1_PORT_80_TCP_PORT;
env PROXY2_PORT_80_TCP_ADDR;
env PROXY2_PORT_80_TCP_PORT;
env PROXY3_PORT_80_TCP_ADDR;
env PROXY3_PORT_80_TCP_PORT;

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       80;
        server_name  localhost;

        location / {
            root   html;
            index  index.html index.htm;
        }

        location /mruby-hello {
            mruby_content_handler_code 'Nginx.echo "server ip: #{Nginx::Connection.new.local_ip}: hello ngx_mruby world."';
        }

        location /mruby-test {
            mruby_content_handler /usr/local/nginx/hook/test.rb;
        }

        location /mruby-proxy {
            mruby_set $backend /usr/local/nginx/hook/proxy.rb;
            proxy_pass http://$backend;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}


接続数の取得

純粋な Nginx が起動してからの累積接続数を取得する場合には Nginx::Var クラスの connection メソッドを利用します。

v = Nginx::Var.new
Nginx.echo v.connection

/usr/local/nginx/hook/test.rb に以下のように追記します。

Nginx.echo "This is test for ngx_mruby"
v = Nginx::Var.new
Nginx.echo v.connection

追記したら curl でアクセスしてみます。

ngx-mruby-nginx-script_05

初めてのアクセスなので connection は 1 となります。尚、connection 以外には connection_requests というメソッドも利用出来ます。

Nginx.echo v.connection_requests

これは 1 つのクライアントからのリクエスト数をカウントしているようですので、これにしきい値を設けると DDoS 対策のようなことが出来ると思われます。

v = Nginx::Var.new
if v.connection_requests.to_i < 100 then
  Nginx.redirect "/may_be_you_ddos.html"
else
  Nginx.echo v.connection
end


接続数に応じた処理

connection メソッドを以下のように接続数が一定数を超えたら 50x.html ページが表示されるようにするには以下のように書くだけで実現可能です。

v = Nginx::Var.new
if v.connection.to_i < 100 then
  Nginx.redirect "/50x.html"
else
  Nginx.echo v.connection
end

これで累積の接続数が 100 を超えた場合に以下のような 50x.html のページが表示されるようになります。

ngx-mruby-nginx-script_06

ただ、これだと累積のアクセス数が 100 を超えてしまったら全てのクライアントが 50x.html となってしまい残念な感じになってしまいます。。。



仕切り直し(やりたいこと(1))

追記にも書かせて頂きましたが @matsumotory さんにご対応頂いて Nginx の HttpStubStatusModule を ngx_mruby に組み込むようにして頂きましたので仕切り直しで試していきたいと思います。

尚、検証に際しては ngx-mruby のコンテナを利用させて頂きました!


同時接続数を取得する

HttpStubStatusModule を ngx_mruby に組み込んで頂いたおかけで以下のように書くことで同時アクセス数を取得出来るようになりました。

r = Nginx::Request.new
Nginx.echo r.var.send r.var.arg_name.to_sym
Nginx.return Nginx::HTTP_OK

リクエストパラメータの name に connection_waiting を渡してあげると接続待ち数が取得出来ます。また、以下のようなパラメータを渡すことでそれぞれの値を取得することも可能です。

  • connections_active
  • connections_reading
  • connections_writing
  • connections_waiting

以下のように指定します。

http://xxx.xxx.xxx.xxx/path/to?name=connections_active


しきい値を超えた場合の処理を追加

以下のような簡単なスクリプトだけで処理が実装可能です。

r = Nginx::Request.new
w = r.var.send r.var.arg_name.to_sym
if w.to_i < 99 then
  Nginx.redirect "/error.html"
else
  Nginx.echo r.var.send r.var.arg_name.to_sym
end


リバースプロキシの設定


動作確認

JMeter を使って動作確認を行いました。


テスト前

ngx-mruby-nginx-script_07

スレッドは 100 でいきたいと思います!


接続開始直後

ngx-mruby-nginx-script_08

順調に伸びてますな…


しきい値を超えたあたり

おお、しきい値を超えたあたりで以下のようにエラーページが表示されました。

ngx-mruby-nginx-script_09

すばらしひ。

これで要件を満たせそうです。



ひとまずまとめ

やりたいこと(2) がまだ出来ていませんが…


今回やったこと

  • ngx_mruby と Nginx を利用してコネクション数等を取得した
  • 取得したコネクション数に応じた処理を簡単なスクリプトで作った
  • 同時接続数をカウントして接続数に応じた処理を簡単なスクリプトで実装した


引き続きの課題

  • Nginx をリバースプロキシとして設定して引き続き試す
  • やりたいこと(2)がまだ手付かず
  • 毎回パラメータをつけてアクセスさせるのは URL の見た目としてはあまり良くないのでパスに対してパラメータ無しの場合(デフォルトは connection_waiting)が出来るかどうかを確認する

@matsumotory さんにご協力頂いてやりたいことを実現しつつあります。@matsumotory さん、本当に有難うございました。引き続き、よろしくお願い致します!(^^ゞ

元記事は、こちら