cgroupを使ってCPUとメモリの割り当てを制限する

cgroupを使ってCPUとメモリの割り当てを制限してみたけど、忘れてしまいそうなのでメモを残す。
試した環境はCentOS 6.4。

インストール

CentOS6でcgroupを使うにはlibcgroupをインストール。

$ sudo yum install libcgroup

cgconfigサービスを起動すると、/cgroup/ディレクトリが有効になる。自動で起動させるならchkconfigで設定。

$ sudo chkconfig cgconfig on
$ sudo service cgconfig start

気にしそうなところ

  • cpuやmemoryなど利用可能なサブシステムはOSによって異なる?
  • cgconfigサービスで起動時にマウントされるサブシステムは/etc/cgconfig.confで設定
  • cgredサービスを起動すると、ルールベースでプロセスをグループに自動で追加してくれる
  • コマンド群で各種操作できる(lscgroup, cgcreate, cgdelete, cgexecなど)
  • cgsnapshotコマンドは、現在のcgroupの状態をcgconfig.confの形式で標準出力
  • 1グループごとのリソース制限の指定になるので、リソースを制限する単位でグループを作る
    • プロセス毎に個別にリソース制限したい場合はグループたくさん作る?
  • プロセスをフォークした場合、同じグループに所属する
  • cgroupを指定してコマンドを実行するならcgexecを使う
  • cgcreateは既に存在しているグループ名を指定してもエラーにならない(シェルスクリプトでチェック機構なしに毎回実行して大丈夫だった)

グループ作成

今回は、コマンドで作成する。cgconfig.confに設定を書いて有効にする方法もある。
my-groupという名前で作ってみる。サブシステムcpuとmemoryの制限を指定するのでこんな感じ。

$ sudo cgcreate -g cpu,memory:/my-group

作成できたかを確認するにはlscgroupで一覧を見る。

$ sudo lscgroup

作成できていれば、/cgroup/cpu/my-group/と/cgroup/memory/my-group/ディレクトリが作成されている。

$ ls /cgroup/cpu/my-group/
cgroup.event_control  cgroup.procs  cpu.cfs_period_us  cpu.cfs_quota_us  cpu.rt_period_us  cpu.rt_runtime_us  cpu.shares  cpu.stat  notify_on_release  tasks
$ ls /cgroup/memory/my-group/
cgroup.event_control  memory.failcnt      memory.limit_in_bytes      memory.memsw.failcnt         memory.memsw.max_usage_in_bytes  memory.move_charge_at_immigrate  memory.soft_limit_in_bytes  memory.swappiness      memory.use_hierarchy  tasks
cgroup.procs          memory.force_empty  memory.max_usage_in_bytes  memory.memsw.limit_in_bytes  memory.memsw.usage_in_bytes      memory.oom_control               memory.stat                 memory.usage_in_bytes  notify_on_release

このディレクトリ内のファイルに値を書き込めば各種制限を変更できる。

CPUの割り当て制限

cpu.cfs_period_usとcpu.cfs_quota_usの値を調整する。

$ cat /cgroup/cpu/my-group/cpu.cfs_period_us
100000
$ cat /cgroup/cpu/my-group/cpu.cfs_quota_us
-1

1コアの50%に制限するにはcpu.cfs_quota_usに50000と書き込む(cfs_period_usの半分)

$ sudo sh -c "echo 50000 > /cgroup/cpu/my-group/cpu.cfs_quota_us"

メモリの割り当て制限

memory.limit_in_bytes(ユーザーメモリの上限)とmemory.memsw.limit_in_bytes(ユーザーメモリ+スワップの上限)の値。

$ cat /cgroup/memory/my-group/memory.limit_in_bytes
9223372036854775807
$ cat /cgroup/memory/my-group/memory.memsw.limit_in_bytes
9223372036854775807

ユーザーメモリ(物理メモリ)を100MB、スワップを500MBに制限する。

$ sudo sh -c "echo 100M > /cgroup/memory/my-group/memory.limit_in_bytes"
$ sudo sh -c "echo 500M > /cgroup/memory/my-group/memory.memsw.limit_in_bytes"

動作の検証

こういうリソースの割り当て制限の検証に便利なツールってあるのだろうか?知らない。
とりあえずスクリプトを作って動作させて、topコマンドやログを眺めるぐらいしかしてない。

CPUの割り当て制限の確認

無限ループでCPUを食いつぶすスクリプトを作る。
test_cpu.py

while True:
    pass

実行結果:

$ ps aux|grep py$
root      5631  0.0  0.1 173332  2628 pts/1    S+   22:42   0:00 sudo cgexec -g cpu,memory:my-group python test_cpu.py
root      5632 49.5  0.1 115044  3720 pts/1    R+   22:42   1:00 python test_cpu.py

CPUの使用率が50%前後になった。

メモリの割り当て制限の確認

文字列を連結していってメモリを使い切るようなスクリプトを作る。
test_memory.py:

s = "0123456789" * 100000
while True:
    s += s

実行結果:

$ sudo cgexec -g cpu,memory:my-group python test_memory.py  # 数秒後に終了する
$ sudo less +G /var/log/messages
Jun 19 22:45:50 localhost kernel: python invoked oom-killer: gfp_mask=0xd0, order=0, oom_adj=0, oom_score_adj=0
Jun 19 22:45:50 localhost kernel: python cpuset=/ mems_allowed=0
Jun 19 22:45:50 localhost kernel: Pid: 5671, comm: python Not tainted 2.6.32-358.el6.x86_64 #1
Jun 19 22:45:50 localhost kernel: Call Trace:
Jun 19 22:45:50 localhost kernel: [<ffffffff810cb5d1>] ? cpuset_print_task_mems_allowed+0x91/0xb0
Jun 19 22:45:50 localhost kernel: [<ffffffff8111cd10>] ? dump_header+0x90/0x1b0
Jun 19 22:45:50 localhost kernel: [<ffffffff8121d0bc>] ? security_real_capable_noaudit+0x3c/0x70
Jun 19 22:45:50 localhost kernel: [<ffffffff8111d192>] ? oom_kill_process+0x82/0x2a0
Jun 19 22:45:50 localhost kernel: [<ffffffff8111d0d1>] ? select_bad_process+0xe1/0x120
Jun 19 22:45:50 localhost kernel: [<ffffffff8111d912>] ? mem_cgroup_out_of_memory+0x92/0xb0
Jun 19 22:45:50 localhost kernel: [<ffffffff81173454>] ? mem_cgroup_handle_oom+0x274/0x2a0
Jun 19 22:45:50 localhost kernel: [<ffffffff81170e90>] ? memcg_oom_wake_function+0x0/0xa0
Jun 19 22:45:50 localhost kernel: [<ffffffff81173a39>] ? __mem_cgroup_try_charge+0x5b9/0x5d0
Jun 19 22:45:50 localhost kernel: [<ffffffff81174b3d>] ? mem_cgroup_try_charge_swapin+0xbd/0xd0
Jun 19 22:45:50 localhost kernel: [<ffffffff811438ed>] ? handle_pte_fault+0x35d/0xb50
Jun 19 22:45:50 localhost kernel: [<ffffffff8104baa7>] ? pte_alloc_one+0x37/0x50
Jun 19 22:45:50 localhost kernel: [<ffffffff8114431a>] ? handle_mm_fault+0x23a/0x310
Jun 19 22:45:50 localhost kernel: [<ffffffff810474c9>] ? __do_page_fault+0x139/0x480
Jun 19 22:45:50 localhost kernel: [<ffffffff810097cc>] ? __switch_to+0x1ac/0x320
Jun 19 22:45:50 localhost kernel: [<ffffffff8150d6e0>] ? thread_return+0x4e/0x76e
Jun 19 22:45:50 localhost kernel: [<ffffffff8151311e>] ? do_page_fault+0x3e/0xa0
Jun 19 22:45:50 localhost kernel: [<ffffffff815104d5>] ? page_fault+0x25/0x30
Jun 19 22:45:50 localhost kernel: Task in /my-group killed as a result of limit of /my-group
Jun 19 22:45:50 localhost kernel: memory: usage 102324kB, limit 102400kB, failcnt 179657
Jun 19 22:45:50 localhost kernel: memory+swap: usage 512000kB, limit 512000kB, failcnt 27
..(以下略)

cgroupの制限でkillされたログが出ている。