コンテナの「暴走」を止める守護神:Cgroups v2の深淵と実務的リソース制御
システムが悲鳴を上げるとき、その多くは「誰かがリソースを食い尽くした」ことが原因です。クラウドネイティブな環境において、Kubernetesがポッドを制御できるのは、その背後にLinuxカーネルの機能である Cgroups (Control Groups)が鎮座しているからです。
今日は、教科書には載っていない「現場視点」での Cgroups の挙動と、v1からv2への移行が我々のインフラ運用に何をもたらしたのか、その核心を紐解いていきましょう。
1. Cgroupsとは何か? ― 仮想化の「見えない檻」
ハイパーバイザー型の仮想化が「ハードウェアを完全にエミュレートして切り分ける」のに対し、コンテナ技術は「Linuxカーネルの機能を使ってプロセスをグループ化し、その挙動を制限する」技術です。
ここでいう「制限」を司るのが Cgroups です。CPU、メモリ、ディスクI/Oといったリソースを特定のグループ(階層構造)に閉じ込め、プロセスが隣のプロセスを飢えさせないように調整します。
Cgroups v1とv2の決定的な違い
かつての v1 は、リソースごとに独立した階層構造を持っていました。これが運用上の悪夢でした。例えば「CPUのグループ」と「メモリのグループ」の構成がズレてしまい、矛盾が生じるケースが多々あったのです。
v2 では、単一の階層構造(Unified Hierarchy)に統合されました。これにより、リソースの親子関係が明確になり、イベント通知の仕組みも洗練されました。現在、モダンなKubernetes環境(特に cgroupfs ドライバを使用するもの)では、この v2 が標準となりつつあります。
2. 実践:Cgroups v2でリソースを「縛る」
実際に手元のLinux環境(最近のディストリビューションなら /sys/fs/cgroup 以下を確認してください)で、どのようにリソース制御が行われているかを見てみましょう。
例えば、ある特定のグループに対してメモリ上限を 512MB に設定する場合、以下のような操作を行います。
# 新しいグループを作成
mkdir /sys/fs/cgroup/my_app_group
# メモリの上限を512MBに設定
echo 536870912 > /sys/fs/cgroup/my_app_group/memory.max
# 現在のシェルプロセスをこのグループに登録
echo $$ > /sys/fs/cgroup/my_app_group/cgroup.procs
これだけで、このシェルから派生するすべてのプロセスは、メモリが 512MB を超えた瞬間にカーネルから「殺される(OOM Killerの対象になる)」か、スワップアウトされる運命をたどります。
3. Web API開発者が知っておくべき「I/O制限」のリアル
APIサーバーがログを大量に書き込み、ディスクI/Oを飽和させてシステム全体がレスポンス低下を起こす……。これは障害対応で最もよく見る光景の一つです。io.max を使えば、特定のコンテナがディスクを叩く速度を制限できます。
# デバイス番号を確認(例: /dev/sda1 が 8:0 なら)
# 読み書きの合計スループットを 10MB/s に制限する設定例
echo "8:0 rbps=10485760 wbps=10485760" > /sys/fs/cgroup/my_app_group/io.max
このように、アプリケーションの特性に合わせて cgroup を調整することは、大規模トラフィックを捌くための「防御的設計」の基本です。
4. 現場の教訓:トラブルシューティングの作法
どれだけ設計が完璧でも、突発的なリソース不足は発生します。その際、以下のチェックリストを常に頭に入れておいてください。
チェックリスト:リソース制限に起因する障害の切り分け
1. OOM Killerのログを確認: dmesg -T | grep -i oom を叩き、プロセスが OOM-kill されていないか確認します。
2. メモリの「アクティブ/非アクティブ」を見る: cat /sys/fs/cgroup/memory.stat を確認し、inactive_file が過剰にキャッシュされていないかチェックします。
3. I/O待ちの確認: iostat -x 1 で %util が100%に張り付いていないか?もしそうなら、cgroup によるスロットリングを疑うべきです。
Pythonによるリソース消費のデモンストレーション
意図的にメモリを食いつぶすスクリプトを書いて、自身の環境で挙動を試すのも良いでしょう。
import time
# 巨大なリストを作成してメモリを強制的に確保
def memory_leak_simulation():
data = []
try:
while True:
# 10MBずつ追加
data.append(" " * 10 * 1024 * 1024)
print("Memory allocated...")
time.sleep(1)
except MemoryError:
print("Memory limit reached!")
if __name__ == "__main__":
memory_leak_simulation()
最後に:インフラの「見えない層」を理解するということ
クラウドの世界では、DockerやKubernetesが裏側で何をしているか見えにくい抽象化が進んでいます。しかし、障害が起きたとき、最後に頼りになるのは「カーネルがパケットとメモリをどう扱っているか」という泥臭い知識です。
Cgroups は、単なる設定値の集まりではありません。それは、マルチテナント環境において「隣人と仲良くするためのルール」そのものです。この仕組みを理解し、適切にチューニングできることこそが、一流のエンジニアへの近道だと私は信じています。
皆さんのシステムが、今日も安定して稼働し続けることを願っています。それでは、また現場でお会いしましょう。
コメント