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でオプションを確認してね!
環境
要件
ここでは具体的な要件(私がやりたいこと)をまとめます。
具体的な設定
主に/etc/cgconfig.confと/etc/cgrules.confをいじっていますが、cgrules.confの方は見たまんまなのでcgconfig.confの方を説明していきます。
完成品
https://gist.github.com/802907 に置いておきますね。
ブラウザ編
作ったグループは2つ。
使ったサブシステムも2つ。
- cpu
- 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つ。
使ったサブシステムは4つ。
- cpu
- memory
- blkio
- 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で設定するようにしました。
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/
そうすることで/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に該当するんだろ?もしかして大して効果ないのかな?
この辺りはまた暇があったら調べてみます。
おわりに
といった感じで様々な設定を行なうことで、前述の目標
- ブラウザが使う資源を制限したい
- バックグラウンドプロセスが使う資源を制限したい
- Mozcの反応を良くしたい
- 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:やむを得ず超えることもあるかも?この辺はソース読まないとなんとも言えないです。