Systemd Cgroupsによるリソース制限と分離の包括的ガイド

systemdのcgroups、スライス、ユニットプロパティを使用して、生のcgroupファイルを編集せずにCPU、メモリ、I/Oを制限します。

Systemd Cgroupsによるリソース制限と分離の包括的ガイド

SystemdはすでにサービスをLinuxコントロールグループに配置しています。バッチワーカーがマシン全体を消費するのを防ぐために、手動で生のcgroupディレクトリを作成する必要はありません。多くの場合、サービスやスライスにいくつかのプロパティを追加し、systemdをリロードするだけで、再起動後も持続し、通常のsystemctlツールで表示されるCPU、メモリ、タスク、I/O制御を取得できます。

コツは適切な種類の制限を選択することです。ハードメモリキャップはホストを保護できますが、低く設定しすぎるとサービスが強制終了される可能性があります。CPUウェイトはシステムがビジーになるまで穏やかです。CPUクォータは厳格ですが、レイテンシを追加する可能性があります。I/O制限はストレージスタックとcgroupバージョンに依存します。リソース制御はチェックボックスではなく、運用上のトレードオフです。

コントロールグループ(cgroups)の理解

systemdの実装に飛び込む前に、cgroupsの基本概念を理解することが不可欠です。CgroupsはLinuxカーネルの階層的メカニズムであり、プロセスをグループ化し、これらのグループにリソース管理ポリシーを割り当てることができます。これらのポリシーには以下が含まれます:

  • CPU: CPU時間の制限、CPUアクセスの優先順位付け。
  • メモリ: メモリ使用量の制限設定、メモリ不足(OOM)状態の防止。
  • I/O: ディスク読み取り/書き込み操作のスロットリング。
  • ネットワーク: ネットワーク制御はLinuxトラフィックコントロールおよび関連ツールを通じて可能ですが、systemdの組み込みユニットプロパティは主にCPU、メモリ、プロセス数、デバイスアクセス、ブロックI/Oに焦点を当てています。
  • デバイスアクセス: 特定のデバイスへのアクセス制御。

カーネルはcgroup設定を仮想ファイルシステムを通じて公開し、通常は/sys/fs/cgroupにマウントされます。各コントローラ(例:cpumemory)には独自のディレクトリがあり、これらのディレクトリ内のディレクトリ階層はグループとそれに関連するリソース制限を表します。

SystemdのCgroup管理アーキテクチャ

Systemdは、構造化されたユニット管理システムを提供することで、直接的なcgroup操作の複雑さを抽象化します。プロセスをユニットの階層に整理し、それがcgroup階層にマッピングされます。リソース管理に関連する主要なユニットタイプは次のとおりです:

  • スライス: これらはサービスユニットの抽象的なコンテナです。スライスは階層を形成し、リソースの委任を可能にします。たとえば、ユーザーセッション用のスライスには、個々のアプリケーション用のスライスが含まれる場合があります。Systemdは、システムサービス、ユーザーセッション、仮想マシン/コンテナ用のスライスを自動的に作成します。
  • スコープ: これらは通常、一時的または動的に作成されたプロセスグループに使用され、多くの場合、ユーザーセッションや完全なサービスユニットとして管理されていないシステムサービスに関連付けられます。これらは一時的であり、内部のプロセスが実行されている限り存在します。
  • サービス: これらはデーモンとアプリケーションを管理するための基本的なユニットです。サービスユニットが開始されると、systemdはそのプロセスをcgroup階層に配置し、通常はスライス内に配置します。リソース制限はサービスユニットに直接適用できます。

Systemdのデフォルト階層は次のようになります:

-.slice (ルートスライス)
  |- system.slice
  |  |- <サービス名>.service
  |  |- another-service.service
  |  ... 
  |- user.slice
  |  |- user-1000.slice
  |  |  |- session-c1.scope
  |  |  |  |- <アプリケーション>.service (ユーザーが開始した場合)
  |  |  |  ...
  |  |  ...
  |  ... 
  |- machine.slice (VM/コンテナ用)
  ... 

Systemdユニットファイルによるリソース制限の適用

Systemdでは、.service.slice、または.scopeユニットファイル内で直接cgroupリソース制限を指定できます。これらのディレクティブは、それぞれ[Service][Slice]、または[Scope]セクションの下に配置されます。

CPU制限

CPUリソース制御の主要なディレクティブは次のとおりです:

  • CPUQuota=: ユニットが使用できる合計CPU時間を制限します。これはパーセンテージ(例:半CPUコアの場合は50%)またはCPUコアの分数(例:0.5)として指定されます。期間あたりのマイクロ秒で値を指定することも可能です。デフォルトの期間は100msです。
  • CPUWeight=: cgroup v2システムでCPU時間の相対的な重みを設定します。重みが高いユニットは競合時に大きなシェアを取得しますが、マシンがアイドル状態のときにCPUを予約することはありません。
  • CPUShares=: 古いcgroup v1時代の重み付け。最新のディストリビューションでは、v1互換性が必要でない限り、CPUWeight=を優先してください。
  • CPUQuotaPeriodSec=: CPUQuotaの期間を設定します。デフォルトは100msです。

例:Webサーバーを1つのCPUコアの75%に制限する:

サービスファイルを作成または編集します。例:/etc/systemd/system/mywebapp.service

[Unit]
Description=My Web Application

[Service]
ExecStart=/usr/bin/mywebapp
User=webappuser
Group=webappgroup

# 1つのCPUコアの75%に制限
CPUQuota=75%

[Install]
WantedBy=multi-user.target

サービスファイルを作成または変更した後、systemdデーモンをリロードし、サービスを再起動します:

sudo systemctl daemon-reload
sudo systemctl restart mywebapp.service

メモリ制限

メモリ制限は、次のようなディレクティブによって制御されます:

  • MemoryMax=: ユニットのプロセスが消費できるメモリ量にハードリミットを設定します。これはバイト単位、またはKMGTのようなサフィックス(例:512M)で指定できます。
  • MemoryLimit=: 一部のシステムで互換性のために保持されている古いスペル。最新のsystemdリリースではMemoryMax=を優先してください。
  • MemoryHigh=: ソフトリミットを設定します。この制限に近づくと、メモリ回収(スワッピング)がより積極的にトリガーされますが、ハードリミットはまだ適用されません。
  • MemorySwapMax=: ユニットが使用できるスワップ領域の量を制限します。

例:データベースを2GBのRAMに制限する:

サービスファイルを作成または編集します。例:/etc/systemd/system/mydb.service

[Unit]
Description=My Database Service

[Service]
ExecStart=/usr/bin/mydb
User=dbuser
Group=dbgroup

# メモリを2ギガバイトに制限
MemoryMax=2G

[Install]
WantedBy=multi-user.target

リロードして再起動:

sudo systemctl daemon-reload
sudo systemctl restart mydb.service

I/O制限

I/Oスロットリングは、次のようなディレクティブを使用して制御できます:

  • IOWeight=: I/O操作の相対的な重みを設定します。値が高いほどI/O優先度が高くなります。範囲は1〜1000(デフォルト500)です。
  • IOReadBandwidthMax=: 読み取りI/O帯域幅を制限します。[<デバイス>] <バイト/秒>として指定します。例:IOReadBandwidthMax=/dev/sda 100Mは、/dev/sdaでの読み取り操作を100MB/sに制限します。
  • IOWriteBandwidthMax=: 書き込みI/O帯域幅を制限します。IOReadBandwidthMaxと同様の形式。

例:バックグラウンド処理サービスを特定のディスクで50MB/sに制限する:

サービスファイルを作成または編集します。例:/etc/systemd/system/batchproc.service

[Unit]
Description=Batch Processing Service

[Service]
ExecStart=/usr/bin/batchproc
User=batchuser
Group=batchgroup

# /dev/sdbでの書き込み操作を50MB/sに制限
IOWriteBandwidthMax=/dev/sdb 50M

# 中程度の読み取り優先度を与える
IOWeight=200

[Install]
WantedBy=multi-user.target

リロードして再起動:

sudo systemctl daemon-reload
sudo systemctl restart batchproc.service

Cgroupsの管理と監視

Systemdは、ユニットに関連付けられたcgroupsを検査および管理するためのツールを提供します。

Cgroupステータスの検査

systemctl statusコマンドは、ユニットのcgroupメンバーシップとリソース使用量に関する情報を提供します。

systemctl status mywebapp.service

cgroupパスを示す行を探します。例:

● mywebapp.service - My Web Application
     Loaded: loaded (/etc/systemd/system/mywebapp.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2023-10-27 10:00:00 UTC; 1 day ago
       Docs: man:mywebapp(8)
   Main PID: 12345 (mywebapp)
      Tasks: 5 (limit: 4915)
     Memory: 15.5M
        CPU: 2h 30m 15s
      CGroup: /system.slice/mywebapp.service
              └─12345 /usr/bin/mywebapp

cgroupファイルシステムを直接検査することもできます:

systemd-cgls # systemdによって管理されるcgroup階層を表示
systemd-cgtop # topと似ていますが、cgroups用

サービスに適用された特定の制限を確認するには:

# 一般的なcgroup v2ホストのメモリ制限の場合
cat /sys/fs/cgroup/system.slice/mywebapp.service/memory.max

# CPU制限の場合
cat /sys/fs/cgroup/system.slice/mywebapp.service/cpu.max

正確なパスとファイル名は、cgroupのバージョンとディストリビューションによって異なります。cgroup v1システムでは、/sys/fs/cgroup/memory/...などのコントローラ固有のパスがまだ存在する場合があります。cgroup v2システムでは、/sys/fs/cgroup/...の下の統合階層が通常のビューです。

Cgroup制限のオンザフライ変更

制限をユニットファイルで設定するのがベストプラクティスですが、systemctl set-propertyを使用して一時的に調整できます:

sudo systemctl set-property mywebapp.service CPUQuota=50%

systemdのバージョンとフラグによっては、set-propertyは永続的なプロパティのために/etc/systemd/system.control/の下にドロップインを作成する場合があります。systemctl cat mywebapp.servicesystemctl show mywebapp.service -p CPUQuota -p MemoryMaxを使用して、何が起こったかを確認します。インフラストラクチャ・アズ・コードとピアレビューの場合、明示的なユニットドロップインの方が通常は明確です。

リソース委任のためのスライス

スライスは、サービスやアプリケーションのグループを管理するのに強力です。スライスにリソース制限を定義でき、そのスライス内のすべてのサービスまたはスコープは、それらの制限を継承するか、制限されます。

例:リソースを大量に消費するバッチジョブ用の専用スライスを作成する:

スライスファイルを作成します。例:/etc/systemd/system/batch.slice

[Unit]
Description=Batch Processing Slice

[Slice]
# このスライス内のすべてのジョブの合計CPUを1コアに制限
CPUQuota=100%
# 合計メモリを4GBに制限
MemoryMax=4G

次に、.serviceファイルでSlice=ディレクティブを使用して、このスライス内で実行するようにサービスを設定できます:

[Unit]
Description=Specific Batch Job

[Service]
ExecStart=/usr/bin/mybatchjob

# このサービスをbatch.sliceに配置
Slice=batch.slice

[Install]
WantedBy=multi-user.target

systemdをリロードし、必要に応じてスライスを有効化/起動し(暗黙的にアクティブ化されることが多い)、サービスを起動します。

sudo systemctl daemon-reload
sudo systemctl start mybatchjob.service

このアプローチにより、関連するプロセスをグループ化し、それらの集合的なリソース消費を管理できます。

ベストプラクティスと考慮事項

  • 段階的な制限から始める: 制限を設定するときは、控えめな値から始め、必要に応じて徐々に増やします。積極的な制限はアプリケーションを不安定にする可能性があります。
  • 監視する: システムのリソース使用量とcgroup設定の影響を定期的に監視します。systemd-cgtophtoptopiotopなどのツールは非常に貴重です。
  • Cgroup v1とv2の違いを理解する: Systemdはcgroup v1とv2の両方をサポートしています。多くのディレクティブは似ていますが、v2は統合階層といくつかの動作の違いを提供します。複雑な問題に直面した場合、システムがどのバージョンを使用しているかを認識していることを確認してください。
  • 優先順位付けとハードリミット: リソースが不足している場合の優先順位付けにはCPUWeightを使用し、厳格な上限にはCPUQuotaを使用します。同様に、MemoryHighはハードリミット前のプレッシャー用であり、MemoryMaxはハードリミットです。
  • サービスとスライス: 個々のアプリケーションにはサービスユニットを使用し、関連するアプリケーションやリソースプールのグループを管理するにはスライスを使用します。
  • ドキュメント化: 特に本番環境では、重要なサービスに適用されるリソース制限を明確に文書化します。
  • OOMキラー: プロセスがMemoryMax制限を超えた場合、カーネルのOOM(メモリ不足)キラーがそれを終了させる可能性があることに注意してください。たとえcgroup内であってもです。Systemdは、OOMPolicy=などのディレクティブを使用して、特定のcgroupに対してOOMキラーの動作を管理できます。

制限を展開するより安全な方法

観察から始めます。制限を追加する前に、通常の負荷時と予想される最悪の負荷時にサービスがどのように動作するかを確認します:

systemctl status mywebapp.service
systemd-cgtop
systemctl show mywebapp.service -p MemoryCurrent -p CPUUsageNSec -p TasksCurrent

メモリの場合、MemoryMax=ではなくMemoryHigh=が最初の良い動きであることがよくあります:

[Service]
MemoryHigh=1G
MemoryMax=1536M

MemoryHigh=は、サービスがハードリミットに達する前にカーネルにプレッシャーをかけるように指示します。MemoryMax=は壁です。プロセスがそれを超え、メモリを回収できない場合、カーネルはcgroup内のプロセスを強制終了する可能性があります。これは暴走ワーカーにはまさに必要なことかもしれませんが、計画していない限り、データベースにとっては悪い驚きです。

CPUの場合、公平性とハードキャップのどちらが必要かを決定します:

[Service]
CPUWeight=50

これにより、競合時の優先度が下がりますが、サービスはアイドル状態のCPUを引き続き使用できます。バックグラウンドジョブの場合、これは多くの場合、クォータよりも優れています。

[Service]
CPUQuota=200%

これにより、サービスは約2つのCPUコア相当の時間に制限されます。これはノイズの多いバッチプロセッサには便利ですが、トラフィックスパイク中にワーカースレッドがスロットリングされると、レイテンシに敏感なアプリケーションに悪影響を及ぼす可能性があります。

プロセス爆発に対しては、タスク制限を追加します:

[Service]
TasksMax=200

これにより、偶発的なフォークストームからホストを保護します。通常のスレッド数に対して十分な高さに設定します。Java、データベース、ブラウザのようなワークロードは、予想よりも多くのタスクを使用する可能性があります。

ベンダーユニットを編集する代わりにドロップインを使用する

/usr/lib/systemd/system/または/lib/systemd/system/の下にあるパッケージによって出荷されるユニットファイルの編集は避けてください。ドロップインを使用します:

sudo systemctl edit mywebapp.service

次に追加します:

[Service]
MemoryHigh=1G
MemoryMax=1536M
CPUWeight=80

保存後:

sudo systemctl daemon-reload
sudo systemctl restart mywebapp.service
systemctl cat mywebapp.service

systemctl catは、ベンダーユニットとオーバーライドを一緒に表示します。これにより、アクティブな設定が1つのコマンドで表示されるため、将来のデバッグがはるかに簡単になります。

チーム、テナント、ワークロードクラスのためのスライス

スライスは、一度に1つのサービスを考えるのをやめると便利になります。ホストがAPI、レポートジェネレーター、およびいくつかのインポートワーカーを実行しているとします。どのインポートワーカーがCPUを使用するかは気にしないかもしれませんが、すべてのインポート作業が一緒になってAPIを飢えさせないことは気にします。

スライスを作成します:

# /etc/systemd/system/import.slice
[Unit]
Description=Import and backfill workloads

[Slice]
CPUWeight=30
MemoryHigh=4G
MemoryMax=5G

インポートサービスをその中に配置します:

[Service]
Slice=import.slice
ExecStart=/usr/local/bin/import-worker

これで、グループは共有プレッシャーを持つようになります。これは、すべてのワーカーに個別のハードキャップを設定し、誰かが新しいワーカーを追加した後も計算が機能することを期待するよりもクリーンです。

人々を混乱させる命名の詳細が1つあります:スライス名は階層をエンコードします。customer-a.sliceはトップレベルのスライスです。customer-a-batch.slicecustomer-a.sliceの子ではありません。これは単なる別のトップレベル名です。階層スライスは特定の方法でダッシュをセパレータとして使用するため、大規模なスライスツリーを設計する前にsystemd.slice(5)を読んでください。

リソース制限で修正できないもの

Cgroupsは、1つのワークロードがホストを圧倒するのを防ぐことができますが、サイズ不足のマシンを高速にすることはできません。データベースがワーキングセットに許可した以上のメモリを必要とする場合、メモリの回収により多くの時間を費やすか、負荷がかかると失敗する可能性があります。APIが短い応答時間を必要とする場合、厳格なCPUクォータはランダムなレイテンシのように見えるスロットリング遅延を生み出す可能性があります。ストレージデバイスがすでに飽和状態にある場合、I/Oウェイトは公平性を向上させるかもしれませんが、スループットを生み出すことはありません。

制限をガードレールとして扱います。アプリケーションレベルの設定と組み合わせます:データベースバッファサイズ、ワーカー数、キュー同時実行性、JVMヒープ制限、Go GOMEMLIMIT、Nodeメモリフラグ、またはランタイムが提供するもの。最良のセットアップは通常、両方です:アプリケーションは自身のメモリと同時実行モデルを認識し、systemdはそのモデルが破綻した場合にマシンの残りの部分を保護します。

保持すべきメンタルモデル

1つのデーモンにはサービスレベルの制限を使用します。関連するワークロードのグループにはスライスレベルの制限を使用します。競合下で優先順位が必要な場合はウェイトを使用します。確固たる境界が必要で、その結果に備えている場合はクォータとハードメモリキャップを使用します。systemctl showで有効なプロパティを確認し、systemd-cgtopで動作を監視し、チームがレビューできるドロップインまたはユニットファイルに設定を保持します。