cgroups v2 on Ubuntu — リソース制限と階層制御の完全理解

Linux知識

Ubuntu 24.04 LTS を使っていると、docker run --memory=128msystemctl がリソースを制限している裏側で cgroup v2 が動いています。「cgroup って聞いたことあるけど、v1 と v2 で何が違うの?」という疑問を持った人向けに、実際にコマンドで確かめながら解説します。

この記事では Ubuntu 24.04(カーネル 6.8.0-83-generic)のホストと docker run --rm ubuntu:24.04 コンテナで実測したデータを使います。コマンドをそのまま打てば同じ出力が得られるはずです。

この記事のポイント

  • Ubuntu 22.04 以降は cgroup v2(統合階層)がデフォルト。/proc/1/cgroup が 1 行 0::/ なら v2 です
  • /sys/fs/cgroup/ 直下に memory.maxcpu.max が並ぶのが v2 の特徴
  • Docker の --memory--cpus は cgroup v2 の memory.maxcpu.max に直接書き込まれる
  • v1 の多階層構造(/sys/fs/cgroup/memory/ など)は廃止方向。Ubuntu 24.04 では v1 は非推奨
  • コントローラ一覧:cpuset / cpu / io / memory / hugetlb / pids / rdma / misc(実測)

目次

  1. cgroup とは何か
  2. v1 から v2 への変化
  3. cgroup v2 が有効か確認する
  4. ファイルシステム構造を確認する
  5. メモリ制限の実際:memory.max を見る
  6. CPU 制限の実際:cpu.max を見る
  7. v1 と v2 の比較まとめ
  8. まとめ

cgroup とは何か

cgroup(control group)は Linux カーネルの機能で、プロセスをグループ化してリソース(CPU・メモリ・I/O など)の使用量を制限・監視できます。Docker のコンテナ分離や systemd のサービス管理は、内部でこの仕組みを使っています。

「コンテナに 1GB のメモリ制限をかける」「重い処理が CPU を独占しないようにする」といった操作が cgroup で実現されています。コマンドラインから直接ファイルに書き込む形なので、カーネルパラメータとして動作し、ユーザーランドのプロセスを再起動せずに適用できるのが特徴です。

v1 から v2 への変化

cgroup v1 は各コントローラ(memory・cpu・blkio など)が /sys/fs/cgroup/memory//sys/fs/cgroup/cpu/ のように個別にマウントされていました。柔軟な反面、コントローラごとに設定が分散して管理が難しいという問題がありました。

cgroup v2 では 統合階層(unified hierarchy) として /sys/fs/cgroup/ 1 か所にマウントされます。すべてのコントローラが同じツリーで管理されるため、「メモリとCPUを同じグループに適用する」といった操作が一貫して行えます。

Ubuntu 22.04 から systemd(249 以降)が cgroup v2 を優先的に使うようになり、Docker も 20.10 から cgroup v2 ドライバーをデフォルトで採用しました。

cgroup v2 が有効か確認する

手元の環境で cgroup v2 が使われているかは /proc/1/cgroup を見れば分かります。




ubuntu@linuxlab: ~
$ cat /proc/1/cgroup
0::/

$ mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)

/proc/1/cgroup0::/ の 1 行だけなら cgroup v2 の統合階層です。v1 では 12:memory:/docker/abc123... のように複数行になります。mount コマンドで type cgroup2 と表示されることも確認できます。

Docker コンテナ内でも同様です。コンテナプロセスのルートパスは変わりますが、ホストの cgroup v2 ツリー上にマップされます。

Ubuntu 24.04 cgroup v2 有効確認(実測)
Ubuntu 24.04 cgroup v2 有効確認(実測)

ファイルシステム構造を確認する

/sys/fs/cgroup/ 直下のファイルを見ると、v2 の構造がよく分かります。実際に ubuntu:24.04 コンテナで確認しました。




ubuntu@linuxlab: ~ (docker run –rm ubuntu:24.04)
$ cat /sys/fs/cgroup/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma misc

$ ls /sys/fs/cgroup/ | grep -E ‘^(memory|cpu|pids)’
cpu.idle
cpu.max
cpu.weight
memory.current
memory.high
memory.max
pids.current
pids.max

v1 では /sys/fs/cgroup/memory/ ディレクトリ内に memory.limit_in_bytes があったのに対し、v2 では /sys/fs/cgroup/memory.max というファイルが直下に存在します。ファイル名も変わっているので、v1 の設定スクリプトをそのまま流用するとエラーになることがあります。

コンテナ内の /sys/fs/cgroup は読み取り専用

Docker コンテナ内では /sys/fs/cgroupro(読み取り専用)でマウントされます。コンテナ内から直接 memory.max を書き込むことはできません。リソース制限は docker run --memory 等のオプションでホスト側から指定します。

cgroup v2 ファイルシステム階層(実測)
cgroup v2 ファイルシステム階層(実測)

メモリ制限の実際:memory.max を見る

docker run --memory=128m を付けると、コンテナの cgroup に何が書き込まれるか確認します。




ubuntu@linuxlab: ~
# 制限なしの場合は “max”
$ docker run –rm ubuntu:24.04 bash -c ‘cat /sys/fs/cgroup/memory.max’
max

# 128MB 制限: バイト数で書き込まれる
$ docker run –rm –memory=128m ubuntu:24.04 bash -c ‘cat /sys/fs/cgroup/memory.max’
134217728

# 128 × 1024 × 1024 = 134,217,728 bytes(128 MiB)
$ python3 -c ‘print(128 * 1024 * 1024)’
134217728

実測結果:--memory=128m で起動したコンテナの memory.max134217728(128 MiB)が正確に書き込まれました。制限なしの場合は文字列 max になります。

memory.current でリアルタイムの使用量が確認でき、制限に近づくと memory.high(ソフト制限)→ memory.max(ハード制限)の順に OOM killer が発動します。v1 の memory.limit_in_bytes に相当しますが、ファイル名が変わっている点に注意です。

cgroup v2 メモリ制限 128MB の実測確認
cgroup v2 メモリ制限 128MB の実測確認

CPU 制限の実際:cpu.max を見る

docker run --cpus=0.5 では CPU をどう制限するか確認します。




ubuntu@linuxlab: ~
# 制限なし: “max 100000”
$ docker run –rm ubuntu:24.04 bash -c ‘cat /sys/fs/cgroup/cpu.max’
max 100000

# 0.5 CPU 制限
$ docker run –rm –cpus=0.5 ubuntu:24.04 bash -c ‘cat /sys/fs/cgroup/cpu.max’
50000 100000

# 書式: <quota_us> <period_us> → 100ms 中 50ms だけ使える

実測結果:--cpus=0.5cpu.max50000 100000 になりました。形式は <quota_us> <period_us> で、100ms(100,000 µs)のうち 50ms(50,000 µs)だけ CPU を使えるという意味です。

正直、最初に見たとき 2 つの数字の意味が分かりませんでした。quota / period という単位で考えると「1 サイクルの中での使用割り当て」として直感的に理解できます。--cpus=1.5 なら 150000 100000、2 コア全部なら 200000 100000 になります。

v1 では cpu.cfs_quota_uscpu.cfs_period_us の 2 ファイルに分かれていましたが、v2 では cpu.max 1 ファイルにまとめられています。

cgroup v2 CPU 制限 0.5 コアの実測確認
cgroup v2 CPU 制限 0.5 コアの実測確認

v1 と v2 の比較まとめ

実測データと公式ドキュメントをもとに、主な違いをまとめました。

cgroup v1 vs v2 主な違い比較表
cgroup v1 vs v2 主な違い比較表

特に注目すべき点が 2 つあります。

1 つ目は pressure ファイルの追加です。v2 では cpu.pressurememory.pressureio.pressure が導入され、リソースが逼迫していることを通知する仕組みが標準化されました。v1 にはなかった機能です。

2 つ目は コントローラの有効化方法の変化です。v1 ではマウント時に個別に指定していましたが、v2 では cgroup.subtree_control ファイルに有効にしたいコントローラ名を書き込みます。




ubuntu@linuxlab: ~
# 子 cgroup で有効にするコントローラを確認
$ cat /sys/fs/cgroup/cgroup.subtree_control
cpuset cpu io memory hugetlb pids rdma misc

# 新しい cgroup を作って memory を有効化する例(ルートでは書き込み不要)
$ sudo mkdir /sys/fs/cgroup/myapp
$ echo “+memory +cpu” | sudo tee /sys/fs/cgroup/myapp/cgroup.subtree_control
+memory +cpu

まとめ

Ubuntu 24.04(カーネル 6.8.0-83-generic)で実測した結果をまとめます。

  • /proc/1/cgroup0::/ の 1 行 → cgroup v2(統合階層)が有効
  • /sys/fs/cgroup/cgroup.controllerscpuset cpu io memory hugetlb pids rdma misc が確認できた
  • docker run --memory=128mmemory.max = 134217728(128 MiB をバイト換算)
  • docker run --cpus=0.5cpu.max = 50000 100000(100ms 周期のうち 50ms 使用可)
  • v1 の memory.limit_in_bytescpu.cfs_quota_us は v2 で memory.maxcpu.max に統合

cgroup v2 を直接触る機会は普段ほとんどありませんが、Kubernetes や systemd のリソース制限・Docker のメモリ制限が効かないときに「memory.max を直接見る」という手段を知っておくと原因を特定しやすくなります。VPS でサーバーを運用するなら、こういった低レイヤの仕組みを一度確認しておく価値があります。

著者アイコン
著者アイコン

cgroup v2 に移行してから Docker の --memory 制限が効きやすくなった印象があります。v1 の hybrid モードが混在していた時期は設定ミスが起きやすかったので、統合階層になって管理が楽になりました。

本格的なサーバー運用を始めるなら、VPS での実機確認がおすすめです。

コメント

タイトルとURLをコピーしました