Dockerのメモリ制限に関するメモ

`docker run`実行時にコンテナのメモリ上限を設定できます。久しぶりに触ってみたらコンテナのメモリ設定のオプションがいくつか追加されていたのでメモ。Dockerのバージョンは1.12.1です。

|オプション|説明|
|:----:|:----:|
| `-m , --memory=""` | メモリの上限（書式： <数値> [<単位>] 、単位は b、k、m、g のいずれか） |
| `--memory-swap=""` | 合計メモリの上限（メモリ＋スワップ、書式： <数値> [<単位>] 、単位は b、k、m、g のいずれか） |
| `--memory-reservation=""` | メモリのソフト・リミット（書式： <数値> [<単位>] 、単位は b、k、m、g のいずれか） |
| `--kernel-memory=""` | カーネル・メモリの上限（書式： <数値> [<単位>] 、単位は b、k、m、g のいずれか） |
| `--oom-kill-disable=false` | コンテナを OOM killer による停止を無効化するかどうか指定 |
| `--memory-swappiness=""` | コンテナがメモリのスワップ度合いを調整。整数値の 0 ～ 100 で指定 |
引用:[Docker run リファレンス](http://docs.docker.jp/engine/reference/run.html#runtime-constraints-on-resources)

ここでは`--memory-reservation`と`--oom-kill-disable=false`、`--memory-swappiness`について調べました。

## 環境
* OSX 10.12
* Docker for MAC(docker 1.12.1)

## `--memory-reservation`
メモリのソフトリミットということで、ホストのメモリに余裕がある時はは制限はかからず、他に使用している時は制限がかかるということらしい。  

試してみました。  
コンテナ2つ起動し、1つは`--memory-reservation`を512MB、もうひとつは制限なしで設定します。Linuxの負荷ツールstressで以下のシナリオでメモリに負荷をかけます。  
前提：ホストのメモリは2GB
1. 両方のコンテナに1.25GBの負荷をかける
2. メモリ制限のないコンテナへの負荷を停止し、512MBのメモリ制限をしているコンテナのみに負荷をかける

1の状態ではメモリ制限したコンテナでは512MBしかメモリが使用されず、制限していないコンテナでは1.25GBのメモリを使用している状態になるはず。  
また2の状態では、ホストのメモリに空きができたので、メモリ制限したコンテナでも1.25GBのメモリが使用できるはず。  

それでは試していきます。

コンテナ起動&コンテナに`stress`インストール

```
$ docker run --name mem-no-limit -dit centos:7 /bin/bash
$ docker run --name mem-limit -dit --memory-reservation 512m centos:7 /bin/bash

$ docker exec mem-no-limit curl http://ftp.tu-chemnitz.de/pub/linux/dag/redhat/el7/en/x86_64/rpmforge/RPMS/stress-1.0.2-1.el7.rf.x86_64.rpm -o stress-1.0.2-1.el7.rf.x86_64.rpm
$ docker exec mem-no-limit yum install stress-1.0.2-1.el7.rf.x86_64.rpm -y 
$ docker exec mem-limit curl http://ftp.tu-chemnitz.de/pub/linux/dag/redhat/el7/en/x86_64/rpmforge/RPMS/stress-1.0.2-1.el7.rf.x86_64.rpm -o stress-1.0.2-1.el7.rf.x86_64.rpm
$ docker exec mem-limit yum install stress-1.0.2-1.el7.rf.x86_64.rpm -y 
```

1. 両方のコンテナに1.25GBの負荷をかける

```
$ docker exec -d mem-no-limit stress --vm 5 --vm-bytes 256M --vm-keep  
$ docker exec -d mem-limit stress --vm 5 --vm-bytes 256M --vm-keep  
$ docker stats -a

CONTAINER           CPU %               MEM USAGE / LIMIT       MEM %               NET I/O               BLOCK I/O             PIDS
7bc27e459b9d        67.66%              512.9 MiB / 1.953 GiB   25.65%              39.68 kB / 2.745 kB   4.17 GB / 839.2 MB    9
2544567cad9d        101.66%             1.312 GiB / 1.953 GiB   67.18%              41.02 kB / 2.745 kB   1.026 GB / 1.528 GB   9
```

おお、ちゃんと512MBで制限されてますね。  

続いてメモリ制限されてないコンテナの負荷を止めます。(コンテナの再起動してstreeプロセスをなくしてます。)
2. メモリ制限のないコンテナへの負荷を停止し、512MBのメモリ制限をしているコンテナのみに負荷をかける

```
$ docker restart memo-no-limit
$ docker stats -a

CONTAINER           CPU %               MEM USAGE / LIMIT       MEM %               NET I/O               BLOCK I/O             PIDS
7bc27e459b9d        196.75%             1.504 GiB / 1.953 GiB   77.02%              39.68 kB / 2.745 kB   3.934 GB / 818.1 MB   9
2544567cad9d        0.00%               420 KiB / 1.953 GiB     0.02%               41.02 kB / 2.745 kB   845.2 MB / 724.5 MB   2
````

メモリ制限してるコンテナでも`--memory-reservation`で設定した512MBを超えてメモリを使用してますね。

## `--oom-kill-disable`
Apacheのように子プロセスが生成されるようなミドルウェアではそんなに気になりませんが、OpenLDAPのようなslapdプロセス1つで動くようなものは、`--oom-kill-disable=true`を設定しないとアウト・オブ・メモリエラーが発生するとカーネルよって停止されてしまいます。`--oom-kill-disable=true`を設定しておけば強制的に停止されることはなくなります。ただし、これは`--memory`と一緒に設定しないと、ホストメモリを使い切ったときにシステム・プロセスを停止しなければならなくなります。  


メモリを128MBで制限し、`--oom-kill-disable`のあり/なしで2つのコンテナを作ります。（負荷ツールのインストールは省略してます。）

```
$ docker run --name oom-kill -dit --memory 128m centos:7 /bin/bash
$ docker run --name no-oom-kill -dit --memory 128m --oom-kill-disable=true centos:7 /bin/bash
```

まずは`--oom-kill-disable`なしのコンテナにメモリ上限以上のメモリ負荷をかけてみます。

```
$ docker exec oom-kill stress --vm 3 --vm-bytes 128M --vm-keep
stress: info: [27] dispatching hogs: 0 cpu, 0 io, 3 vm, 0 hdd
stress: FAIL: [27] (420) <-- worker 32 got signal 9
stress: WARN: [27] (422) now reaping child worker processes
stress: FAIL: [27] (456) failed run completed in 2s
```

プロセスがKillされました。

つぎは`--oom-kill-disable`のコンテナにメモリ上限以上のメモリ負荷をかけてみます。

```
$ docker exec no-oom-kill stress --vm 3 --vm-bytes 128M --vm-keep
stress: info: [27] dispatching hogs: 0 cpu, 0 io, 3 vm, 0 hdd
```

とりあえずプロセスは動き続けているようです。

```
$ docker stats -a
CONTAINER           CPU %               MEM USAGE / LIMIT     MEM %               NET I/O               BLOCK I/O             PIDS
fac173bff8f9        0.00%               127.7 MiB / 128 MiB   99.75%              40.2 kB / 2.745 kB    57.34 kB / 155.1 MB   5
556fae8a9b3e        0.00%               44 KiB / 128 MiB      0.03%               40.37 kB / 2.745 kB   360.4 kB / 155.2 MB   1
```

ただコンテナはメモリを使い切っているので、ビジー状態でなにも反応しません。

```
$ docker exec no-oom-kill ps aux
rpc error: code = 2 desc = containerd: container did not start before the specified timeout
```

##  `--memory-swappiness`

コンテナのカーネルは、アノニマス・ページ・メモリ上の何パーセントかをスワップ・アウトします。そこで`--memory-swappiness`を設定して、そのパーセンテージを制御できるようです。  0であれはアノニマス・ページ・メモリは無効、100であれば全ページがアノニマス・ページ・メモリ可能という設定になります。  
使い所はそんなに多くなさそうですが、パフォーマンスを優先して`-memory-swappiness=0`することはありそうです。

```
$ docker run -itd -memory-swappiness=0 centos:7 /bin/bash
```


## おわり
より柔軟にリソースを制限できるようになってますね。これらを駆使し、リソースをよりケチって高使用率でDockerを運用したいものです。