リクエスト単位でApacheのリソースを制御するためのmrubyを使った制御DSLコードの紹介

IOT研究会コンテナ勉強会で発表した内容を制御コード例を交えて具体的なリソース制御の書き方を紹介したいと思います。

詳しい内容は上の行のエントリへのリンクを見て頂くとして、簡単にまとめると、Apacheへのリクエスト単位でCPUやDISK I/O等のコンピュータリソースを制御しようという話になります。今回はその仕組(mod_mrubyでcgroupを制御)の導入から具体的な制御DSLコードのサンプルを紹介します。

導入

導入はこれまでに何度も紹介していた内容とかぶるのですが、mod_mrubyのインストールと同様です。まずはgithubからmod_mrubyをcloneして、mod_mrubyディレクトリにあるbuild_config.rbを開いて、コメントアウトになっているmruby-cgroupのリンクを有効にします。mruby-cgroupは、mrubyからcgroupを制御するmrbgemになります。

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

conf.gem :git => 'git://github.com/matsumoto-r/mruby-cgroup.git'

[/program]

後は、mod_mrubyのインストールと同様、READMEに従ってインストールします。つまり、上記の手順がmrubyに拡張であるmrbgemを組み込むための手順にもなります。

制御DSLサンプル

では、どういう使い方ができるのか、思いつくものを列挙していこうと思います。これによって皆様のやりたいことを思いつくきっかけになれば良いですね。

単純に全てのリクエストをCPU50%に制御

場合によっては、100%使ってサーバ落ちるなんて事象もありますので、こうしておくと安心ですね。制御DSLは以下になります。レスポンス処理の前のフックで、リソース制御のDSLコードを呼び出すようにしておきます。

また、第二引数のcacheは、ファイルをApache httpd起動時にコンパイルしておいてメモリにキャッシュしておくためのオプションです。httpd起動中にDSLコードを変更する必要がある場合等は第二引数を無しにしておくと良いでしょう。その辺りの説明は先日のエントリで紹介しています。

  • httpd.conf

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

mrubyFixupsMiddle           /usr/local/apache/hooks/resouce_manage_start.rb cache

[/program]

  • resource_manage_start.rb

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

c = Cgroup::CPU.new "apache/resouce_manage"

# CPU使用率50%に制御
c.cfs_quota_us = 50000

if !c.exist?
  c.create
end

c.attach

[/program]

これで、全てのリクエストがCPU使用率50%に制御されます。

リクエスト情報を条件にCPU30%に制御

次に、リクエストに関する情報を条件にして、リクエストのリソースを制御してみましょう。

mod_mrubyではApache::Requestクラスからリクエストに関する様々な情報を得る事ができます。例えば、ファイル名やバーチャルホスト名、Basic認証等のユーザ名、リクエストメソッド、プロトコル、ファイルサイズ、ファイルのowner・group、ファイル・タイプ、inode、リンク数、タイムスタンプ・・・挙げだすときりがないのですが、まぁ色々有ります。詳しくはこの辺りのコードを見ると良いです。

それらを使う事で、例えば条件にマッチした場合はCPU使用率30%に制御するためには以下のように書きます。今回はマッチしない場合は元の制御に戻す必要があるので、リクエスト前後でリソース制御DSLのフックが必要になります。リクエスト前でリソースを制限して、レスポンス後にリソースを元に戻すという流れです。

  • httpd.conf

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

mrubyFixupsMiddle           /usr/local/apache/hooks/resouce_manage_start.rb cache
mrubyLogTransactionMiddle   /usr/local/apache/hooks/resouce_manage_end.rb   cache

[/program]

  • resource_manage_start.rb

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

r = Apache::Request.new

# 色々な条件を書ける。複数条件や正規表現、配列やハッシュももちろん書ける。Rubyと同じ。
if r.filename == "/path/to/file.cgi"
#if r.hostname == "matsumoto-r.jp"
#if r.method == "POST"
#if r.protocol == "HTTP/1.1"
#if r.user == "matsumoto_r"
#if r.finfo.user == 500
#if r.finfo.size > 1000
#if r.filename =~ /^.*¥.cgi/ && r.finfo.user == 500
  c = Cgroup::CPU.new "apache/resouce_manage"
  c.cfs_quota_us = 30000
  if c.exist?
    c.modify
  else
    c.create
  end

  c.attach
end

[/program]

  • resource_manage_end.rb

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

c = Cgroup::CPU.new "apache/resouce_manage"

# CPU100%に戻す
c.cfs_quota_us = 100000
c.modify

[/program]

もちろんこれらは、制御構造はmrubyなので複数の条件を指定したり、正規表現を使う事も可能です。配列や連想配列でリストを作るのも良いですね。

サーバの負荷が低い時は各バーチャルホストはCPU30%で負荷が高い時はCPU10%に制御

サンプルを書き出すときりがないので、最後にホスティングでよくあるようなシチュエーションを想定してリソースを制御してみます。

サーバの負荷によってリソースを動的に制御したい場合もあると思います。mod_mrubyを使えば、そういうことも簡単にできます。サーバ負荷が低い場合はCPU使用率30%、負荷が上がってきたら10%に制御したい場合は以下のようなDSLになるでしょう。(今回は簡単のため、vhostディレクトリを作って仮想ホストがある想定で書いています)

  • httpd.conf

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

<Location /vhosts>
    mrubyFixupsMiddle           /usr/local/apache/hooks/resouce_manage_start.rb cache
    mrubyLogTransactionMiddle   /usr/local/apache/hooks/resouce_manage_end.rb   cache
</Location>

[/program]

  • resource_manage_start.rb

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

sc  = Apache::Scoreboard.new
c   = Cgroup::CPU.new "apache/resouce_manage"

# 通常はCPU30%に制御
c.cfs_quota_us = 30000

# スコアボードクラスからサーバやプロセスの情報を条件に比較
# 単位時間辺りのリクエスト数や転送量等も条件に利用可
# この場合はロードアベレージ(1分)が1.0以上だったらCPU10%
if sc.loadavg[0] > 1.0
  c.cfs_quota_us = 10000
end

if c.exist?
  c.modify
else
  c.create
end

c.attach

[/program]

  • resource_manage_end.rb

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

c = Cgroup::CPU.new "apache/resouce_manage"

c.cfs_quota_us = 30000
c.modify

[/program]

上記のように、Apache::Scoreboardクラスを使う事で、Apacheプロセスのアクセス・負荷情報を取得する事ができます。詳しくはここを見て下さい。

それぞれの仮想ホストがロードアベレージが1以下の間は30%ずつCPU使用率を割り当てられ、1以上の場合は10%に制御されます。簡単ですね。上述した条件等をいれることで、より柔軟な制御ができるようになると思います。例えば、悪い子ちゃんuidやホスト名を配列で与えておいて、そこに含まれていたらCPUを5%にする、等も可能です。

まとめ

このように、サンプルを提示することで非常に簡単にリクエスト単位でリソース制御できる事がわかりました。上記のサンプルではCPU使用率だけでしたが、現状、CPU、CPUコア、DISK I/O等も制御できるようになっています。今後は、トラフィックやメモリ等も実装して、より細かくリソースを制御できるようにしたいと思います。

皆様も是非、自身の環境にあったリソース制御を記述して見て下さい。こんな使い方もできたよーとか、こんな制御はできませんか?等ありましたら、@matsumotoryにメンション頂ければ反応します。是非是非、最初は遊び程度に試してみて下さい!