cgroupsとしばらく一緒に過ごしてみた

この記事はhttp://atnd.org/events/10701向けに書かれました。

はじめに

サーバ側ではかなり前からcgroupsを使っていましたが、クライアント側では使ったことがなかったので、頑張ってノートPCで使ってみることにしました。12月から使い始めて、1月半ぐらい使い続けてます。

サーバ側では独自スクリプトを書いて設定していましたが、今回は既存のツール(libcgroup)を使っています。とはいえ、libcgroupには資源利用状況を確認する機能がなかったので、独自スクリプト(cgtree)を作りました。もしよかったら使ってみてね!

今回cgroupsでやりたかったことは、

  • 資源を大量に消費するプロセスの制限
  • バックグラウンドプロセスの制限

などです。限られた資源を無駄に使うプロセスに鉄槌を。

注意

この記事で紹介する設定は、自分の環境に合わせてちまちまとチューニングしてきた結果ですので、万人向けではありません。私の設定は参考程度にして、自分の要件に合わせてチューニングしましょう。

定量的に評価してないので、著者の気のせいかもしれません。I _feel_ that the user experience is better than so far!

禁句:良いスペックのマシン買えば?メモリ増やせば?

基礎知識

cgroupsとは?

Linuxに実装された、プロセスをグループに分けて資源使用量を調節する機能です。個々の資源毎にサブシステムが用意されていて、個別にon/offできます。今回はcpu, cpuacct, memory, blkio, freezerサブシステムを使います。

何も設定していなければ、すべてのプロセスはどのグループにも属さないことになります。(rootグループに入っているとも言う。)

詳しくはRed Hatの平さんのプレゼン資料 (pdf)を読めばいいと思うよ!

libcgroupとは?

cgroupsの設定や操作を簡単に使うためのツールです。いろいろなデーモンやコマンドラインツールがありますが、今回主に使うのはcgconfigサービスとcgredサービス(cgrulesengdデーモン)です。cgconfigは/etc/cgconfig.confに設定されたとおりにグループ階層を作り(mountし)各種パラメタを設定します。cgredはfork/execなどを監視し、設定されたとおりにプロセスをグループに入れます(詳しい動作は後述)。

詳しくはRed Hatの平さんのプレゼン資料 (pdf)を読めばいいと思うよ!

cgtreeとは?

cgtreeは拙作のcgroupsグループ単位で資源利用状況を表示してくれるツールです。

[使用例]

$ cgtree -o memory
# Total=1434.7MB, Used(w/o buffer/cache)=699.8MB, SwapUsed=139.0MB
# Legend: # of procs (TotalUsed, RSS, SwapUsed)
: 103 procs (636.3MB, 343.3MB, 118.9MB)
  bgjob: 10 procs (42.3MB, 31.7MB, 31.7MB)
  noswap: 2 procs (29.9MB, 6.0MB, 1.1MB)
  browser: 1 procs (351.8MB, 201.5MB, 0.0MB)
    flash: 1 procs (13.1MB, 7.2MB, 0.0MB)

詳しい使い方はREADMEを読むかcgtree -hでオプションを確認してね!

環境

  • ハードウェア
    • Intel(R) Core(TM)2 CPU U7500 @ 1.06GHz
    • 1.5 GB RAM
  • ソフトウェア
    • Ubuntu 10.10 (Maverick)
    • linux-2.6.35-25-genericをカスタマイズしてblkioを有効にしたもの
    • libcgroup-0.37

要件

ここでは具体的な要件(私がやりたいこと)をまとめます。

  1. ブラウザが使う資源を制限したい
    • 他のプロセスのCPUやメモリを食い尽くすのを止めさせたい
    • Flashをおとなしくさせたい
      • Firefoxの場合Flashのせいで本体が遅くなることがある
  2. バックグラウンドプロセスが使う資源を制限したい
    • cron, anacron, dropbox, tracker(検索インデクス生成する要らない子), gwibber-service(マルチサービス対応SNSクライアントな要らない子)
    • カーネルコンパイル
      • 他の仕事を優先させたくて、コンパイルを一時停止させたいという要件もある
    • おまいらにくれてやる資源ねーから!
  3. Mozcの反応を良くしたい
    • しばらく使ってなくて、いざ使おうとすると反応が遅くなるのをどうにかしたい
    • スワップアウトさせない
  4. Autogroupの真似事もしてみたい
    • .bashrcとかに書くアレ (注:このページ妙に重いです)

具体的な設定

主に/etc/cgconfig.confと/etc/cgrules.confをいじっていますが、cgrules.confの方は見たまんまなのでcgconfig.confの方を説明していきます。

完成品

https://gist.github.com/802907 に置いておきますね。

ブラウザ編

作ったグループは2つ。

  1. browser
    • 他のプロセスからの隔離
  2. browser/flash
    • Flashのブラウザ本体への悪影響を減らす

使ったサブシステムも2つ。

  1. cpu
  2. memory

(cpuacctはcpuとペアで使うのでカウントしてないです。)

group browser {
  cpu {
    cpu.shares = 500;
  }
  cpuacct {
  }
  memory {
    memory.soft_limit_in_bytes = 400M;
    memory.limit_in_bytes = 500M;
  }
}
group browser/flash {
  cpu {
    cpu.shares = 200;
  }
  cpuacct {
  }
  memory {
    memory.soft_limit_in_bytes = 200M;
    memory.limit_in_bytes = 300M;
  }
}

[cpu]

browserグループのcpu.shareは500にしてあり、rootグループのプロセスの半分しかCPUが割り当たらないようにしています。最適な値かどうかわからないですが、今のところ問題なさそうです。

Firefox-4.0はfirefox-binとplugin-container(Flash)という2つのプロセスを生成します。同一グループに両プロセスを入れた場合、Firefox本体とFlashでCPU割当て比が1:1になり、FlashがCPUを大量に消費すると本体の動画がもっさりします。そこでbrowser/flash/をcpu.share=200に設定して比率を5:1になるようにしました。

ちなみにGoogle Chromeの場合は、本体、タブ、拡張機能がそれぞれ別プロセスになるため、Flashと同じグループに置いておいてもcpu割当て量は1:1になりません(N:1になる)。なのでbrowser/flashグループは必要ないと思います。

[memory]

本体を400MB、Flashに200MB割り当たるように設定しました。日々調整しながら決まった値なので、環境依存です。また変えるかも。

階層構造を作っていますが、use_hierarchy=1にしていないので、制限は独立しています(つまり400MBを本体とFlashで取り合うわけではない)。

soft_limit_in_bytesとlimit_in_bytesについては後述。

バックグラウンドプロセス編

作ったグループは2つ。

  1. bgjob
    • 他のプロセスからの隔離
  2. bgjob/restricted

使ったサブシステムは4つ。

  1. cpu
  2. memory
  3. blkio
  4. freezer
group bgjob {
    task {
      uid = root;
      gid = peo3;
    }
    admin {
      uid = root;
      gid = root;
    }
  cpu {
    cpu.shares = 10;
  }
  cpuacct {
  }
  blkio {
    blkio.weight = 100;
  }
  memory {
    memory.soft_limit_in_bytes = 100M;
    memory.limit_in_bytes = 200M;
    memory.swappiness = 100;
  }
}
group bgjob/restricted {
  perm {
    task {
      uid = root;
      gid = peo3;
    }
    admin {
      uid = root;
      gid = peo3;
    }
  }
  cpu {
  }
  cpuacct {
  }
  freezer {
  }
}

[bgjob]

とにかく割当て資源量を少なくしています。cpu.share=10(rootグループのプロセスの1/100), メモリは100MB, swappinessは100(最もスワップアウトされ易くなる), blkio.weight=100(最低値)に設定。

[bgjob/restricted]

カーネルコンパイル時にはプロセスを大量に生成します。そして個々のプロセスはbgjob内のプロセスと同等のCPU資源を消費するので、バックグラウンドとはいえあまり嬉しくなかったので作りました。CPU使用量のみ制限してます。

単にCPU使用量を減らすだけならnice値を上げれば良いのですが、コンパイルを中断したいときがあったので別グループにしてfreezerサブシステムを有効にしました。これにより、

cgset -r freezer.state=FROZEN bgjob/restricted

で中断

cgset -r freezer.state=THAWED bgjob/restricted

で再開できます。また

cgexec -g blkio:bgjob cgexec -g cpu,freezer:bgjob/restricted fakeroot make-kpkg --initrd --append-to-version=-blkio kernel-image kernel-headers

で当該グループ内でカーネルコンパイルを行なうことができます*1

ちなみにユーザがtasksやfreezer.stateを書きかえられるようにpermで設定しています。

Mozc編
group noswap {
  memory {
    memory.swappiness = 0;
  }
}

swappinessを0にしてスワップされにくいようにしてます。いちおう効果があるぽいです。

なんちゃってAutogroup編
group user {
  perm {
    task {
      uid = root;
      gid = peo3;
    }
    admin {
      uid = root;
      gid = peo3;
    }
  }
  cpu {
#   release_agent = /usr/local/sbin/cgroup_clean;
  }
  cpuacct {
  }
  blkio {
    blkio.weight = 500;
  }
}

ユーザがディレクトリを作れるようにperm.admin.gidをユーザのGIDに設定しています。

libcgroupはrelease_agentの設定に対応していないみたいです。なので/etc/init.d/cgconfigで設定するようにしました。

bgjob/restrictedでカーネルコンパイルするようにしたので、効果はあんまりないかも?

cgrules.conf

cgconfig.confで生成したグループにどのプロセスを入れるかを記述する設定ファイルです。cgrulesengdというデーモンが指定プロセスを指定グループに入れます(詳しい動作は後述)。

peo3:firefox-4.0-bin  cpu,memory       browser
peo3:google-chrome    cpu,memory       browser
peo3:firefox-bin      cpu,memory       browser
peo3:plugin-container cpu,memory       browser/flash
peo3:ibus-engine-mozc memory           noswap
peo3:update-manager   cpu,blkio,memory bgjob
peo3:software-center  cpu,blkio,memory bgjob
peo3:dropbox          cpu,blkio,memory bgjob
peo3:gwibber-service  cpu,blkio,memory bgjob
peo3:tracker-store    cpu,blkio,memory bgjob
peo3:tracker-extract  cpu,blkio,memory bgjob
peo3:tracker-miner-fs cpu,blkio,memory bgjob
lastfm:lastfmsubmitd  cpu,blkio,memory bgjob
#root:cron             cpu,blkio,memory bgjob
root:anacron          cpu,blkio,memory bgjob
root:aptd             cpu,blkio,memory bgjob

cronの項目をコメントアウトしていますが、それはcgrulesengdがcronだけグループに入れてくれないからです。原因はまだわかってないです。

ただノートPCは常時起動させていないので、ほとんどの場合、定期実行タスクはanacron経由で実行されるため困ってはいないですが。

Tipsと注意

libcgroupのdefaultグループ

何はともあれ/etc/default/cgconfigを編集してCREATE_DEFAULT=noにしましょう。

cgconfigはCREATE_DEFAULT=yesのときにdefaultグループにすべてのプロセスを入れます。ですが、入れてはまずいカーネルスレッドも入れちゃうため、動作がおかしくなるときがあります*2。私の環境ではレジュームできなくなりました。

安全のためdefaultグループは使わないようにするのが良いと思います。(実際、なくても困らないですし。)

service cgconfig restart

修正した設定を反映させようとcgconfig restart/reloadすると、一度全部グループを消しちゃうので、割当てられたプロセスが全部rootグループに戻ってしまいます。

なので設定を反映させたかたらcgset等で自力でやるか、いっそのことrebootさせるのが良いかも。

service cgred restart

前述の通りシェルプロセスはuser/????/グループに入っているため、シェルでservice restartするとcgrulesengdデーモンがそのグループに入ってしまいます。

代わりにrootグループに入れるためには

sudo cgexec -g cpu,memory,blkio:/ service cgred restart

ってやると良いです。

cgconfig.confのコメントアウト

どうも行頭に'#'を書かないとだめみたいです。

デバッグ

See /var/log/messages

もっとkwsk知りたい方

cgrulesengdの動作

[どのタイミングでプロセスをグループに入れている?]

cgrulesengdはデーモンで、起動しているときにだけ指定プロセスを指定グループにいれてくれます。

最初は定期的にプロセスリストをチェックしてるのかと思ったのですが、そうではないようです。

netlinkのNETLINK_CONNECTORファミリでカーネルprocess events connectorという機能を使うことで、プロセスの起動などのイベント発生時にcallbackしてもらってるようです。fork/exec/exit/setuid/setgidなどのイベントを契機にプロセスをグループに入れたりしているようです。

[cgrules.confに指定するプロセス名前]

どういうプロセス名前が指定可能なのか調べてみると、わりとまじめに名前抽出をしてることがわかりました。

cgrulesengdは/proc//{status,exe,cmdline}の3つのファイルを使ってプロセス名を割り出しています。基本的にはstatusのNameエントリを見れば名前が判るのですが、(何の制限か知らないですが)その名前は15文字以降が切り捨てられています。そこでexe(シンボリックリンク)が指すファイルを使って名前を補完しようとします。もし、exeが実行可能ファイルならファイル名をそのまま使うのですが、もしそれがシェルやスクリプト言語インタプリタだった場合はさらにcmdlineを参照します。コマンドライン引数の2番目をチェックして、statusの名前と部分一致した場合cmdlineから取り出した名前を使います。

そうすることで/usr/bin/python /usr/lib/system-service/system-service-dのようなプロセスから、system-service-dという名前を抽出できるようになり、cgrules.confでsystem-service-dと指定可能になるわけです。

memory.limit_in_bytesとmemory.soft_limit_in_bytes

※正確なところはカーネル付属文書のSoft limitsの項を読むかソースコードを参照してください。

ざっくり言うとlimit_in_bytes(以下hard)の方が強い制限でsoft_limit_in_bytes(以下soft)の方が弱い制限ということになります。hardで設定したメモリ量を必ず越えないようにカーネルが制限をかける一方で*3、softで設定したメモリ量は、空きメモリに余裕がある間は超えても良いが、メモリが足りない状況では回収(もしくはスワップアウト?)されるかもしれないですすよ、ということらしいです。

softの細かい動作はこの辺りを読めば解るかも? kswapd => balance_pgdat => mem_cgroup_soft_limit_reclaim

もしsoft関連のreclaimがここにしかないならば、グループ内のプロセスがsoft limitを超えるメモリを使おうとしても、そのタイミングでは制限をかけない、ってことになるんじゃないかと思います。

group_isolationは設定すべき?

Linux付属ドキュメントにgroup_isolationに関する項目というものがあります。どうも、isolationを厳しくするか否かの設定らしいです。

私の環境では/sys/block/sda/queue/iosched/group_isolationです。

ドキュメントを読むと、0の場合(デフォルト)はsequential workloadの場合しか公平性を保証しない。1の場合は加えてrandom I/Oの公平性も保証するが、スループットが落ちるらしいです。

その下の段落の説明はよくわからなかったです。sync-noidleとかcollective idlingってなんだろ?

あと、最後の節「What works」に

Currently only sync IO queues are support. All the buffered writes are still system wide and not per group.

とありますね。。。うーん実際のところ普段のworkloadのうちどのぐらいがこのsync I/Oに該当するんだろ?もしかして大して効果ないのかな?

この辺りはまた暇があったら調べてみます。

おわりに

といった感じで様々な設定を行なうことで、前述の目標

  1. ブラウザが使う資源を制限したい
  2. バックグラウンドプロセスが使う資源を制限したい
  3. Mozcの反応を良くしたい
  4. Autogroupの真似事もしてみたい

は達成できた気がします

個々の設定を決めるために、top, iotop, free, ps, vmstat, dstat, nice, ioniceそしてcgtreeといった資源使用量表示ツールを使って思ったとおり動いているか、自分で体感して不満はないかなどかなり試行錯誤をしています。快適な環境は一日にしてはならずですね。まぁメモリ増設すれ「それ以上いけない」

なにはともあれ、みんなもcgroupsを日常的に使ってみよう!

*1:カーネル2.6.35はblkioの階層機能がなく、blkioとcpu,freezerでグループ階層が違うため、個別にグループ割当てを行なってます。2.6.38からはこの制限がなくなります。

*2:関係ありそうなバグレポート https://lkml.org/lkml/2011/1/4/313

*3:やむを得ず超えることもあるかも?この辺はソース読まないとなんとも言えないです。