【Dockerの最新機能を使ってみよう】Dockerコンテナで利用できるリソースや権限を制限する

 新たなサーバー環境構築ツールとして普及が始まっているDockerは、その開発も積極的に行われている。そこで本連載記事では、4回に渡って最近Dockerに実装された新機能について紹介していく。今回は、コンテナに割り当てるリソースの制限やDockerが持つ権限分離機構について紹介する。

Dockerが備えるリソース制限機構

 仮想化技術を利用するメリットの1つに、柔軟にリソースを管理できる点がある。Dockerでは古くからコンテナに割り当てるCPUリソースやメモリ容量を指定する機能があったが、Docker 1.6以降では割り当てるCPUリソースやメモリをより詳細に指定できるようになった。また、Docker 1.10ではブロックI/Oに関する制限も設定できるようになっている。以下では、これらコンテナに向けたリソース制限機能について紹介する。

稼働中コンテナのリソース使用状況を確認する

 リソースの制限について説明する前に、まずは各コンテナがどの程度のリソースを使用しているのかを確認する方法を紹介しておこう。こういった情報はコンテナ内で各種計測コマンドを実行しても確認できるのだが、Dockerにはコンテナのリソース使用状況を確認するための「docker stats」コマンドが用意されており、これを利用すると容易に各コンテナの状況を確認できる。

# docker stats <コンテナ名もしくはコンテナID>

 対象とするコンテナ名もしくはコンテナIDは複数を指定可能だ。また、Docker 1.10以降ではコンテナ名もしくはコンテナID指定を省略した場合、ホスト上のすべてのコンテナに関する状況が表示される。

↓コンテナを実行
# docker run -td busybox
ac9206ea6439f2043b76d00745eeed73f7937f29c449c256fd14b73ecb09ea2f
↓コンテナのリソース使用状況を確認
# docker stats
CONTAINER           CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O
ac9206ea6439        0.00%               57.34 kB / 1.041 GB   0.01%               648 B / 648 B       0 B / 0 B

 なお、「docker stats」コマンドで確認できるのはCPUおよびメモリ、ネットワークI/O、ブロックI/O(Docker 1.9以降)だ。

CPU使用量の制限

 Dockerではコンテナを起動する「docker run」コマンドに特定のオプションを与えることで、起動するコンテナが利用できるリソースを制限できる。CPU関連のリソースを制限するオプションとしては、表1のものが用意されている。

表1 CPU関連のリソース制限オプション
オプション説明備考
–cpuset-cpus使用するCPUのIDDocker 1.5以前は「–cpuset」
–cpuset-mems使用するメモリノードのIDNUMA環境向け。Docker 1.7以降
–cpu-sharesCPU配分の際の重みデフォルトは1024
–cpu-periodスケジューリング期間マイクロ秒で指定。Docker 1.7以降
–cpu-quotaスケジューリング期間中のうちコンテナに割り当てる期間マイクロ秒で指定。Docker 1.7以降

 このうち、Dockerで古くからサポートされているのが「–cpu-shares」オプションだ。これはそのホスト上で稼動しているコンテナに割り当てるCPUリソースを相対値で指定するもので、デフォルト値は1024となっている。コンテナに割り振られるCPUリソースは、次のように計算できる。

(<対象のコンテナに対し指定された--cpu-sharesの値>÷<各コンテナに対し指定された--cpu-sharesの値の総計>)×<利用できるCPUリソースの総量>

 ホスト上のすべてのコンテナが同じ値(たとえばデフォルト値の1024)の場合、各コンテナには同じだけのCPUリソースが割り当てられる(図1)。

図1 「--cpu-shares」オプション指定によるCPUリソース割り当て
図1 「–cpu-shares」オプション指定によるCPUリソース割り当て

 また、たとえば4つのコンテナがあり、その1つに「2048」、1つに「512」、残り2つがデフォルトの「1024」だった場合、利用できるCPUリソースはそれぞれ4/9、1/9、2/9、2/9となる。

 なお、「–cpu-shares」での指定は、あくまですべてのコンテナがフルにCPUリソースを消費しようとした際の割り当てを指定するものであり、あるコンテナがアイドル状態である場合、その分のCPUリソースは他のコンテナに配分されるようになっている。

 コンテナが使用するCPUコアを指定することも可能だ。これは、「–cpuset-cpus」オプションで指定できる。なお、バージョン1.5以前はこのオプションは「–cpuset」という名称だった。このオプションではそのコンテナが使用するCPUコアのIDを数値で指定する。たとえばIDが0のコアのみを使用するよう指定するには、次のようにオプションを指定すれば良い。

--cpuset-cpus=0

 カンマや「-」で複数のコアを指定することも可能だ。たとえばIDが0から2までのコアを指定するには次のようにする。

--cpuset-cpus=0-2

 また、NUMAアーキテクチャを持つマシンでは同様の形式で使用するメモリノードを指定できる「–cpuset-mems」オプションも用意されている。

 「–cpu-shares」オプションはコンテナが使用するCPUリソースを相対的に配分するためのものだが、Docker 1.7以降ではスケジューラ(Completely Fair Scheduler、CFS)単位でのCPUリソース制限を行う「–cpu-period」および「–cpu-quota」オプションが追加された。こちらでは「–cpu-period」で指定した時間のうち「–cpu-quota」で指定した時間までコンテナにCPUを割り当てる、という設定が行える。

 たとえば、1秒(1000000マイクロ秒)当たり最大0.2秒(200000マイクロ秒)間1つのCPUを利用できるようにするには、次のように指定する。

--cpu-period=1000000 --cpu-quota=200000

メモリの制限

 続いて、メモリ関連のオプションを紹介しよう(表2)。

表2 メモリ関連のリソース制限オプション
オプション説明備考
-m、–memoryメモリ容量を制限する
–memory-reservationメモリ容量を緩く制限するDocker 1.9以降
–kernel-memoryカーネルが利用できるメモリ量を制限するDocker 1.9以降
–memory-swapメモリ+SWAPの総量を制限するDocker 1.5以降
–memory-swappiness=コンテナ内でのスワップメモリ利用頻度を調整するDocker 1.8以降
–oom-kill-disableOOM Killerを無効化するDocker 1.7以降
–oom-score-adjコンテナ自体のOOM Killer優先度を調整するDocker 1.10以降
–shm-size/dev/shmに割り当てる容量を指定するDocker 1.10以降

 「–memory」(もしくは「-m」)オプションでは、そのコンテナが利用できる最大メモリ量を指定できる。指定の際は「k」や「m」、「g」といった単位および「b」(バイト)も利用可能だ。たとえばコンテナが利用できるメモリ量を最大256MBに制限するには、以下のオプションを指定してコンテナを起動すれば良い。

--memory=256mb

 なお、実際の制限値はOSのページサイズの倍数に丸められるため、指定した値がそのまま制限値になるわけでは無い。たとえば上記のように「–memory=256mb」を指定したコンテナについて「docker stats」コマンドでリソース使用状況を確認すると、メモリのリミット(MEM LIMIT)が「268.4MB」となっていることが分かる。

# docker stats
CONTAINER           CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O
1f5816cebef4        0.00%               61.44 kB / 268.4 MB   0.02%               508 B / 508 B       0 B / 0 B

 Docker 1.9ではこれに加え、空きメモリに余裕がない場合にのみ制限が適用される「–memory-reservation」オプションや、コンテナにおいてカーネルが使用するメモリ量を制限する「–kernel-memory」オプションも導入されている。これらオプションも「–memory」オプションと同様の形式でサイズを指定できる。

 コンテナが利用するスワップメモリの量も制限可能だ。これは「–memory-swap」オプションで指定できる。なお、このオプションで指定する値はメモリ量とスワップメモリ量の合計になる点に注意したい。また、このオプションを利用する場合は必ず「–memory」オプションも同時に指定しておく必要があり、かつ「–memory」オプションよりも大きい値を指定する必要がある。なお、デフォルトではメモリ量と同量がスワップメモリとして利用可能となる。

 Linuxカーネルにはメモリのスワップ頻度を調整するパラメータであるswappiness(/proc/sys/vm/swappiness)があるが、Docker 1.8以降ではコンテナごとにこのパラメータを変更できるようになった。これは「–memory-swappiness」オプションで指定できる。Linuxカーネルでの指定の場合と同様、指定できる値は0から100までで、デフォルトは60だ。数値が大きいほど、頻繁にスワップを行うようになる。

 Linuxでは利用できるメモリが少なくなるとその中身をスワップメモリに追い出して利用できるメモリを確保しようとするが、たとえばスワップメモリを使い果たすといった状況となってそれでもメモリが不足した場合、OOM Killer(Out of Memory Killer)というシステムによって稼働中のプロセスが強制終了され、カーネルの稼動に必要なメモリを確保しようとする。Docker 1.7では、このOOM Killerを無効化する「–oom-kill-disable」オプションも追加された。この場合、空きメモリ以上のメモリを要求した処理はメモリが確保されるまで一時停止するようになる。

 OOM Killerでは、強制終了させるプロセスを「oom_score_adj」という値を使って管理している。Docker 1.10では、コンテナ内プロセスのoom_score_adjの値を指定する「–oom-score-adj」オプションも追加された。指定できる値は-1000から1000の間で、値が小さいほど強制終了の対象になりやすい。また、-1000を設定すると強制終了の対象外となる。これを利用して、ホストの空きメモリ容量が少なくなった場合でもコンテナを強制終了されにくくすることが可能になる。

 sのほか、同じくDocker 1.10ではコンテナ内で/dev/shmに割り当てる容量を指定する「–shm-size」オプションも追加された。/dev/shmはLinuxカーネルによって提供されるRAMディスクのマウント先で、デフォルト値は64MBだ。

I/O関連の制限

 DockerではCPUやメモリ以外のリソースに対する制限は当初実装されていなかったが、最近になってブロックI/Oに関する制限やデバイス単位での制限を行える機能が追加されている(表3)。

表3 I/O関連のリソース制限オプション
オプション説明備考
–blkio-weightブロックI/Oデバイスに対する優先度。10~1000の間で指定Docker 1.7以降
–blkio-weight-device=[]指定したブロックI/Oデバイスに対する優先度。10~1000の間で指定Docker 1.10以降
–device-read-bps指定したブロックI/Oデバイスに対する読み込みアクセス上限をバイト/秒で設定Docker 1.10以降
–device-read-iops指定したブロックI/Oデバイスに対する読み込みアクセス上限をIO数/秒で設定Docker 1.10以降
–device-write-bps指定したブロックI/Oデバイスに対する書き込みアクセス上限をバイト/秒で設定Docker 1.10以降
–device-write-iops指定したブロックI/Oデバイスに対する書き込みアクセス上限をIO数/秒で設定Docker 1.10以降

 まずDocker 1.7で追加されたのが、ブロックデバイスに対するI/Oを調整する「–blkio-weight」オプションだ。このオプションでは、10から1000の範囲でそのコンテナに対するI/Oの優先度を指定できる。複数のコンテナが同時にI/Oを行っている際、優先度が大きいコンテナほどより多くのI/Oを行える。Docker 1.10ではデバイスごとにコンテナのI/O優先度を指定できる「–blkio-weight-device」オプションも追加されている。

 同じくDocker 1.10では、指定したデバイスに対し毎秒当たりのバイト数という単位でI/Oを制限できる「–device-read-bps」および「–device-write-bps」オプションと、毎秒当たりのI/O数という単位でI/Oを制限できる「–device-read-iops」および「–device-write-iops」オプションが追加された。これらオプションは、「<デバイス名>:<上限>」といった形式で対象とするデバイスおよび上限を指定できる。

 たとえば、fedora 23+Docker 1.10.3のデフォルト設定ではコンテナのファイルシステムが/dev/mapper/fedora-rootというデバイス上に作られる。このデバイスに対し書き込みを毎秒1MBまでに制限するには、以下のようにしてコンテナを起動すれば良い。

# docker run -ti --device-write-bps=/dev/mapper/fedora-root:1mb busybox

 このようにして起動したコンテナ内でddコマンドでファイルシステムへの書き込み速度を測定したところ、毎秒928.5KBという結果となった。

/ # dd bs=1M count=10 if=/dev/zero of=/test conv=fsync
10+0 records in
10+0 records out
10485760 bytes (10.0MB) copied, 11.029001 seconds, 928.5KB/s

 同じ環境で「–device-write-bps=」オプションを指定せずに起動したコンテナで同様の操作を行った場合は毎秒142.4MBという結果になっており、「–device-write-bps=」オプションによって書き込みが毎秒1MB未満に制限されていることが分かる。

/ # dd bs=1M count=10 if=/dev/zero of=/test conv=fsync
10+0 records in
10+0 records out
10485760 bytes (10.0MB) copied, 0.070217 seconds, 142.4MB/s

 Docker 1.6ではコンテナ内でのulimit設定を行う「–ulimit」オプションも追加されている。このオプションでは、LinuxのPAMが利用するlimits.confファイルに似た書式でコンテナが利用するリソースを制限できる。ここで指定できる値についてはlimits.confのmanページ(「man limits.conf」コマンドで確認できる)で確認できるのでそちらを参照してほしい。たとえばファイルディスクリプタの最大数を2048に設定するには、以下のように指定する。

--ulimit="nofile=2048"

稼働中コンテナに対するリソース制限の追加/変更

 Docker 1.10では新たに「docker update」コマンドが追加され、コンテナの起動時でリソース制限を設定するだけでなく、稼働中のコンテナに対してもリソース制限を追加したり、その値を変更することが可能となった。ただし、すべての制限を追加/変更できるわけではなく、設定できるのは表4のものとなる。

表4 「docker update」コマンドで設定できるリソース制限
オプション説明
–blkio-weightブロックI/Oデバイスに対する優先度。10~1000の間で指定
–cpu-sharesCPU配分の際の重み。デフォルトは1024
–cpu-periodスケジューリング期間。マイクロ秒で指定
–cpu-quotaスケジューリング期間中のうちコンテナに割り当てる期間。マイクロ秒で指定
–cpuset-cpus使用するCPUのID
–cpuset-mems使用するメモリノードのID
–kernel-memoryカーネルが利用できる割り当てるメモリ量を制限する
-m、–memoryメモリ容量を制限する
–memory-reservationメモリ容量を緩やかに制限する
–memory-swapメモリ+SWAPの総量を制限する