Skip to content
Published on

[Linux] cgroup完全ガイド:コンテナリソース制御の核心

Authors

目次

  1. cgroupとは?
  2. cgroup v1 vs v2
  3. 主要コントローラー深層分析
  4. Dockerとcgroup
  5. Kubernetesとcgroup
  6. cgroup実践トラブルシューティング
  7. cgroupとセキュリティ
  8. まとめ

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

コンテナ技術の二本柱はNamespacecgroupである。

+-------------------------------------------+
|      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 v1cgroup v2
Hierarchyコントローラー別独立単一統合(Unified)
マウントポイント/sys/fs/cgroup/cpu/、/sys/fs/cgroup/memory/ 等/sys/fs/cgroup/ のみ
プロセス配置各コントローラーで異なるグループ可能全コントローラーで同一グループ
PSI(Pressure Stall Info)非対応対応
Threadedモード非対応対応
サブツリー委任限定的完全なdelegationモデル
Memory QoSmemory.limitのみmemory.min/low/high/maxの4段階
IO制御blkio(Direct IOのみ)io(Buffered IO対応)
CPU burst非対応(カーネルパッチ必要)対応(kernel 5.14+)

v2マイグレーション互換性

ソフトウェアcgroup v2サポート開始バージョン
systemd236+
Docker20.10+
containerd1.4+
Kubernetes1.25+(GA)
Podmanネイティブ対応
RHEL9+(デフォルト)
Ubuntu21.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