メモリ、CPU、GPU に対する実行時オプション

読む時間の目安: 5 分

デフォルトにおいてコンテナーには、リソースの利用に関して制限がありません。 したがってホストカーネルのスケジューラーが割り振るリソースを、その分だけ利用できます。 Docker には、コンテナーが利用するメモリや CPU をどれくらいにするかを制御する方法があります。 docker runコマンドにおいて実行時フラグを設定する方法です。 この節では、どのようなときにそういった制約を行うのか、そして制約によってどのような影響があるのかを説明します。

制約に関する機能を利用するには、カーネルがケーパビリティーをサポートしている必要があります。 サポートしているかどうかは、docker info コマンドを実行すればわかります。 利用しているカーネルにおいてケーパビリティーが無効になっていると、このコマンドの出力の最後に、以下のような出力が行われます。

WARNING: No swap limit support

これを有効にする方法は、各オペレーティングシステムのドキュメントを参照してください。 さらに詳しくはここで説明しています

メモリ

メモリ不足時のリスクへの理解

コンテナーがホストマシンのメモリを必要以上に消費することは避けなければなりません。 Linux ホストにおいて、重要なシステム関数を実行するだけの十分なメモリがないことをカーネルが検出した場合、OOME例外、つまりOut Of Memory Exceptionがスローされます。 そしてプロセスの停止を行いメモリを開放します。 Docker であろうが重要なアプリケーションであろうが、あらゆるプロセスが強制的に停止させられます。 停止させてはならないプロセスが停止してしまうと、システム全体を停止させる事態にもなりかねません。

Docker においては、デーモンに対しての OOM プライオリティ調整機能があります。 これによりメモリ不足のリスクを軽減し Docker デーモンが他のプロセスに比べて停止しにくいようにしています。 この OOM プライオリティの調整機能は、コンテナーにはありません。 したがって Docker デーモンや他のシステムプロセスが停止することよりも、単一のコンテナーが停止する可能性の方が高いことになります。 これは Docker が採用する安全策なので、無理に回避する方法を取らないでください。 Docker デーモンに対して、手動で--oom-score-adjに極端な負数を指定したり、コンテナーに対して--oom-kill-disableを指定したりするようなことはやめてください。

Linux カーネルの OOM 管理については Out of Memory Management を参照してください。

OOME に起因する不安定リスクを回避するには、以下の対応があります。

  • アプリケーションの本番環境への移行前に、アプリケーションがどのようにメモリを必要とするかをテストして理解すること。
  • アプリケーションが、一定のリソースがあればホスト上だけで動作することを確認すること。
  • これ以降に示すような、コンテナーのメモリ使用量を制限すること。
  • Docker ホスト上のスワップの設定に十分注意すること。 スワップはメモリに比べて、処理速度が遅く性能が劣ります。 ただしシステムメモリの不足を補うためのバッファを利用します。
  • コンテナーを サービス に変更する検討をすること。 そしてサービスレベルでの制約やノードラベルを利用することで、十分なメモリを有するホスト上でのみアプリケーションが動作するように検討すること。

コンテナーに対するメモリアクセスの制限

Docker では、ハードリミット(hard limit)により厳しくメモリを制限することができます。 コンテナーが利用するユーザーメモリ、あるいはシステムメモリを指定量以下に抑えます。 また緩い制限であるソフトリミット(soft limit)もあり、所定の条件下でない限りは、コンテナーが求めるメモリ使用を認めることができます。 所定の条件とはたとえば、ホスト上のメモリ不足やリソースコンフリクト発生をカーネルが検出したような場合です。 制限を指定するオプションを利用する場合には、単独で利用するか複数組み合わせて利用するかによって、その効果はさまざまです。

この制約オプションのほとんどは、正の整数を指定して、バイト、キロバイト、メガバイト、ギガバイトを表わすbkmgを後ろにつけます。

オプション 内容説明
-mまたは--memory= コンテナーに割り当てるメモリ最大使用量。このオプションを利用する場合、指定できる最小値は6m(6 メガバイト) です。つまり最低でも 6 メガバイトに設定することが必要です。That is, you must set the value to at least 6 megabytes.
--memory-swap* コンテナーにおいてディスクへのスワップを許容するメモリ容量。--memory-swapの詳細 を参照してください。
--memory-swappiness デフォルトにおいては、コンテナーによって利用されている匿名ページを一定の割合でスワップアウトすることができます。--memory-swappinessの設定では 0 から 100 までの設定を行って、その割合を調整します。--memory-swappinessの詳細 を参照してください。
--memory-reservation --memoryに比べてソフトリミットとして小さな値を設定します。Docker がホストマシン上のコンフリクトやメモリ不足を検出したときに採用されます。この--memory-reservationを指定する際には、これが優先的に採用されるように --memory よりも小さな値を設定します。これはソフトリミットであり、この設定値を越えない保証はないからです。
--kernel-memory コンテナーに割り当てるカーネルメモリの最大使用量。指定できる最小値は4mです。カーネルメモリはスワップされるものではないため、カーネルメモリ不足となったコンテナーは、ホストマシンのリソースに影響を及ぼすことになります。これはホストマシンにとっても、また他のコンテナーにとっても副作用を引き起こします。--kernel-memoryの詳細を参照してください。
--oom-kill-disable out-of-memory (OOM) エラーが発生すると、デフォルトでカーネルはコンテナー内のプロセスを停止させます。この動作を変更するには--oom-kill-disableオプションを指定します。これによってコンテナー上での OOM キラープロセスが無効になりますが、それは-m/--memoryオプションを同時に指定しているコンテナーに限定されます。-mフラグを設定していなかった場合は、ホストがメモリ不足となり、ホストシステムの他のプロセスを停止させてメモリ確保を行うことになります。

cgroups とメモリに関する全般的な情報は、メモリリソースコントローラー に関するドキュメントを参照してください。

--memory-swapの詳細

--memory-swapは、--memoryが同時に設定されている場合のみ、その意味をなす修正フラグです。 スワップを利用すれば、コンテナーにおいて要求されたメモリが超過して、利用可能な RAM を使い果たしたとしても、それをディスクに書き出すことになります。 ただしメモリのスワップが頻発すると、アプリケーションの性能は劣化します。

これを設定したときの結果は複雑です。

  • --memory-swapに正の整数が指定する場合は、--memory--memory-swapを同時に指定する必要があります。 --memory-swapは、利用可能なメモリとスワップの総量を表わします。 また--memoryはスワップを含めず、利用されるメモリの総量を制御します。 したがってたとえば--memory="300m"--memory-swap="1g"を指定した場合、そのコンテナーが利用できるのは 300m のメモリと 700m (1g - 300m) のスワップとなります。

  • --memory-swap0にすると、この設定は無視され、設定されていないものとして扱われます。

  • --memory-swapに設定された値が--memoryと同じ値である場合で、かつ--memoryに正の整数が設定されている場合、コンテナーはスワップへアクセスしませんコンテナーにおけるスワップ利用の防止 を参照してください。

  • --memory-swapが設定されていない場合で、かつ--memoryが設定されている場合、コンテナーは--memoryに設定されている値をスワップ容量とします。 当然このときは、ホストコンテナーがスワップメモリを持つものとして設定されている場合に限ります。 たとえば--memory="300m"と設定され、--memory-swapが設定されていない場合、そのコンテナーはメモリとスワップの総量として 600m を利用することになります。

  • --memory-swapを明示的に-1とした場合、コンテナーが利用できるスワップは、ホストシステムでの利用可能なスワップ範囲内で無制限となります。

  • コンテナーの内部からfreeなどのツールを実行すると、ホスト上で利用可能なスワップ容量が表示されます。 コンテナー内において利用可能な量を示すわけではありません。 freeや同等のツールを利用する際には、出力結果からスワップ容量を判断できないことに注意してください。

コンテナーにおけるスワップ利用の防止

--memory--memory-swapに同じ値を設定した場合、コンテナーがスワップを利用しないようになります。 --memory-swapは、利用可能なメモリとスワップを合わせた総量を表わすものであり、--memoryは利用可能なメモリ使用量を意味するからです。

--memory-swappinessの詳細

  • 0 を指定すると、匿名ページのスワップを無効にします。
  • 100 を指定すると、匿名ページのすべてをスワップ可能とします。
  • --memory-swappiness を設定しなかった場合、デフォルトでは、ホストマシンからその値を受け継ぎます。

--kernel-memoryの詳細

カーネルメモリに対する制約は、コンテナーに割り当てられるメモリ全体に関わります。 以下の状況が考えられます。

  • メモリ制限なし、カーネルメモリ制限なし: これがデフォルトの動作です。
  • メモリ制限なし、カーネルメモリ制限あり: この設定が適当な状況とは、ホストマシン上の実際のメモリ容量よりも、cgroup が必要とするメモリの総量が上回っている場合です。 カーネルメモリは、ホストマシン上での利用可能量を越えないように、またそれ以上に必要としているコンテナーは、利用可能になるまで待つような設定とすることができます。
  • メモリ制限あり、カーネルメモリ制限なし: メモリ全体が制限されますが、カーネルメモリは制限されません。
  • メモリ制限あり、カーネルメモリ制限あり: ユーザーメモリとカーネルメモリをともに制限するのは、メモリに関する障害をデバッグする際に利用できます。 コンテナーがこのいずれかのメモリを予想以上に消費している場合、メモリ不足となっても、他のコンテナーやホストには影響を及ぼしません。 この設定において、カーネルメモリの制限値がユーザーメモリの制限値より小さい場合は、メモリ不足によってコンテナー内に OOM エラーが発生することになります。 カーネルメモリの制限値の方が大きい場合は、コンテナー内に OOM エラーが発生することはありません。

カーネルメモリに制限を設けた場合、ホストマシンはプロセスごとに「最高水位標」(high water mark)の統計をとります。 そこからどのプロセスが(今の場合、どのコンテナーが)過剰にメモリを消費しているかを知ることができます。 具体的にはホストマシン内の/proc/<PID>/statusを見ることで、プロセスごとの状況がわかります。

CPU

各コンテナーがホストマシンの CPU サイクルにアクセスすることは、デフォルトでは制限がありません。 ホストマシンの CPU サイクルにアクセスするコンテナーに制限を加える方法はいろいろとあります。 よく利用されるのは デフォルト CFS スケジューラー です。 リアルタイムスケジューラー を利用することもできます。

デフォルト CFS スケジューラーの設定

CFS は Linux 上の普通のプロセスに対して用いられる Linux カーネル CPU スケジューラーです。 コンテナーが利用する CPU リソースのアクセス量を設定するために、いくつかの実行時フラグが用意されています。 この設定を行うと、Docker はホストマシン上にあるコンテナーの cgroup 設定を修正します。

オプション 内容説明
--cpus=<値> コンテナーが CPU リソースをどれだけ利用可能かを指定します。たとえばホストマシンに CPU が 2 つあり--cpus="1.5"という設定を行った場合、コンテナーに対して CPU 最大 1.5 個分が保証されます。これは--cpu-period="100000"--cpu-quota="150000"を設定することと同じです。
--cpu-period=<値> CFS スケジューラー間隔を指定します。これは--cpu-quotaとともに指定されます。デフォルトは 100000 マイクロ秒(100 ミリ秒)です。たいていの場合、このデフォルト値を変更することはしません。たいていの場合、より便利なのは--cpusです。
--cpu-quota=<値> コンテナーに対して CFS クォータを設定します。--cpu-periodごとのマイクロ秒単位の時間であり、スロットリングされる前にこの時間に制限されます。有効しきい値として動作します。たいていの場合、より便利なのは--cpusです。
--cpuset-cpus コンテナーが利用する CPU またはコアを特定します。CPU が複数あれば、カンマ区切りあるいはハイフン区切りのリストで CPU の利用範囲を指定します。1 つめの CPU を 0 とします。指定例としては以下です。0-3(1 つめから 4 つめまでの CPU を利用する場合)、1,3(2 つめと 4 つめの CPU を利用する場合)
--cpu-shares コンテナーへの配分を定めるもので、デフォルト値は 1024 です。本フラグを利用する場合は、デフォルト値より大きければ配分を増やし、小さければ減らします。そしてホストマシンの CPU サイクルへのアクセスを高比率、低比率で行います。これは CPU サイクルが制限されている場合に限って動作します。CPU サイクルが豊富に利用可能であるとき、すべてのコンテナーは必要な分だけ CPU を利用します。こういうことから、これはソフトリミットと言えます。--cpu-sharesは Swarm モード内においてコンテナーがスケジュールされることを妨げません。コンテナーの CPU リソースは、これによって利用可能な CPU サイクルが優先的に割り当てられます。ただし CPU アクセスを保証したり予約するものではありません。

CPU が 1 つである場合に、以下のコマンドはコンテナーに対し、毎秒 CPU の最大 50 % を保証します。

$ docker run -it --cpus=".5" ubuntu /bin/bash

手動で--cpu-period--cpu-quotaを指定しても同じです。

$ docker run -it --cpu-period=100000 --cpu-quota=50000 ubuntu /bin/bash

リアルタイムスケジューラーの設定

コンテナーにおいてリアルタイムスケジューラーを利用するように設定することができます。 CFS スケジューラーが利用できないタスクに対して用います。 初めに ホストマシンのカーネルが正しく設定されていること を確認した上で、Docker デーモンの設定 を行うか、各コンテナーの個別設定 を行ってください。

警告

CPU スケジュールや優先処理は、高度なカーネルレベルの機能です。 たいていの場合、その機能設定をデフォルトから変更する必要はありません。 設定を誤ると、ホストシステムが不安定または利用不能になることがあります。

ホストマシンカーネルの設定

Linux カーネルにおいてCONFIG_RT_GROUP_SCHEDが有効になっていることを確認します。 これにはzcat /proc/config.gz | grep CONFIG_RT_GROUP_SCHEDを実行するか、あるいはファイル/sys/fs/cgroup/cpu.rt_runtime_usが存在するかどうかで確認します。 カーネルのリアルタイムスケジューラーの設定方法については、各オペレーティングシステムのドキュメントを参照してください。

Docker デーモンの設定

リアルタイムスケジューラーを利用するコンテナーを起動するには、Docker デーモンに--cpu-rt-runtimeフラグをつけて起動します。 設定値には、リアルタイムタスクに対して、実行時間ごとに割り当てられる最大の時間をマイクロ秒単位で指定します。 たとえばデフォルトの実行時間である 1000000 マイクロ秒に対して、--cpu-rt-runtime=950000と設定すると、このリアルタイムスケジューラーを利用するコンテナーは、各 1000000 マイクロ秒ごとに 950000 マイクロ秒ずつ稼動するようになります。 残りの 50000 マイクロ秒は、リアルタイムスレッド以外のタスクに利用されます。 systemdを利用するシステム上で、これを恒常的な設定とするには systemd を用いた Docker の管理と設定 を参照してください。

個々のコンテナーに対する設定

コンテナーの CPU 優先順位づけ(priority)を制御するフラグがいくつかあります。 docker runを実行する際に、これを指定します。 適切な値設定に関しては、オペレーティングシステムのドキュメントやulimitコマンドを参照してください。

オプション 内容説明
--cap-add=sys_nice コンテナーがCAP_SYS_NICEケーパビリティーを利用できるようにします。これによってコンテナーにおけるプロセスのnice値の加算、リアルタイムスケジューラーポリシーの設定、CPU アフィニティの設定、その他が行えるようになります。
--cpu-rt-runtime=<値> Docker デーモンにおいて、リアルタイムスケジューラー実行時間内のリアルタイム優先順位づけによる最大実行時間をマイクロ秒で指定します。同時に--cap-add=sys_niceフラグの指定も必要です。
--ulimit rtprio=<値> コンテナーに対して許容するリアルタイム優先順位づけの最大数。同時に--cap-add=sys_niceフラグの指定も必要です。

以下に示すコマンドは、debian:jessieコンテナーに対して 3 つのフラグを設定する例です。

$ docker run -it \
    --cpu-rt-runtime=950000 \
    --ulimit rtprio=99 \
    --cap-add=sys_nice \
    debian:jessie

カーネルまたは Docker デーモンが正しく設定できていない場合には、エラーが発生します。

GPU

NVIDIA GPU へのアクセス

前提条件

NVIDIA ドライバーページ にアクセスして、適切なドライバーをダウンロード、インストールしてください。 これを行ったらシステムを再起動してください。

GPU が起動中でありアクセス可能であることを確認してください。

nvidia-container-runtime のインストール

(https://nvidia.github.io/nvidia-container-runtime/) にある手順に従い、次に以下のコマンドを実行してください。

$ apt-get install nvidia-container-runtime

$PATH上からnvidia-container-runtime-hookがアクセスできることを確認します。

$ which nvidia-container-runtime-hook

Docker デーモンを再起動します。

GPU の有効化

コンテナーの起動時に--gpusフラグをつけると、GPU リソースにアクセスすることができます。 このとき GPU をどれだけ利用するかを指定します。 たとえば以下のとおりです。

$ docker run -it --rm --gpus all ubuntu nvidia-smi

利用可能な GPU をすべて有効にした場合、以下のような出力結果となります。

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 384.130            	Driver Version: 384.130               	|
|-------------------------------+----------------------+----------------------+
| GPU  Name 	   Persistence-M| Bus-Id    	Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GRID K520       	Off  | 00000000:00:03.0 Off |                  N/A |
| N/A   36C	P0    39W / 125W |  	0MiB /  4036MiB |      0%  	Default |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU   	PID   Type   Process name                         	Usage  	|
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

deviceオプションを使って GPU を指定します。 たとえば以下です。

$ docker run -it --rm --gpus device=GPU-3a23c669-1f69-c64e-cf85-44e9b07e7a2a ubuntu nvidia-smi

これにより指定した GPU が有効になります。

$ docker run -it --rm --gpus '"device=0,2"' ubuntu nvidia-smi

これは 1 つめと 3 つめの GPU が有効になります。

メモ

NVIDIA GPU は、単一の Engine が起動するシステムからのみアクセスすることができます。

NVIDIA ケーパビリティーの設定

ケーパビリティーは手動で設定します。 たとえば Ubuntu では以下のコマンドを実行します。

$ docker run --gpus 'all,capabilities=utility' --rm ubuntu nvidia-smi

上を行うとutilityドライバーケーパビリティーによってnvidia-smiツールが追加され、コンテナーにより利用可能となります。

ケーパビリティーも他の設定も、環境変数を利用してイメージに設定することができます。 利用可能な環境変数の詳細は nvidia-container-runtime GitHub ページを参照してください。 この環境変数は Dockerfile 内に指定することもできます。

その環境変数を自動的に設定する CUDA イメージを利用することもできます。 詳細は CUDA イメージ GitHub ページを参照してください。

docker, daemon, configuration, runtime