- Authors

- Name
- Youngju Kim
- @fjvbn20031
目次
- cgroupとは?
- cgroup v1 vs v2
- 主要コントローラー深層分析
- Dockerとcgroup
- Kubernetesとcgroup
- cgroup実践トラブルシューティング
- cgroupとセキュリティ
- まとめ
1. cgroupとは?
Control Groupsの誕生
cgroup(Control Groups)は、Linuxカーネルが提供するプロセスグループ単位のリソース制限・隔離メカニズムである。 2006年、GoogleエンジニアのPaul MenageとRohit Sethが「process containers」という名前で開発を開始し、 2007年にLinuxカーネル2.6.24にmergeされた際、カーネル内の既存の「container」という用語との混同を避けるためcgroupに改名された。
cgroupが解決する問題
cgroupがなかった時代、一つのプロセスがシステム全体のCPUやメモリを独占するのを防ぐのは困難だった。 cgroupは以下の機能を提供する:
- リソース制限(Resource Limiting):CPU、メモリ、IO、ネットワーク帯域幅などの使用量上限設定
- 優先順位付け(Prioritization):プロセスグループ間のリソース配分比率調整
- アカウンティング(Accounting):グループ別リソース使用量の測定・報告
- 制御(Control):プロセスグループの一時停止、再開、チェックポイント
NamespaceとcgroupのRelationship
コンテナ技術の二本柱はNamespaceとcgroupである。
+-------------------------------------------+
| Linux コンテナ隔離モデル |
+-------------------------------------------+
| |
| Namespace(隔離 - Visibility) |
| ┌─────────────────────────────────────┐ |
| │ PID NS : プロセスID隔離 │ |
| │ NET NS : ネットワークスタック隔離 │ |
| │ MNT NS : ファイルシステムマウント隔離 │ |
| │ UTS NS : ホスト名隔離 │ |
| │ IPC NS : IPCリソース隔離 │ |
| │ USER NS : UID/GIDマッピング隔離 │ |
| └─────────────────────────────────────┘ |
| |
| cgroup(制限 - Resource Limits) |
| ┌─────────────────────────────────────┐ |
| │ CPU : CPU時間制限 │ |
| │ Memory : メモリ使用量制限 │ |
| │ IO : ブロックデバイスIO制限 │ |
| │ PIDs : プロセス数制限 │ |
| │ Devices : デバイスアクセス制御 │ |
| └─────────────────────────────────────┘ |
| |
+-------------------------------------------+
核心的な違いを要約すると:
- Namespace = 「何が見えるか」(隔離)
- cgroup = 「どれだけ使えるか」(制限)
cgroupファイルシステムの基本構造
cgroupは仮想ファイルシステム(VFS)を通じて管理される。すべての設定はファイルの読み書きで行われる。
# cgroupマウント確認
mount | grep cgroup
# cgroup v2がマウントされている場合
# cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
# 現在のプロセスのcgroup確認
cat /proc/self/cgroup
# 0::/user.slice/user-1000.slice/session-1.scope
2. cgroup v1 vs v2
cgroup v1の構造
cgroup v1では、各コントローラー(CPU、memory、blkioなど)が独立したhierarchyを持つ。
cgroup v1 構造:
/sys/fs/cgroup/
├── cpu/ # CPUコントローラー
│ ├── docker/
│ │ ├── container-abc/
│ │ │ ├── cpu.cfs_quota_us
│ │ │ ├── cpu.cfs_period_us
│ │ │ └── cpu.shares
│ │ └── container-xyz/
│ └── tasks
├── memory/ # Memoryコントローラー
│ ├── docker/
│ │ ├── container-abc/
│ │ │ ├── memory.limit_in_bytes
│ │ │ └── memory.usage_in_bytes
│ │ └── container-xyz/
│ └── tasks
├── blkio/ # Block IOコントローラー
├── pids/ # PIDコントローラー
├── devices/ # Deviceコントローラー
└── freezer/ # Freezerコントローラー
v1の特徴:
- 各コントローラーが別々のディレクトリツリーを持つ
- 一つのプロセスが各コントローラーで異なるグループに属することが可能
- 柔軟だが管理が複雑
cgroup v2の構造(Unified Hierarchy)
cgroup v2は単一統合hierarchyを使用する。
cgroup v2 構造(Unified):
/sys/fs/cgroup/ # ルートcgroup
├── cgroup.controllers # 利用可能なコントローラー一覧
├── cgroup.subtree_control # 子に有効化するコントローラー
├── system.slice/ # systemdシステムサービス
│ ├── docker.service/
│ └── sshd.service/
├── user.slice/ # ユーザーセッション
│ └── user-1000.slice/
└── kubepods.slice/ # Kubernetes pods
├── kubepods-burstable.slice/
└── kubepods-besteffort.slice/
v1 vs v2 比較表
| 特性 | cgroup v1 | cgroup v2 |
|---|---|---|
| Hierarchy | コントローラー別独立 | 単一統合(Unified) |
| マウントポイント | /sys/fs/cgroup/cpu/、/sys/fs/cgroup/memory/ 等 | /sys/fs/cgroup/ のみ |
| プロセス配置 | 各コントローラーで異なるグループ可能 | 全コントローラーで同一グループ |
| PSI(Pressure Stall Info) | 非対応 | 対応 |
| Threadedモード | 非対応 | 対応 |
| サブツリー委任 | 限定的 | 完全なdelegationモデル |
| Memory QoS | memory.limitのみ | memory.min/low/high/maxの4段階 |
| IO制御 | blkio(Direct IOのみ) | io(Buffered IO対応) |
| CPU burst | 非対応(カーネルパッチ必要) | 対応(kernel 5.14+) |
v2マイグレーション互換性
| ソフトウェア | cgroup v2サポート開始バージョン |
|---|---|
| systemd | 236+ |
| Docker | 20.10+ |
| containerd | 1.4+ |
| Kubernetes | 1.25+(GA) |
| Podman | ネイティブ対応 |
| RHEL | 9+(デフォルト) |
| Ubuntu | 21.10+(デフォルト) |
cgroup v2有効化の確認
# cgroup v2が使用中か確認
stat -fc %T /sys/fs/cgroup/
# cgroup2fs -> v2
# tmpfs -> v1
# またはマウント情報で確認
grep cgroup /proc/mounts
# カーネルブートパラメータでv2を強制使用
# GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=1"
3. 主要コントローラー深層分析
3.1 CPUコントローラー
CFS Bandwidth Control
LinuxのCFS(Completely Fair Scheduler)はCPU時間を公平に分配する。 cgroup CPUコントローラーはCFSの上にbandwidth throttlingを追加する。
CFS Bandwidth Controlの原理:
Period = 100ms(デフォルト)
Quota = 50ms (割り当て量)
0ms 50ms 100ms 150ms 200ms
|----------|----------|----------|----------|
[##########]...........[##########]...........
^-- 実行 --^ ^スロットル^ ^-- 実行 --^ ^スロットル^
CPU 1個基準:quota/period = 50ms/100ms = 0.5 CPU(50%)
v1 vs v2 CPUパラメータ
# ===== cgroup v1 =====
# CFS quota:マイクロ秒単位、-1は無制限
cat /sys/fs/cgroup/cpu/docker/CONTAINER_ID/cpu.cfs_quota_us
# 50000 (50ms)
cat /sys/fs/cgroup/cpu/docker/CONTAINER_ID/cpu.cfs_period_us
# 100000 (100ms)
# CPU shares:相対的重み(デフォルト1024)
cat /sys/fs/cgroup/cpu/docker/CONTAINER_ID/cpu.shares
# 512
# ===== cgroup v2 =====
# cpu.max:"quota period"形式
cat /sys/fs/cgroup/kubepods.slice/.../cpu.max
# 50000 100000 (50ms quota, 100ms period)
# "max 100000" -> 無制限
# cpu.weight:1-10000(デフォルト100)
cat /sys/fs/cgroup/kubepods.slice/.../cpu.weight
# 100
CPU shares/weight変換
v1 cpu.shares -> v2 cpu.weight 変換:
v2_weight = (1 + ((v1_shares - 2) * 9999) / 262142)
例:
v1 shares=1024(デフォルト)-> v2 weight ≈ 39
v1 shares=512 -> v2 weight ≈ 20
v1 shares=2048 -> v2 weight ≈ 78
cpuset:CPUピンニング
特定のCPUコアにプロセスを固定(ピンニング)できる。
# v2:使用するCPUコアを指定
echo "0-3" > /sys/fs/cgroup/mygroup/cpuset.cpus
echo "0" > /sys/fs/cgroup/mygroup/cpuset.mems
# 現在の設定を確認
cat /sys/fs/cgroup/mygroup/cpuset.cpus.effective
実習:CPU制限cgroupの直接作成
# cgroup v2環境での実習
# 1. 新しいcgroupを作成
sudo mkdir /sys/fs/cgroup/cpu-test
# 2. CPUコントローラーを有効化(ルートから)
echo "+cpu" | sudo tee /sys/fs/cgroup/cgroup.subtree_control
# 3. CPU 50%に制限(50ms quota / 100ms period)
echo "50000 100000" | sudo tee /sys/fs/cgroup/cpu-test/cpu.max
# 4. 現在のシェルをcgroupに追加
echo $$ | sudo tee /sys/fs/cgroup/cpu-test/cgroup.procs
# 5. CPU負荷を生成して制限を確認
stress --cpu 1 --timeout 10 &
# 6. スロットリング統計を確認
cat /sys/fs/cgroup/cpu-test/cpu.stat
# usage_usec 4500000
# user_usec 4500000
# system_usec 0
# nr_periods 100
# nr_throttled 50
# throttled_usec 5000000
# 7. クリーンアップ
echo $$ | sudo tee /sys/fs/cgroup/cgroup.procs
sudo rmdir /sys/fs/cgroup/cpu-test
CPUスロットリングの問題とburst
CPUスロットリングは、短いburstワークロードで深刻なレイテンシスパイクを引き起こす可能性がある。
問題シナリオ:平均CPU使用率30%のWebサーバー(limit: 1 CPU)
通常時: [###-------][###-------][###-------] -> 応答 10ms
バースト時:[##########][..........][###-------] -> 応答 110ms!
^-- 100ms全部使用 --^ ^-- スロットル --^
解決策:CPU burst(kernel 5.14+、cgroup v2)
cpu.max.burst = 許容burstマイクロ秒
前のperiodで未使用のquotaを貯蓄してburst時に使用
# CPU burstの設定(cgroup v2、kernel 5.14+)
echo 20000 > /sys/fs/cgroup/mygroup/cpu.max.burst
# 20msまでburstを許容
3.2 Memoryコントローラー
v2の4段階メモリ制限
cgroup v2はv1より細かい4段階のメモリ制御を提供する。
Memory制御4段階(cgroup v2):
memory.min : 絶対保護(これ以下へのreclaim不可)
↑ 最小保証領域
memory.low : ソフト保護(可能な限りreclaim防止、best-effort)
↑ 優先領域
memory.high : ソフト制限(超過時throttle、OOMではない)
↑ 警告領域
memory.max : ハード制限(超過時OOM Killer発動)
↑ 禁止領域
0 MB 1024 MB
|=====[min]==[low]==========[high]====[max]========|
|<-保護->|<-優先->|<-通常使用->|<-スロットル->|<-OOM->|
v1 vs v2 メモリパラメータ
# ===== cgroup v1 =====
# ハード制限
echo 536870912 > memory.limit_in_bytes # 512MB
# ソフト制限(reclaimプレッシャー)
echo 268435456 > memory.soft_limit_in_bytes # 256MB
# Swap含む制限
echo 1073741824 > memory.memsw.limit_in_bytes # 1GB (mem+swap)
# OOM制御
echo 1 > memory.oom_control # OOM Killer無効化
# ===== cgroup v2 =====
echo 512M > memory.max # ハード制限
echo 256M > memory.high # ソフト制限(throttle)
echo 128M > memory.low # best-effort保護
echo 64M > memory.min # 絶対保護
echo 256M > memory.swap.max # swap制限
memory.stat分析
cat /sys/fs/cgroup/kubepods.slice/.../memory.stat
# anon 104857600 # 匿名メモリ(heap, stack)
# file 52428800 # ファイルキャッシュ
# kernel 8388608 # カーネルメモリ(slabなど)
# shmem 0 # 共有メモリ
# pgfault 250000 # ページフォルト回数
# pgmajfault 10 # メジャーページフォルト
# workingset_refault 500 # working setからevict後の再参照
# oom_kill 0 # OOM kill回数
重要な指標の解釈:
anonが高い:アプリケーションのヒープメモリ使用量が大きいfileが高い:ファイルキャッシュが多い(通常は正常)pgmajfaultが高い:ディスクからページを読み込む頻度が高い(メモリ不足のサイン)oom_kill > 0:OOMが発生した
実習:メモリ制限とOOMテスト
# 1. メモリ制限cgroupを作成
sudo mkdir /sys/fs/cgroup/mem-test
echo "+memory" | sudo tee /sys/fs/cgroup/cgroup.subtree_control
# 2. 100MBハード制限を設定
echo 104857600 | sudo tee /sys/fs/cgroup/mem-test/memory.max
# 3. 80MBソフト制限を設定(超過時throttle)
echo 83886080 | sudo tee /sys/fs/cgroup/mem-test/memory.high
# 4. プロセスをcgroupに追加してメモリ割り当て
echo $$ | sudo tee /sys/fs/cgroup/mem-test/cgroup.procs
# 5. 100MB超過メモリ割り当てを試行
python3 -c "
data = []
for i in range(200):
data.append(bytearray(1024 * 1024)) # 1MBずつ割り当て
print(f'Allocated {i+1} MB')
"
# -> 約100MBでOOM Killed
# 6. OOMイベントを確認
cat /sys/fs/cgroup/mem-test/memory.events
# oom 1
# oom_kill 1
# high 150
# 7. dmesgでOOMログを確認
dmesg | grep -i "killed process"
Swap制御
# cgroup v2でのswap制限
echo 0 > /sys/fs/cgroup/mygroup/memory.swap.max # swap使用禁止
echo max > /sys/fs/cgroup/mygroup/memory.swap.max # swap無制限
# v1ではmemory + swap合算で制御
echo 1073741824 > memory.memsw.limit_in_bytes # mem + swap = 1GB
3.3 IOコントローラー
v1(blkio)vs v2(io)
v1のblkioコントローラーはDirect IOのみ制御可能だった。Buffered IOはpage cacheを経由するため、blkioでは追跡できなかった。 v2のioコントローラーはwritebackメカニズムを通じてBuffered IOも制御する。
# ===== cgroup v2: io.max =====
# 形式:MAJ:MIN rbps=NUM wbps=NUM riops=NUM wiops=NUM
# デバイス番号の確認
lsblk -o NAME,MAJ:MIN
# sda 8:0
# 読み取り10MB/s、書き込み5MB/sに制限
echo "8:0 rbps=10485760 wbps=5242880" > /sys/fs/cgroup/mygroup/io.max
# IOPS制限:読み取り1000 IOPS、書き込み500 IOPS
echo "8:0 riops=1000 wiops=500" > /sys/fs/cgroup/mygroup/io.max
io.weight(相対的重み)
# io.weight:1-10000(デフォルト100)
echo "default 200" > /sys/fs/cgroup/mygroup/io.weight
# 特定デバイスへの重み設定
echo "8:0 500" > /sys/fs/cgroup/mygroup/io.weight
io.latency(v2専用)
レイテンシターゲットを設定してIOレイテンシを保証する。
# 5msレイテンシターゲットを設定
echo "8:0 target=5000" > /sys/fs/cgroup/mygroup/io.latency
3.4 PIDコントローラー
Fork Bomb防止
# PID制限を設定
echo 100 > /sys/fs/cgroup/mygroup/pids.max
# 現在のPID数を確認
cat /sys/fs/cgroup/mygroup/pids.current
# 5
# Fork bombテスト(安全な環境でのみ!)
# 制限がないとシステム全体がフリーズする可能性あり
# pids.maxが設定されていれば100個でfork()が失敗
Fork Bomb防止の原理:
pids.max = 100
Process Tree:
init(1)
├── bash(2)
│ ├── worker(3)
│ ├── worker(4)
│ │ ├── child(5)
│ │ └── child(6)
│ ...
│ └── worker(100) <- pids.maxに到達
│ └── fork() -> EAGAIN(失敗!)
3.5 その他のコントローラー
devicesコントローラー
デバイスアクセスをホワイトリスト/ブラックリスト方式で制御する。
# v1: devices.allow / devices.deny
echo 'c 1:3 rmw' > devices.allow # /dev/nullアクセス許可
echo 'b 8:0 r' > devices.allow # /dev/sda読み取り許可
echo 'a' > devices.deny # すべてのデバイス拒否
# GPUアクセス制御(NVIDIA)
echo 'c 195:* rmw' > devices.allow # NVIDIAデバイス許可
freezerコントローラー
プロセスグループを一時停止(freeze)し再開(thaw)できる。
# v2: cgroup.freeze
echo 1 > /sys/fs/cgroup/mygroup/cgroup.freeze # 一時停止
echo 0 > /sys/fs/cgroup/mygroup/cgroup.freeze # 再開
# 状態確認
cat /sys/fs/cgroup/mygroup/cgroup.events
# frozen 1
hugetlbコントローラー
Huge page使用量を制限する。
# 2MB huge pages制限
echo 1073741824 > hugetlb.2MB.limit_in_bytes # 1GB
cat hugetlb.2MB.usage_in_bytes
4. Dockerとcgroup
Dockerリソース制限オプション
# CPU制限
docker run -d \
--cpus="1.5" \ # CPU 1.5個(quota=150000, period=100000)
--cpu-shares=512 \ # CPU shares(相対的重み)
--cpuset-cpus="0,1" \ # CPU 0、1番コアのみ使用
nginx
# メモリ制限
docker run -d \
--memory=512m \ # メモリハード制限512MB
--memory-swap=1g \ # メモリ+スワップ合計1GB(スワップ512MB)
--memory-reservation=256m \ # ソフト制限
--oom-kill-disable \ # OOM Killer無効化(注意!)
nginx
# IO制限
docker run -d \
--device-read-bps /dev/sda:10mb \ # 読み取り10MB/s
--device-write-bps /dev/sda:5mb \ # 書き込み5MB/s
--device-read-iops /dev/sda:1000 \ # 読み取り1000 IOPS
nginx
# PID制限
docker run -d \
--pids-limit=100 \ # 最大100プロセス
nginx
Dockerが作成するcgroupパス
# systemd cgroup driver使用時
# /sys/fs/cgroup/system.slice/docker-CONTAINER_ID.scope/
# コンテナID確認
CONTAINER_ID=$(docker ps -q --filter name=my-nginx)
# cgroupパス確認
docker inspect --format='{{.HostConfig.CgroupParent}}' $CONTAINER_ID
# 直接cgroupファイルを確認(v2)
cat /sys/fs/cgroup/system.slice/docker-${CONTAINER_ID}.scope/cpu.max
cat /sys/fs/cgroup/system.slice/docker-${CONTAINER_ID}.scope/memory.max
cat /sys/fs/cgroup/system.slice/docker-${CONTAINER_ID}.scope/pids.max
docker statsでリアルタイムモニタリング
# すべてのコンテナのリソース使用量
docker stats
# CONTAINER ID NAME CPU % MEM USAGE/LIMIT MEM % NET I/O BLOCK I/O
# abc123 nginx 0.50% 50MiB / 512MiB 9.77% 1.2kB/0B 0B/0B
# xyz789 redis 1.20% 30MiB / 256MiB 11.7% 500B/200B 4kB/0B
# 特定コンテナのみ
docker stats my-nginx --no-stream
Docker cgroup driver:cgroupfs vs systemd
+---------------------------+---------------------+---------------------+
| | cgroupfs | systemd |
+---------------------------+---------------------+---------------------+
| cgroup管理方式 | Dockerが直接管理 | systemdに委任 |
| initシステムとの競合 | 可能性あり | なし(統合) |
| Kubernetes推奨 | 非推奨 | 推奨(デフォルト) |
| 設定場所 | Dockerが直接作成 | systemd scope/slice |
| cgroup v2互換性 | 限定的 | 完全対応 |
+---------------------------+---------------------+---------------------+
// /etc/docker/daemon.json
// systemd cgroup driver設定
{
"exec-opts": ["native.cgroupdriver=systemd"],
"log-driver": "json-file",
"log-opts": {
"max-size": "100m"
},
"storage-driver": "overlay2"
}
5. Kubernetesとcgroup
kubelet cgroup driver設定
# kubelet設定(/var/lib/kubelet/config.yaml)
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd # 推奨
cgroupsPerQOS: true # QoS別cgroup hierarchy作成
enforceNodeAllocatable:
- pods
- system-reserved
- kube-reserved
kubeReserved:
cpu: '500m'
memory: '1Gi'
systemReserved:
cpu: '500m'
memory: '1Gi'
Pod QoSクラスとcgroup
KubernetesはPodのrequests/limits設定に基づいて3つのQoSクラスを付与する。
Pod QoSクラス決定ロジック:
1. Guaranteed:すべてのコンテナでrequests == limitsに設定
- 最高優先度、OOM score = -997
2. Burstable:requests < limits(一部でも)
- 中優先度、OOM score = 2~999
3. BestEffort:requests/limitsともに未設定
- 最低優先度、OOM score = 1000
# Guaranteed Pod例
apiVersion: v1
kind: Pod
metadata:
name: guaranteed-pod
spec:
containers:
- name: app
image: nginx
resources:
requests:
cpu: '500m'
memory: '256Mi'
limits:
cpu: '500m' # requests == limits
memory: '256Mi' # requests == limits
# Burstable Pod例
apiVersion: v1
kind: Pod
metadata:
name: burstable-pod
spec:
containers:
- name: app
image: nginx
resources:
requests:
cpu: '250m'
memory: '128Mi'
limits:
cpu: '500m' # requests < limits
memory: '256Mi'
Kubernetesが作成するcgroup hierarchy
/sys/fs/cgroup/
└── kubepods.slice/ # 全Pod
├── kubepods-burstable.slice/ # Burstable QoS
│ └── kubepods-burstable-podABCD.slice/ # Pod単位
│ ├── cri-containerd-XXXX.scope # コンテナ単位
│ │ ├── cpu.max # limits.cpu
│ │ ├── cpu.weight # requests.cpu基準
│ │ ├── memory.max # limits.memory
│ │ ├── memory.min # requests.memory(v2)
│ │ └── pids.max # pod pid limit
│ └── cri-containerd-YYYY.scope
├── kubepods-besteffort.slice/ # BestEffort QoS
│ └── kubepods-besteffort-podEFGH.slice/
└── kubepods-podIJKL.slice/ # Guaranteed QoS
└── cri-containerd-ZZZZ.scope
Kubernetesリソース要求がcgroupにマッピングされる仕組み
Kubernetes -> cgroup v2 マッピング:
resources.requests.cpu: 500m
-> cpu.weight =(500m / 総allocatable CPU)に比例
-> 競合時にPodが保証されるCPU比率
resources.limits.cpu: 1000m
-> cpu.max = "100000 100000"(100ms/100ms = 1 CPU)
resources.requests.memory: 256Mi
-> memory.min = 268435456(Guaranteed QoS in v2)
resources.limits.memory: 512Mi
-> memory.max = 536870912
pid limit(kubelet設定):
-> pids.max = podPidsLimit(デフォルト-1、無制限)
ノードリソース予約
ノードリソース分配:
総ノードリソース(例:16 CPU、64Gi Memory)
├── kube-reserved : kubelet、kube-proxyなど(0.5 CPU、1Gi)
├── system-reserved : sshd、journaldなど(0.5 CPU、1Gi)
├── eviction-threshold: 回収閾値(memory.available=100Mi)
└── allocatable : Podに割り当て可能なリソース(15 CPU、61.9Gi)
allocatable = total - kube-reserved - system-reserved - eviction-threshold
cgroup v2 + Kubernetes:MemoryQoS
Kubernetes 1.22+のMemoryQoS機能(alpha/beta)はcgroup v2のmemory.highを活用する。
MemoryQoSの動作:
Burstable Pod(requests: 256Mi、limits: 512Mi)
cgroup v2設定:
memory.min = 0 (BestEffort保護なし)
memory.low = 0 (デフォルト)
memory.high = 268435456(requests基準、throttleポイント)
memory.max = 536870912(limits基準、OOMポイント)
メモリ使用量がrequests(256Mi)を超えると:
-> memory.highによりthrottle(割り当て速度低下)
-> すぐにOOM killされない
-> limits(512Mi)を超えるとOOM kill
6. cgroup実践トラブルシューティング
6.1 CPUスロットリングの確認
# コンテナのCPUスロットリング確認
# PodのcgroupパスCGROUP_PATH
CGROUP_PATH=$(cat /proc/1/cgroup | grep -oP '(?<=::).*')
# cpu.statを確認
cat /sys/fs/cgroup${CGROUP_PATH}/cpu.stat
# usage_usec 45000000
# user_usec 40000000
# system_usec 5000000
# nr_periods 1000
# nr_throttled 350 <- 35%のスロットル比率!
# throttled_usec 15000000
# スロットル比率の計算
# throttle_ratio = nr_throttled / nr_periods
# 350 / 1000 = 35% -> 高い!limits増加を検討必要
CPUスロットリング解決戦略
CPUスロットリング診断フロー:
1. nr_throttled / nr_periods > 5% か?
├── Yes -> 2へ進む
└── No -> CPUスロットリングは問題ではない
2. 平均CPU使用量がlimitsに近いか?
├── Yes -> limits増加が必要
└── No -> burstパターンの問題 -> 3へ進む
3. 短時間のCPU burstが発生しているか?
├── Yes -> cpu.max.burstを設定(kernel 5.14+)
│ またはCPU limits削除を検討
└── No -> period調整を検討
6.2 OOM Killerトラブルシューティング
# 1. dmesgでOOMイベントを確認
dmesg | grep -i "killed process"
# [12345.678] Killed process 1234 (java) total-vm:4096000kB,
# anon-rss:524288kB, file-rss:8192kB, shmem-rss:0kB,
# UID:1000 pgrp:1234
# 2. cgroupメモリイベントを確認
cat /sys/fs/cgroup/kubepods.slice/.../memory.events
# low 0
# high 500 # memory.high超過回数
# max 10 # memory.max超過回数
# oom 2 # OOM発生回数
# oom_kill 2 # OOM kill回数
# 3. 現在のメモリ使用量 vs 制限
cat /sys/fs/cgroup/kubepods.slice/.../memory.current
# 524288000 (500MB)
cat /sys/fs/cgroup/kubepods.slice/.../memory.max
# 536870912 (512MB) <- ほぼ到達!
# 4. メモリ使用量の詳細分析
cat /sys/fs/cgroup/kubepods.slice/.../memory.stat | head -10
OOM解決戦略
OOM Kill診断フロー:
1. memory.eventsでoom_kill > 0を確認
├── oom_kill増加中 -> 2へ進む
└── oom_kill = 0 -> OOM問題ではない
2. memory.current vs memory.maxを比較
├── current >= max * 0.9 -> メモリ不足
│ ├── anonが高い -> ヒープメモリリークの可能性 -> プロファイリング
│ ├── fileが高い -> ファイルキャッシュ過多 -> 正常な場合あり
│ └── shmemが高い -> 共有メモリ使用を確認
└── current << max -> 急激な割り当てパターンを確認
3. 解決策:
a) memory limits増加
b) アプリケーションのメモリリーク修正
c) JVM:-XX:MaxRAMPercentage=75設定
d) memory.high設定でsoft throttleを適用
6.3 プロセスのcgroup確認
# 特定プロセスのcgroup確認
cat /proc/PID/cgroup
# v2出力:0::/kubepods.slice/kubepods-burstable.slice/...
# v1出力:
# 12:pids:/docker/abc123
# 11:memory:/docker/abc123
# 10:cpu,cpuacct:/docker/abc123
# systemd-cgls:cgroupツリーを表示
systemd-cgls
# Control group /:
# -.slice
# ├─user.slice
# │ └─user-1000.slice
# ├─system.slice
# │ ├─docker.service
# │ └─sshd.service
# └─kubepods.slice
# ├─kubepods-burstable.slice
# └─kubepods-besteffort.slice
6.4 systemd-cgtop:リアルタイムcgroupリソースモニタリング
# systemd-cgtop:topに似たcgroupモニタリング
systemd-cgtop
# Control Group Tasks %CPU Memory Input/s Output/s
# / 235 15.2 3.5G - -
# /system.slice 45 5.1 1.2G - -
# /kubepods.slice 120 8.3 2.0G - -
# /kubepods.slice/burstable 80 6.1 1.5G - -
6.5 cAdvisorとPrometheusメトリクス
主要cAdvisor Prometheusメトリクス:
CPU:
container_cpu_usage_seconds_total # 累積CPU使用時間
container_cpu_cfs_throttled_seconds_total # 累積スロットル時間
container_cpu_cfs_periods_total # 総CFS period数
container_cpu_cfs_throttled_periods_total # スロットルされたperiod数
Memory:
container_memory_usage_bytes # 現在のメモリ使用量
container_memory_working_set_bytes # working set(OOM判定基準)
container_memory_rss # RSS(実際の物理メモリ)
container_memory_cache # ページキャッシュ
OOM:
container_oom_events_total # OOMイベント回数
kube_pod_container_status_last_terminated_reason # OOMKilledなど
有用なPromQLクエリ
# CPUスロットル比率(5分平均)
rate(container_cpu_cfs_throttled_periods_total[5m])
/ rate(container_cpu_cfs_periods_total[5m])
# メモリ使用率(limits対比)
container_memory_working_set_bytes
/ container_spec_memory_limit_bytes
# OOM Killが発生したPod
kube_pod_container_status_last_terminated_reason{reason="OOMKilled"}
7. cgroupとセキュリティ
コンテナ隔離の3要素
コンテナセキュリティ隔離レイヤー:
+--------------------------------------------------+
| Host Kernel |
| |
| ┌──────────┐ ┌──────────┐ ┌──────────┐ |
| │ Namespace│ │ cgroup │ │ Seccomp/ │ |
| │ (隔離) │ │ (制限) │ │ AppArmor │ |
| │ │ │ │ │(セキュリ│ |
| │ - PID │ │ - CPU │ │ ティ) │ |
| │ - NET │ │ - Memory │ │ - syscall│ |
| │ - MNT │ │ - IO │ │ フィルタ│ |
| │ - USER │ │ - PIDs │ │ - MAC │ |
| └──────────┘ └──────────┘ └──────────┘ |
| |
+--------------------------------------------------+
3つすべてが揃って初めて安全な隔離!
CVE-2022-0492:cgroup escape脆弱性
CVE-2022-0492はcgroup v1のrelease_agentメカニズムを悪用したコンテナ脱出脆弱性である。
攻撃原理:
1. release_agentはcgroupの最後のプロセスが終了時に実行されるプログラム
2. 脆弱性:cgroup_release_agent_write()で権限検証が欠落
3. 攻撃者がunshare()で新しいuser/cgroup namespaceを作成
4. 書き込み可能なcgroupfsをマウント
5. release_agentにホストで実行するバイナリパスを設定
6. ホスト権限で任意コード実行
防御:
- Seccomp + AppArmor/SELinuxが適用されていれば防御可能
- unshare() syscallをブロック、または
- cgroupfsマウントを制限
rootlessコンテナとcgroup v2 delegation
# cgroup v2でrootlessコンテナが動作するには
# ユーザーにcgroupサブツリーを委任する必要がある
# systemdで委任設定
sudo systemctl edit user@1000.service
# [Service]
# Delegate=cpu memory pids io
# またはユーザー単位で設定
mkdir -p /etc/systemd/system/user@.service.d/
cat > /etc/systemd/system/user@.service.d/delegate.conf << 'CONF'
[Service]
Delegate=cpu cpuset io memory pids
CONF
systemctl daemon-reload
Kubernetes Pod Security Standardsとcgroup
# Pod Security Standard: restricted
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: nginx
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ['ALL']
readOnlyRootFilesystem: true
resources:
limits: # cgroupリソース制限は必須
cpu: '500m'
memory: '256Mi'
requests:
cpu: '250m'
memory: '128Mi'
8. まとめ
cgroup v1からv2マイグレーションチェックリスト
- カーネルバージョン確認:5.8+推奨(PSI、CPU burstなど)
- systemdバージョン確認:236+
- コンテナランタイム確認:Docker 20.10+、containerd 1.4+
- Kubernetesバージョン確認:1.25+(GA)
- GRUBブートパラメータ設定
- kubelet cgroup driverをsystemdに変更
- モニタリングツールの互換性確認(cAdvisor、Prometheus)
- カスタムcgroupスクリプトがあればv2インターフェースに更新
- テスト環境で十分に検証してからプロダクションに適用
プロダクション推奨設定
# Kubernetesノード推奨設定
# kubelet config
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
cgroupsPerQOS: true
podPidsLimit: 1024 # Fork bomb防止
enforceNodeAllocatable:
- pods
- system-reserved
- kube-reserved
kubeReserved:
cpu: '500m'
memory: '1Gi'
ephemeral-storage: '1Gi'
systemReserved:
cpu: '500m'
memory: '1Gi'
ephemeral-storage: '1Gi'
evictionHard:
memory.available: '100Mi'
nodefs.available: '10%'
imagefs.available: '15%'
核心コマンドチートシート
# === 情報確認 ===
cat /proc/self/cgroup # 現在のプロセスcgroup
stat -fc %T /sys/fs/cgroup/ # v1(tmpfs) vs v2(cgroup2fs)
cat /sys/fs/cgroup/cgroup.controllers # 利用可能コントローラー一覧
systemd-cgls # cgroupツリー表示
systemd-cgtop # リアルタイムcgroupモニタリング
# === CPU ===
cat /sys/fs/cgroup/PATH/cpu.max # CPU quota/period
cat /sys/fs/cgroup/PATH/cpu.weight # CPU weight
cat /sys/fs/cgroup/PATH/cpu.stat # スロットル統計
# === Memory ===
cat /sys/fs/cgroup/PATH/memory.max # ハード制限
cat /sys/fs/cgroup/PATH/memory.current # 現在の使用量
cat /sys/fs/cgroup/PATH/memory.stat # 詳細統計
cat /sys/fs/cgroup/PATH/memory.events # OOMイベント
# === IO ===
cat /sys/fs/cgroup/PATH/io.max # IO制限
cat /sys/fs/cgroup/PATH/io.stat # IO統計
# === PID ===
cat /sys/fs/cgroup/PATH/pids.max # PID制限
cat /sys/fs/cgroup/PATH/pids.current # 現在のPID数
# === Docker ===
docker stats # リアルタイムリソースモニタリング
docker inspect CONTAINER | grep -i cgroup # cgroup設定確認
# === トラブルシューティング ===
dmesg | grep -i "killed process" # OOM killログ
cat /proc/PID/cgroup # プロセスcgroup確認
cat /proc/PID/oom_score # OOMスコア確認
クイズ:cgroup理解度テスト
Q1. cgroup v1とv2の最大の構造的違いは?
A:v1はコントローラーごとに独立したhierarchyを持つが、v2は単一統合(unified)hierarchyを使用する。v2では一つのプロセスがすべてのコントローラーで同じcgroupに属する。
Q2. Kubernetes Pod QoSクラスの「Guaranteed」になるために必要な条件は?
A:すべてのコンテナでCPUとメモリのrequestsとlimitsが同一に設定されていなければならない。
Q3. cgroup v2のmemory.highとmemory.maxの違いは?
A:memory.highはソフト制限で、超過時にプロセスをthrottle(速度制限)する。memory.maxはハード制限で、超過時にOOM Killerが発動する。
Q4. CPUスロットル比率が35%の場合、何を意味するか?
A:全CFS periodのうち35%でCPU quotaをすべて消費してプロセスが実行できなかったことを意味する。CPU limitsの増加またはCPU burst設定の検討が必要。
Q5. Dockerでsystemd cgroup driverがcgroupfsより推奨される理由は?
A:systemdがinitシステムとして動作する場合、cgroupfsとsystemdの2つのcgroup管理者が同時に存在するとリソース圧迫時にシステムが不安定になる可能性がある。systemd driverを使用すれば単一のcgroup管理者に統合される。
Q6. CVE-2022-0492はどのcgroupメカニズムを悪用するか?
A:cgroup v1のrelease_agentメカニズムを悪用する。release_agentはcgroupの最後のプロセス終了時に実行されるプログラムで、権限検証の欠落によりコンテナからホスト権限で任意コードを実行できた。
参考資料
- Linux Kernel Documentation: Control Group v2
- Kubernetes Documentation: About cgroup v2
- Red Hat: Migrating from CGroups V1 to V2
- CFS Bandwidth Control - Linux Kernel Documentation