cgroupのメモリ制御機能を使ってみた

# 目的
cgroupによって、特定プロセスのメモリをどのように制御できるのかをご紹介します。
今回はcgroup v1を使います。
OSはCentOS7です。

# cgroupとは
システムのプロセスをグループ化したり、そのグループ化したプロセスにどれくらいCPUやメモリなどのリソースを割り当てるかを制御するLinuxカーネルの機能です。
cgroupを使うことで、あるプロセスだけがシステムのリソースを使い切ってしまうのを防げます。 

cgroupについてより詳しく知りたい方は以下を参照ください。
https://docs.redhat.com/ja/documentation/red_hat_enterprise_linux/6/html/resource_management_guide/ch01

# cgroupによるメモリ制御を試す
以下の流れで進めていきます。
1. テスト用のグループ作成
1. 使用できるメモリの最大値を設定
1. メモリに負荷をかけるプログラムを実行
1. テストグループに3で実行したプロセスを追加

### 1. テスト用のグループ作成
cgroupではプロセスをグループ化してリソースを制御できます。
デフォルトでは/sys/fs/cgroup/memoryにメモリを制御するプログラム（サブシステム）がマウントされていて、その配下にディレクトリを作成することでグループを新たに作成できます。
今回はtestというグループを作成します。
```
# mkdir /sys/fs/cgroup/memory/test
```

ディレクトリを作成すると、そのグループを制御するために必要なファイルが自動で作成されます。
```
# cd /sys/fs/cgroup/memory/test
# ls
cgroup.clone_children       memory.kmem.max_usage_in_bytes      memory.limit_in_bytes            memory.numa_stat            memory.use_hierarchy
cgroup.event_control        memory.kmem.slabinfo                memory.max_usage_in_bytes        memory.oom_control          notify_on_release
cgroup.procs                memory.kmem.tcp.failcnt             memory.memsw.failcnt             memory.pressure_level       tasks
memory.failcnt              memory.kmem.tcp.limit_in_bytes      memory.memsw.limit_in_bytes      memory.soft_limit_in_bytes
memory.force_empty          memory.kmem.tcp.max_usage_in_bytes  memory.memsw.max_usage_in_bytes  memory.stat
memory.kmem.failcnt         memory.kmem.tcp.usage_in_bytes      memory.memsw.usage_in_bytes      memory.swappiness
memory.kmem.limit_in_bytes  memory.kmem.usage_in_bytes          memory.move_charge_at_immigrate  memory.usage_in_bytes
```

各ファイルの用途を知りたい方は以下を参照ください。
https://docs.redhat.com/ja/documentation/red_hat_enterprise_linux/6/html/resource_management_guide/sec-memory

### 2. 使用できるメモリの最大値を設定
testグループで使用できるメモリの最大値を設定するには、「memory.limit_in_bytes」ファイルに値を書き込みます。
今回は10Mを設定してみます。
```
# echo 10M > memory.limit_in_bytes
# cat memory.limit_in_bytes
10485760
```

### 3. メモリに負荷をかけるプログラムを実行
メモリを10M以上使うプログラムを実行したかったので、stressコマンドを使いました。
オプションの意味は以下の通りです。
| オプション | 意味 |
|:-:|:-:|
|  -m N | メモリを消費するN個のVMを生成する  |
| --vm-bytes  | 1VMごとのサイズを指定する  |

stressコマンド実行後、topコマンドでPIDとメモリ使用量を確認します。
```
# stress -m 1 --vm-bytes 512M &
[1] 10853
# top  | egrep 'COMMAND|stress'
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
10854 root      20   0  531604  40956    128 R 100.0  2.0   1:50.60 stress
10854 root      20   0  531604 456700    128 R  99.4 22.3   1:54.14 stress
10854 root      20   0  531604 227324    128 R 100.0 11.1   1:58.14 stress
10854 root      20   0  531604 108540    128 R  99.8  5.3   2:02.13 stress
10854 root      20   0  531604  38908    128 R  99.7  1.9   2:05.14 stress
```
PIDは10854、メモリの使用量は、最低でも10M以上は使っているようです。

### 4. テストグループに3で実行したプロセスを追加
グループにプロセスを追加するには、/sys/fs/cgroup/memory/test配下の「tasks」ファイルにPIDを追加します。グループにプロセスを追加するとそのグループに設定されているメモリの最大値などが適用されることになります。
では、実際にPIDを追加して、当該プロセスがどうなるか確認してみます。

```
# echo 10854 > tasks
# stress: FAIL: [10853] (415) <-- worker 10854 got signal 9
stress: WARN: [10853] (417) now reaping child worker processes
stress: FAIL: [10853] (451) failed run completed in 30s

[1]+  終了 1                stress -m 1 --vm-bytes 512M
```
testグループに設定していた最大値10Mよりも多く使用していたため、
OOM Killerによって強制終了されました。

# まとめ
今回はcgroup v1でメモリの制御を試してみました。
メモリを制御するには、メモリ制御用のプログラム（サブシステム）がマウントされているディレクトリ配下に新たにグループを作成し、その配下に作成されたファイルに最大値を設定したり、プロセスを追加することで制御できることがわかりました。

# 参照
https://gihyo.jp/admin/serial/01/linux_containers/0003