mod_mrubyでアクセス数やトラフィック量によるApacheのアクセス制限

mod_mrubyでApacheのスコアボードのデータを取得できるようになりましたので、アクセス数やトラフィック量、及びリクエスト処理中のスレッド数の割合でアクセス制限が可能になりました。

ということで、早速mod_mrubyを使って、Apacheのアクセス制限を行うためのサンプルRubyスクリプトを書いてみました。Githubでもexample/で公開中です。Rubyスキルが低すぎるのでスクリプトに関するツッコミは程々でお願いします…

Apacheの1秒間トラフィック量でアクセス制限

サンプルスクリプトは以下のようになります。

# LoadModule mruby_module modules/mod_mruby.so
# ExtendedStatus On
# mrubyAccessCheckerMiddle /var/www/html/traffic_rate_limit.rb

class Traffic
    def initialize(rate)
        @rate = rate
    end
    def kbcheck
        sb = Apache::Scoreboard.new()
        kbpersec = sb.total_kbyte / sb.uptime * 100
        Apache.errlogger(4, "kbpersec: " + kbpersec.to_s)
        if kbpersec > @rate
            true
        else
            false
        end
    end
end

# traffic kbyte / sec is 10000(10MB/sec)
t = Traffic.new(10000)
if t.kbcheck
    Apache::return(Apache::HTTP_SERVICE_UNAVAILABLE)
else
    Apache::return(Apache::DECLINED)
end

このスクリプトでは、トラフィック量による簡単なアクセス制限を実現するため、Apacheが起動してから処理している毎秒のトラフィック量(kbpersec)が10MB/secを超えた(t.kbcheck)場合に、503を返します。実運用で使う場合には、もう少し凝った実装が必要だと想いますが、これでもそれなりに制限できそうです。

Apacheの設定は、AccessCheckerフェイズでフックさせるようにしておきます。scoreboardの値を得られるようにExtendedStatusもOnにしておきましょう。

LoadModule mruby_module modules/mod_mruby.so
ExtendedStatus On
mrubyAccessCheckerMiddle /var/www/html/traffic_rate_limit.rb

リクエスト処理中のWorkerスレッドの割合や1秒間のアクセス数でアクセス制限

サンプルスクリプトは以下です。

# LoadModule mruby_module modules/mod_mruby.so
# ExtendedStatus On
# mrubyAccessCheckerMiddle /var/www/html/worker_rate_limit.rb

class Worker
    def initialize(rate, count)
        @sb = Apache::Scoreboard.new()
        @myrate = rate
        @mycount = count
    end
    def busycheck
        busyrate = @sb.busy_worker / (@sb.server_limit * @sb.thread_limit) * 100
        Apache.errlogger(4, "busy worker rate: " + busyrate.to_s)
        if busyrate > @myrate
            true
        else
            false
        end
    end
    def accesscheck
        accesspsec = @sb.total_access / @sb.uptime
        Apache.errlogger(4, "access per sec: " + accesspsec.to_s)
        if accesspsec > @mycount
            true
        else
            false
        end
    end
end

# busy worker thread rate is 90 and access per sec is 1000
w = Worker.new(90, 1000)
if w.busycheck
    Apache::return(Apache::HTTP_SERVICE_UNAVAILABLE)
elsif w.accesscheck
    Apache::return(Apache::HTTP_SERVICE_UNAVAILABLE)
else
    Apache::return(Apache::DECLINED)
end

Apacheで動作しているすべてのWorkerスレッドにおいて、リクエストを処理しているWorkerスレッドの割合(busyrate)が90%を超えた(w.busycheck)場合に503を返します。

また、そのチェック(w.busycheck)は問題なくても、1秒間のアクセス数が1000アクセスを超えた(w.accesscheck)場合に、503を返すような実装もしてみました。

最後に

503を返すような簡単なアクセス制限であれば、このように簡単に実装できるようになってきました。今後は、トラフィックのコントロールもできるようにしたいです。後は、mrubyの拡張ライブラリ(IO、Socket、File、Process)がリリースされれば、もっと色々おもしろい事ができそうなので、リリースが待ち遠しいです。自分で作っちゃってもいいのですが、そこはあまり分散しないように、リリースを待とうとおもいます。