Dockerコンテナのリソース制限機能を使った実践的な体験談 その2

このチュートリアルでは、**Alibaba Cloud** ECS上で**Dockerコンテナ**のリソース制限機能を利用した実践的な体験ができます。

#他のコンテナに比例するcpu-shares
このテストでは、CPUシェアは他のコンテナに比例します。デフォルト値の1024は本質的な意味を持ちません。

すべてのコンテナのCPU占有率が4の場合、すべてのコンテナでCPU時間を均等に共有します。

これは、すべてのコンテナがCPUシェア=1024の場合、すべてのコンテナがCPU時間を均等に共有しているのと同じです。

実行します。

```
docker container run -d --cpu-shares=4 --name mycpu1024a alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=100 | md5sum'
docker container run -d --cpu-shares=4 --name mycpu1024b alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=100 | md5sum'
docker container run -d --cpu-shares=4 --name mycpu1024c alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=100 | md5sum'
```

ログを調査します。

```
docker logs mycpu1024a
docker logs mycpu1024b
docker logs mycpu1024c
```

コンテナをpruneして終了です。

```
docker container prune -f 
```

彼らはまだすべて同じ時間を実行したことに注意してください。4/1024が遅くなることはありませんでした。

#cpu-share：CPU サイクルが制限されている場合のみ有効
cpu-shareは CPU サイクルが制限されている場合にのみ適用されます。

他のコンテナが稼働していない状態で、1つのコンテナのcpu-shareを定義することは無意味です。

```
docker container run -d --cpu-shares=4 --name mycpu1024a alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=100 | md5sum'

docker logs mycpu1024a
```

```
real    0m 12.67s
user    0m 0.00s
sys     0m 12.27s
```

今すぐ共有を4000に増やして再実行してください - 参照 - ランタイムの違いはありません。

1つのコンテナが利用可能なすべてのCPU時間を使用しています。

```
docker container run -d --cpu-shares=4000 --name mycpu1024a alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=100 | md5sum'
```

この1つのコンテナをプルーニングして、これで終わりです。

```
docker container prune -f 
```

#--cpus= コンテナが使用できるCPUリソースの量を定義する
>コンテナが利用可能なすべてのCPUリソースのうち、どれだけのCPUリソースを使用できるかを指定します。例えば、ホストマシンに2つのCPUがあり、--cpus="1.5 "と設定した場合、コンテナは最大でもCPUの1/2が保証されます。

以下のコマンドで使用している --CPUs の値の範囲に注意してください。実行してみてください。

```
docker container run -d --cpus=2 --name mycpu2 alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=50 | md5sum'
docker container run -d --cpus=1 --name mycpu1 alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=50 | md5sum'
docker container run -d --cpus=.5 --name mycpu.5 alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=50 | md5sum'
docker container run -d --cpus=.25 --name mycpu.25 alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=20 | md5sum'
docker container run -d --cpus=.1 --name mycpu.1 alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=10 | md5sum'
```

dockerの統計を調査します。

```
docker stats
```

期待される出力

```
CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
843bea7263fb        mycpu2              57.69%              1.258MiB / 985.2MiB   0.13%               578B / 0B           1.33MB / 0B         0
186ba15b8258        mycpu1              55.85%              1.25MiB / 985.2MiB    0.13%               578B / 0B           1.33MB / 0B         0
3bcc26eab1ac        mycpu.5             46.60%              1.262MiB / 985.2MiB   0.13%               578B / 0B           1.33MB / 0B         0
79d7d7e3c38c        mycpu.25            25.43%              1.262MiB / 985.2MiB   0.13%               508B / 0B           1.33MB / 0B         0
b4ba5503a048        mycpu.1             9.76%               1.328MiB / 985.2MiB   0.13%               508B / 0B           1.33MB / 0B         0
```

mycpu.1, mycpu.25, mycpu.5は完全に制限が適用されていることを示しています。

しかし、mycpu1とmycpu2は100 + 200 %の追加CPUを持っていません。したがって、これらの設定は無視され、残りのCPU時間を均等に共有します。

#--cpus CPUの数
cpus設定はコンテナが使用するCPUの数を定義します。

DockerやLinuxディストロではCPUは次のように定義されます。

CPUs = Threads per core X cores per socket X sockets

CPUは物理的なCPUではありません。

私のサーバーのCPU数を調べてみましょう。

```
lscpu | head -n 10
```

```
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    1
Core(s) per socket:    2
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
```

不要な情報を削除しました。

```
lscpu | head -n 10

CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    1
Core(s) per socket:    2
Socket(s):             1
```

CPUs = Threads per core X cores per socket X sockets

ＣＰＵ＝１×２×１＝２ＣＰＵ

で確認してください。

```
grep -E 'processor|core id' /proc/cpuinfo
```

2 core id = 2 cores per socket
2 processors = 2 cpus

```
processor       : 0
core id         : 0
processor       : 1
core id         : 1
```

OK このサーバーは2つのCPUを持っています。あなたのサーバは異なるでしょうから、以下のテストを行う際にはその点を考慮してください。

cpus設定はコンテナが使用するCPUの数を定義します。

ここでは両方のCPUを使用し、1つだけ、1/2、1/4のCPUを使用し、CPU負荷の高いワークロードのためのランタイムを記録してみましょう。

注意: --cpus=2

```
docker container run --cpus=2 --name mycpu alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=30 | md5sum'
```

期待される出力

```
real    0m 3.61s
user    0m 0.00s
sys     0m 3.50s
```

比較するものが何もありません。他のテストを実行してみましょう

```
docker container prune -f
```

注意:--cpus=1

```
docker container run --cpus=1 --name mycpu alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=30 | md5sum'
real    0m 3.54s
user    0m 0.00s
sys     0m 3.37s
```

```
docker container prune -f
```

注意： --cpus=.5

```
docker container run --cpus=.5 --name mycpu alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=30 | md5sum'
real    0m 9.97s
user    0m 0.00s
sys     0m 4.78s
```

```
docker container prune -f
```

注意： --cpus=.25

```
docker container run --rm --cpus=.25 --name mycpu alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=30 | md5sum'
real    0m 19.55s
user    0m 0.00s
sys     0m 4.69s
```

--cpus=2 realtime. 3.6秒
--cpus=1 realtime. 3.5秒
--cpus=.5 realtime: 9.9秒
--cpus=.25 realtime: 19.5秒

私たちの単純なベンチマークでは、2つのCPUを同時に使用することは効果的ではありません。

半分のCPUは2倍遅く、4分の1のCPUは4倍遅く動作します。

CPUsの設定は有効です。コンテナ内のアプリケーションがマルチスレッド化できない場合や、1つ以上のCPUを効果的に使用できない場合は、1つのCPUだけを割り当ててください。

#--CPU-period と --CPU-quota
廃止されたオプションです。Docker 1.13以降を使用している場合は、代わりに--cpusを使用してください。

--cpu-period CPU CFS(Completely Fair Scheduler)の期間を制限する
--cpu-quota CPU CFS (Completely Fair Scheduler) のクォータを制限します。

上記の演習では、--CPUの設定がいかに簡単にできるかを明確に示しています。

#--cpuset-cpus 実行を許可するCPU (0-3, 0,1)
>--cpuset-cpus - 実行を許可するCPU (0-3, 0, 1)

残念ながら私のサーバーには2つのCPUしかなく、1つ以上のCPUを使っても効果がないことは先ほど見ました。

サーバーに複数のCPUが搭載されている場合、-cpuset設定のより興味深い組み合わせを実行できますが、これは役に立たないでしょう：この特定のベンチマークは1つのスレッドしか使用しません。

このチュートリアルの後半では、スレッド数を指定できる sysbench (実際のベンチマークツール) を使ったテストがあります。

私の結果は以下の通りです: 2つのCPUを使っても違いはありません。

```
docker container run --rm --cpuset-cpus=0,1 --name mycpu alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=30 | md5sum'

Expected output : 

real    0m 3.44s
user    0m 0.00s
sys     0m 3.35s
```

```
docker container run --rm --cpuset-cpus=0 --name mycpu alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=30 | md5sum'

Expected output : 

real    0m 4.15s
user    0m 0.00s
sys     0m 4.00s
```

```
docker container run --rm --cpuset-cpus=1 --name mycpu alpine:3.8 /bin/sh -c 'time dd if=/dev/urandom bs=1M count=30 | md5sum'

Expected output : 

real    0m 3.40s
user    0m 0.00s
sys     0m 3.28s
```

#実際のベンチマークアプリケーションを使用したテストコンテナの限界
上記のテストはすべてクイックハックを使用しています。

コンテナのリソース制限を適切にテストするには、実際の Linux ベンチアプリケーションが必要です。

私はCentOSを使い慣れているので、ベンチコンテナのベースとして使用します。どちらのベンチアプリケーションもDebian / Ubuntuでも利用可能です。yumのインストールをapt-getのインストールに変換すれば、簡単に同じ結果が得られます。

2つのベンチアプリケーションをコンテナにインストールする必要があります。最良の方法は、これらのアプリケーションを含むイメージを構築することです。

そのため、dockerbenchディレクトリを作成します。

```
mkdir dockerbench
cd dockerbench

nn Dockerfile
```

```
FROM centos:7

RUN set -x \
   && yum -y install https://www.percona.com/redir/downloads/percona-release/redhat/0.0-1/percona-release-0.0-1.x86_64.rpm \
   && yum -y install sysbench \
   
  && curl http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -o epel-release-latest-7.noarch.rpm \
  && rpm -ivh epel-release-latest-7.noarch.rpm \
  && yum -y install stress   
```

最初のインストールでは、sysbench のホームである percona yum repo を追加します。

その後、Yum は sysbench をインストールします。

curlはEPELのyumレポを追加します。

そして、Yum は stress をインストールします。

私たちのベンチのイメージを構築します。インターネットでの yum ダウンロード + yum 依存関係の解決と他の通常の活動に基づいて、約1分かかります。

もしCentOS 7のイメージをダウンロードしていない場合は、さらに1分かかるかもしれません。

```
docker build --tag centos:bench --file Dockerfile  .
```

これでCentOSのベンチイメージができたので、繰り返し使えるようになりました。

docker run -it --rm centos:bench /bin/sh

#--cpus Sysbenchツールでテスト
構文:

>sysbench --threads=2 --events=4 --cpu-max-prime=800500 —>verbosity=0 cpu run

- --threads=2 ... 2つのスレッドを実行することで、2つのCPUと1つのCPUを比較することができます。
- --events=4 ... 4回実行
- --cpu-max-prime=800500 ... 800500までの素数を計算します。
- --verbosity=0 ... 詳細な出力を表示しません。
- cpu run ... CPUという名前のテストを実行します。
実験を経て、私は10年前のコンピュータで800500がテストを素早く実行するための良い値であると判断しました。CPUマーク700。0桁が多いと読みにくいので、そこに5を入れてみました。

2つのCPU:

```
docker run -it --rm --name mybench  --cpus 2 centos:bench /bin/sh

sh-4.2# time sysbench --threads=2 --events=4 --cpu-max-prime=800500 --verbosity=0 cpu run

real    0m1.952s
user    0m3.803s
sys     0m0.029s
sh-4.2#
sh-4.2# exit
exit
```

Realはウォールクロック時間-sysbenchの開始から終了までの時間：1.9秒。

Userはsysbench内のユーザモードコード(カーネル外)で使用したCPU時間です。2台のCPUを使用しています：それぞれ1.9秒のCPU時間を使用していますので、合計のユーザ時間はそれぞれのCPUの時間を足したものです。

経過したウォールクロック時間は1.9秒です。2つのCPUが同時に/同時に作業していたので、それらの合計時間がユーザー時間として表示されます。

Sysはカーネルがシステムコールを行っているCPUの時間です。

1つのCPU:

```
docker run -it --rm --name mybench  --cpus 1 centos:bench /bin/sh

sh-4.2# time sysbench --threads=2 --events=4 --cpu-max-prime=800500 --verbosity=0 cpu run

real    0m4.686s
user    0m4.678s
sys     0m0.026s
sh-4.2#
sh-4.2# exit
exit
```

これらの比較を実行するより便利な方法は、docker の run line で bench コマンドを実行することです。

この方法で1つのCPUを再実行してみましょう。

```
docker run -it --rm --name mybench  --cpus 1 centos:bench /bin/sh -c 'time sysbench --threads=2 --events=4 --cpu-max-prime=800500 --verbosity=0 cpu run'

real    0m4.659s
user    0m4.649s
sys     0m0.028s
```

この方法で半分のCPUを実行してみましょう。

```
docker run -it --rm --name mybench  --cpus .5 centos:bench /bin/sh -c 'time sysbench --threads=2 --events=4 --cpu-max-prime=800500 --verbosity=0 cpu run'

real    0m10.506s
user    0m5.221s
sys     0m0.035s
```

結果は完璧に筋が通っています。

- 2 CPUs : real 0m1.952s
- 1 CPU : real 0m4.659s
- 5 CPUs : real 0m10.506s
私たちのイメージにあるsysbenchを使えば、このようなテストを非常に簡単かつ迅速に行うことができます。ほんの数秒でDockerコンテナのCPU使用量を制限することができます。

率直に言って、.5CPUテストのために10.506秒も待つのは長すぎます。

職場の開発サーバでこれを行った場合、CPU負荷は1分以上の経過で劇的に変化します。開発者は 2 秒間の 2CPU 実行中にコンパイルを行い、サーバは 5 秒間の 1CPU 実行中に CPU が静かになる可能性があり、数値は完全に歪んでしまいます。

このような状況の変化に対して、ある程度ロバストなアプローチが必要です。すべてのテストは、可能な限り迅速に、そして直接次から次へと実行しなければなりません。

それを試してみましょう。最大素数を100倍に減らします。

これら3つの命令をすべてカットアンドペーストして、一度に行って、結果を観察します。

```
docker run -it --rm --name mybench  --cpus 2 centos:bench /bin/sh -c 'time sysbench --threads=2 --events=4 --cpu-max-prime=8005 --verbosity=0 cpu run'
docker run -it --rm --name mybench  --cpus .5 centos:bench /bin/sh -c 'time sysbench --threads=2 --events=4 --cpu-max-prime=8005 --verbosity=0 cpu run'
docker run -it --rm --name mybench  --cpus 1 centos:bench /bin/sh -c 'time sysbench --threads=2 --events=4 --cpu-max-prime=8005 --verbosity=0 cpu run'
```

期待される出力

```
2 CPUs
real    0m0.049s
user    0m0.016s
sys     0m0.021s

1 CPUs 
real    0m0.049s
user    0m0.019s
sys     0m0.020s

.5 CPU 
real    0m0.051s
user    0m0.020s
sys     0m0.019s
```

ベンチマーク起動時のオーバーヘッドがウォールクロックの実時間を圧倒しています。テストは絶望的に短すぎます。

3回の個人的な実験の後、元のワークロードを10倍に減らせば完璧だと思います。

```
docker run -it --rm --name mybench  --cpus 2 centos:bench /bin/sh -c 'time sysbench --threads=2 --events=4 --cpu-max-prime=100500 --verbosity=0 cpu run'
docker run -it --rm --name mybench  --cpus 1 centos:bench /bin/sh -c 'time sysbench --threads=2 --events=4 --cpu-max-prime=100500 --verbosity=0 cpu run'
docker run -it --rm --name mybench  --cpus .5 centos:bench /bin/sh -c 'time sysbench --threads=2 --events=4 --cpu-max-prime=100500 --verbosity=0 cpu run'
```

(中の5は000000の長い文字列を読みやすくするためのものです)

```
2 CPUs
real    0m0.152s
user    0m0.225s
sys     0m0.015s

1 CPU
real    0m0.277s
user    0m0.279s
sys     0m0.019s

.5 CPU
real    0m0.615s
user    0m0.290s
sys     0m0.024s
```

比率は完璧です。全体的な実行時間は 1 秒未満で、開発サーバーの CPU 負荷の変化による影響を最小限に抑えています。

ここで説明されていることを理解するために、お使いのサーバーで数分遊んでみてください。

run コマンドで --rm を使用したことに注意してください。これは /bin/sh でコンテナに渡されたコマンドを終了した後、コンテナを自動的に削除します。

*アリババクラウドは日本に２つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
[アリババクラウドジャパン公式ページ](https://www.alibabacloud.com/ja)*