この記事は、以前にコンテナの経験がない参加者向けに準備されたもので、主にコンテナの CPU とメモリの制限について説明します。
CPU の制限#
まず、Mac または Windows の参加者が Docker Desktop を使用する場合、デフォルトで Docker Desktop の CPU 制限が 1 に設定されます。つまり、Docker Desktop は 1 つの CPU しか使用できません。これは、Docker Desktop が仮想マシン(Windows では WSL2/Hyper-V、Mac ではおそらく QEMU)でラップされているためです。これは、特定の CPU 数のホストマシンで Docker を実行していることに相当します。
まず、CPU 制限について言及しましょう。本質的には、プロセスの CPU 使用時間を制限するものであり、Linux では、プロセスには 3 つのスケジューリング優先度があります。
- SCHED_NORMAL
- SCHED_FIFO
- SCHED_RR
1 は Linux の CFS スケジューラを使用しており、一般的な通常のプロセスは SCHED_NORMAL です。OK、前提知識は省略します。
コンテナ内の CPU 制限について言及すると、現在の主流の文脈では、コンテナは Linux の CGroup と Namespace をベースにした一連の技術ソリューションを指します。したがって、この文脈では、CPU 制限の実装には Linux CGroup の 3 つの CPU サブシステムが使用されます。私たちが関心を持つ主なパラメータは次の 4 つです。
- cpu.cfs_period_us
- cpu.cfs_quota_us
- cpu.shares
- cpuset.cpus
それぞれについて説明します。
まず、cpu.shares について説明します。Docker では、--cpu-shares というパラメータが使用されます。これは、CPU 利用率の重み付けの下限を設定するソフト制限であり、デフォルト値は 1024 です。相対値については、抽象的に理解するのが少し難しいかもしれません。したがって、例を見てみましょう。1 つのコアを持つホストで 3 つのコンテナを実行する場合、1 つのコンテナの cpu-shares を 1024 に設定し、他の cpu-shares を 512 に設定するとします。3 つのコンテナのプロセスが CPU の 100% を使用しようとする場合(cpu.shares は下限に対して設定されるため、100% の CPU 使用が重要です。この場合にのみ設定値が反映されます)、1024 のコンテナは CPU 時間の 50% を占有します。さらに別の例を挙げると、前述のシナリオで他の 2 つのコンテナにはあまり多くのタスクがない場合、空いている CPU 時間は、最初の 1024 のコンテナが引き続き使用できます。
次に、cpu.cfs_quota_us と cpu.cfs_period_us について説明します。これらの 2 つのパラメータは組み合わせて使用する必要があり、本質的な意味は、cpu.cfs_period_us の単位時間内に、プロセスが cpu.cfs_quota_us(単位はマイクロ秒)を最大限利用できることを意味します。クォータが使い果たされると、プロセスはカーネルによってスロットルされます。Docker では、--cpu-period と --cpu-quota という 2 つの値を使用して設定できます。また、--cpu を使用して設定することもできます。たとえば、--cpu を 2 に設定すると、コンテナは cpu.cfs_quota_us が cpu.cfs_period_us の 2 倍であることを保証します。残りは同様です(Docker のデフォルトの cpu.cfs_period_us のしきい値は 100ms、つまり 10000us です)。
これで、3 つのパラメータについて説明しました。では、いつどのパラメータを使用する必要があるのでしょうか。一般的に、パフォーマンスに敏感なプロセスでは、cpu.shares を使用してプロセスができるだけ多くの CPU を使用できるようにします。ビジネスプロセスでは、cpu.cfs_quota_us と cpu.cfs_period_us を使用して相対的に公平な分配を確保します。ただし、これには別の問題もあります。ビジネスのトラフィックが多いアプリケーションの場合、頻繁なスロットルにより RT などの指標に毛刺が生じる可能性があります。Linux 5.12 以降、cpu.cfs_burst_us という新機能が導入され、プロセスは CPU 利用率が比較的低いアイドル時に一定のクレジットを蓄積し、密集した使用時にバッファを交換することで、より少ないスロットルとより高い CPU 利用率を実現できます(もちろん、この機能はまだ主流のコンテナに完全にサポートされていません)。
さて、新しい問題が発生しました。共有メモリもしくは cpu.cfs_quota_us と cpu.cfs_period_us のいずれかがスロットルされる確率は少なくありません。プロセスが CPU をより効果的に利用するためにはどうすればよいでしょうか?答えは cpuset.cpus です。Docker のパラメータは --cpuset-cpus で、プロセスを特定のコアにバインドすることができます。
うーん、CPU の部分はこれで終わりです。
メモリの制限#
引き続き前提の説明です。
まず、Mac または Windows の参加者が Docker Desktop を使用する場合、Docker Desktop のメモリ制限が設定されます。これは、特定のメモリ量のホストマシンで Docker を実行していることに相当します。
そして、今日の文脈では、メモリリソースの制限は依然として CGroup のメモリサブシステムに依存しており、多くのパラメータがありますが、私たちは現時点では次の 2 つに関心を持つ必要があります。
- memory.limit_in_bytes
これは、コンテナの最大メモリ制限を表します。-1 に設定すると、メモリの制限はありません。Docker では、--memory というパラメータが使用されます。
動作は次の 2 つのケースに分かれます。
- システムのメモリにまだ余裕があるが、コンテナのメモリが制限を超えている場合、コンテナプロセスは OOMKiller によってキルされます。
- システムのメモリがコンテナよりも先にカーネルのしきい値に達した場合、OOMKiller はシステム全体で負荷などの複数の要因に基づいてスコアを計算し、ランク付けして高い順から低い順に OOM Kill を実行します。
もちろん、実際にはもう 1 つの追加のケースがあります。--oom-kill-disable パラメータを使用して memory.oom_control の値を設定できます。1 に設定すると、コンテナのメモリが制限を超えても OOM Kill されずに一時停止されます。0 に設定すると、コンテナのメモリが制限を超えると OOM Kill されます。
メモリの動作に関する説明は以上です。
まとめ#
大体こんな感じです。初心者向けの記事で、あまり内容はありませんが、気にしないでください(