先日、ApacheやnginxといったWebサーバにおいてDoS的アクセスを検知して任意の制御を行うhttp-dos-detectorを公開しました。
上記の拡張も非常に便利なのですが、もう少しシンプルに、現在のサーバに対するリクエスト状況においてとある属性、例えば、どのファイルにどれだけアクセスが発生しているかを計測したい場合があります。これまでは、そういった情報をApacheのスコアボードの範囲内で情報を得たりしてきました。
しかし、Webサービスの高度化に伴い、もう少し柔軟にアクセス状況を計測し、その計測を元に任意の制御をプログラマブルに行いたい場合があると思います。
そこで、それを実現するための拡張を、mod_mrubyとngx_mrubyを利用して実装しました。つまり、Apacheとnginx両方で動きますし、mrubyの実装コードも同じになっていますので、両方のサーバですぐに同等の処理を使う事ができます。
この拡張をhttp-access-limitterと名づけました。
h2o_mrubyも実装が進めば、この辺の処理も行えるようになると想像しております。
http-access-limitterの使用例
ということで、簡単な使い方は上記リポジトリのREADMEを見ていただくとして、基本的にはそれぞれのWebサーバのアクセスチェックのタイミングで任意の属性、例えば以下の例ではリクエストファイルのカウンタをインクリメントし、ログ出力のタイミングでカウンタをデクリメントすることができます。
公開しているコードでは、とあるファイルに対する同時アクセス数が2以上になったら503を返すような処理を書いており、以下のようになります。
- インクリメントのコード
#### threshold = 2 #### Server = get_server_class r = Server::Request.new cache = Userdata.new.shared_cache global_mutex = Userdata.new.shared_mutex file = r.filename config = { # access limmiter by target :target => file, } unless r.sub_request? limit = AccessLimitter.new r, cache, config # process-shared lock timeout = global_mutex.try_lock_loop(50000) do begin limit.increment Server.errlogger Server::LOG_NOTICE, "access_limitter: file:#{r.filename} counter:#{limit.current}" if limit.current > threshold Server.errlogger Server::LOG_NOTICE, "access_limitter: file:#{r.filename} reached threshold: #{threshold}: return #{Server::HTTP_SERVICE_UNAVAILABLE}" Server.return Server::HTTP_SERVICE_UNAVAILABLE end rescue => e raise "AccessLimitter failed: #{e}" ensure global_mutex.unlock end end end
- デクリメントのコード
Server = get_server_class r = Server::Request.new cache = Userdata.new.shared_cache global_mutex = Userdata.new.shared_mutex file = r.filename config = { # access limmiter by target :target => file, } unless r.sub_request? limit = AccessLimitter.new r, cache, config # process-shared lock global_mutex.try_lock_loop(50000) do begin limit.decrement Server.errlogger Server::LOG_NOTICE, "access_limitter_end: #{r.filename} #{limit.current}" rescue => e raise "AccessLimitter failed: #{e}" ensure global_mutex.unlock end end end
また、limit.current
によって現在のカウンタの値を取得し、その後に行う処理は上記例のように503を返すだけではなく、mrubyで何かAPIをつつくとか、mod_mrubyとかngx_mrubyの機能により別のプロキシにリダイレクトするとか、何かリクエストの属性をまとめたログをどこかに吐くとか、様々な制御を行う事ができます。
また、
file = r.filename config = { # access limmiter by target :target => file, }
このカウンターのターゲットをリクエスト処理における別の属性、例えばファイルのパーミッションだったり、アクセス元IPであったり、認証のユーザ名だったり様々な属性を使う事ができます。これによって、任意の属性に対する同時アクセス状況を計測可能となり、それに基づいた任意の制御が実行可能になります。
これらについても、mruby及びmod_mrubyやngx_mrubyで取得・加工可能なターゲットであれば何でも良いです。
AccessLimitter.new
なインスタンスを複数生成すれば、2段階の条件だったり、複数条件の組み合わせももちろん可能となります。やりたい事が色々できそうですね。
まとめ
ということで、http-dos-detectorのよりももう少しシンプルでありながら、使い方によっては様々なな応用が可能なhttp-access-limitterを実装・紹介しました。これと、http-dos-detectorを使えば、アクセス状況や同時接続状況などさまざまな状況において適切な制御を実装することができると思うので、是非色々試してみたください。