Hatena::ブログ(Diary)

peo3の日記

2011-01-31 cgroupsとしばらく一緒に過ごしてみた

この記事は500 Internal Server Error : イベント開催支援ツール ATND(アテンド)向けに書かれました。

はじめに

サーバ側ではかなり前から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)
<root>: 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でオプションを確認してね!

環境

要件

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

  1. ブラウザが使う資源を制限したい
  2. バックグラウンドプロセスが使う資源を制限したい
  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

使ったサブシステムも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本体とFlashCPU割当て比が1:1になり、FlashCPUを大量に消費すると本体の動画もっさりします。そこで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/<pid>/{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で設定したメモリ量を必ず越えないようにカーネルが制限をかける一方で*3softで設定したメモリ量は、空きメモリに余裕がある間は超えても良いが、メモリが足りない状況では回収(もしくはスワップアウト?)されるかもしれないですすよ、ということらしいです。

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. Linux 2.6.35.9付属のcgroupのドキュメント
  2. Red Hatの平さんのプレゼン資料 (pdf)
  3. Ubuntuで簡単にカスタマイズカーネルを作る方法 (英語)
  4. RHEL 6のcgroupの文書 (英語)
  5. The Proc Connector and Socket Filters

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

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

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