人間とウェブの未来

「ウェブの歴史は人類の歴史の繰り返し」という観点から色々勉強しています。

virtualingでchroot配下のプロセスを簡単にリソース制御してみよう

virtualingとはkazuhoさんの低コストでchrootするためのjailingと、mruby-virtualingによるリソース制御や必要であればIPアドレスの付与する機能を組み合わせたツールです。virtualingを使うと、Rubyコードによって、リソース制御可能なchroot環境でミドルウェアやコマンドを実行させることができます。

https://github.com/matsumoto-r/mruby-virtualing

例えば、とあるhttpdをvirtualingで動かしたい時は、以下のようなコードを書きます。

Virtual.new({

  :resource => {
    # CPU [msec] exc: 30000 -> 30%
    :cpu_quota => 30000,
    # IO [Bytes/sec] 10 Mbyte/sec
    :blk_dvnd => "202:0",
    :blk_rbps => 10485760,
    :blk_wbps => 10485760,
    # Memory [Bytes] 512Mbyte
    :mem => 512 * 1024 * 1024,
  },

  :jail => {
    :root => "/chroot/apache",
    :bind => ["/usr/local"],
    :ro_bind => ["/usr/local/lib"],
    :cmnd => "/usr/local/apache/bin/httpd -X"
  },

  :ip => {
    :vip   => "192.168.0.30",
    :dev  => "eth0",
  },

}).run

これを、mruby-virtualをcloneしてrakeしてできたvirtualingコマンドを以下のように実行します。予めjailingコマンドをインストールしておく必要があります。

sudo ./virtualing httpd.rb

そうすると、Rubyのコードに書いたリソース設定の範囲内、かつ、chrootでファイルシステムが隔離された領域でApacheが動きます。

このサンプルでは、CPUを30%、IOのRead/Writeを10Mbyte/sec、メモリを512Mbyteに設定しているので、その範囲内でApacheが動く事になります。

で、ここで問題なのがCPUやIOは所謂帯域的な使い方ができるので、このリソースの閾値を最大値としてその中で継続的に処理をしてくれますが、メモリに関してはそうはいきません。例えば、512Mbyte以上プロセスが使うと、これまでと同じようにOoM(Out of Memory) Killerが走ってプロセスが殺されます。

それをどうにかしたいなぁと思って、Memoryの使用量やOoM Killerが走るようなeventを検知して、Rubyブロックをコールバックできるようにしました。そのために、

  • eventfdを操作するmruby-eventfdを実装
  • virtualing内でeventの検知からコールバックまでの処理を実装

のような対応しました。

メモリイベントを検知してRubyをコールバック

上記のサンプルでは、最終的にrunメソッドでVirtualを実行しています。そこで、メモリのイベントを検知してコールバックしたい場合は、runメソッドの代わりにrun_with_mem_eventfdを使います。

Virtual.new({

  :resource => {

  (snip)

    # Memory [Bytes] 512Mbyte
    :mem => 512 * 1024 * 1024,
  },

  (snip)

}).run_with_mem_eventfd { |ret| puts "OOM KILLER!!! > #{ret}" }

上記のように書くと、OoMKillerが走る直前にそのイベントを検知し、ブロックをコールバックして実行します。また、以下のように引数で:oomを渡しても同じ動作をします。つまり引数無しではデフォルトOoMKillerのイベントを検知します。

}).run_with_mem_eventfd(:oom) { |ret| puts "OOM KILLER!!! > #{ret}" }

メモリの使用量を検知

もう一つの使い方として、:usageを引数に渡すと指定のメモリ使用量を超えた、下回った時にブロックをコールバックすることができます。例えば、

}).run_with_mem_eventfd(:usage, 4 * 1024 * 1024) do |ret|
  puts "Usage Up or Down to threadshould !!! > #{ret}"
  # メモリ拡張とかする
end

このように書くと、4Mbyteの使用量でブロックをコールバックすることができます。これを利用すると、メモリ使用量を検知して、さらにメモリ使用量を増やすとか、減らすとか、どこかに通知するなどといった事がブロック内にかけるので、CPUやIOのように継続的に処理できないといったメモリの使用量回りでのデメリットをある程度改善できるのではないかと思います。

まとめ

以上のように、virtualingコマンドの改善点をご紹介しました。CPUやIOは帯域を絞りつつ継続的に処理できるのに対し、メモリではそのあたりを柔軟に処理できなかった問題をある程度解決できたのではないかと思います。

また、このあたりのイベント通知の機能はkernelのバージョンにも依存しており、手元のサーバで確認した限りでは、2.6.32ではOoMの通知はできるがUsageの通知はできない、3.10以降では両方の通知ができるという状況のようです。

ではでは、chrootでもリソースがある程度制御できるようになったので、基本的にはミドルウェアをそのまま起動させるのではなく、virtualingを経由して起動させるようにすると、セキュリティ面やリソース面で色々と捗るのではないかと思っておりますので是非使ってみてください。