DevOpsIndex

cgroup v2 — Resource Control

cgroup v2 is the unified hierarchy used by all modern Linux distributions and Kubernetes (>= 1.25 with containerd).


Hierarchy

/sys/fs/cgroup/                      ← cgroup v2 root
├── cpu.max                          ← system-wide CPU limit (max)
├── memory.max
├── kubepods.slice/                  ← K8s pods
│   ├── besteffort.slice/
│   ├── burstable.slice/
│   └── guaranteed.slice/
│       └── pod<uid>/
│           └── <container-id>/
│               ├── cpu.max          ← CPU quota
│               ├── cpu.weight       ← relative CPU shares
│               ├── memory.max       ← hard memory limit
│               ├── memory.high      ← soft limit (triggers reclaim)
│               ├── memory.current   ← current RSS
│               ├── memory.events    ← OOM event counter
│               ├── io.max           ← I/O bandwidth limit
│               └── pids.max         ← PID limit
└── system.slice/                    ← systemd services

CPU Control

cpu.max — Quota (hard limit)

cat /sys/fs/cgroup/kubepods.slice/.../cpu.max
# 50000 100000
# format: quota period (microseconds)
# 50000/100000 = 0.5 cores = 500m in K8s notation

# Set: 1 CPU out of every 100ms window
echo "100000 100000" > cpu.max

# Unlimited
echo "max 100000" > cpu.max

cpu.weight — Shares (relative scheduling)

cat cpu.weight
# 100  ← default weight (range 1-10000)

# Double this cgroup's CPU share vs default
echo 200 > cpu.weight

Throttling detection

cat cpu.stat
# usage_usec 123456789      ← total CPU time consumed
# user_usec  100000000
# system_usec 23456789
# nr_periods  1000           ← scheduler periods elapsed
# nr_throttled 234           ← periods where cgroup was throttled
# throttled_usec 4567890     ← total time throttled

# Throttle % = nr_throttled / nr_periods × 100
# > 5% sustained = CPU limit too low for workload

K8s mapping:

K8s field cgroup file Formula
resources.limits.cpu: "500m" cpu.max 50000 100000
resources.requests.cpu: "250m" cpu.weight round(1024 × 0.25) = 256

Memory Control

# Hard limit — exceeding this triggers OOM kill
cat memory.max       # e.g. 536870912 (512Mi)

# Soft limit — triggers aggressive reclaim before OOM
cat memory.high      # e.g. 483183820 (~90% of max)

# Current RSS
cat memory.current

# OOM events
cat memory.events
# low 0
# high 14          ← hit memory.high 14 times (reclaim triggered)
# max 0
# oom 0            ← full OOM kills
# oom_kill 0

# Kill entire cgroup on OOM (not just one process)
cat memory.oom.group   # 1 = enabled in K8s containers

Memory Pressure Files (PSI)

PSI (Pressure Stall Information) measures how much time tasks are stalled waiting for a resource. Available for cpu, memory, io.

cat /proc/pressure/memory
# some avg10=0.50 avg60=1.20 avg300=0.80 total=12345678
# full avg10=0.10 avg60=0.30 avg300=0.20 total=3456789
#
# some = at least one task stalled
# full = ALL tasks stalled (severe — no progress at all)
# avg10/60/300 = % of time stalled over 10s/60s/5m windows

cat /proc/pressure/cpu
cat /proc/pressure/io

# Also per-cgroup:
cat /sys/fs/cgroup/kubepods.slice/pod<uid>/<cid>/memory.pressure

Thresholds to alert on:

PSI metric Warning Critical
memory some avg60 > 10% > 30%
memory full avg60 > 1% > 5%
io full avg60 > 5% > 20%

I/O Control

# Format: MAJ:MIN rbps wbps riops wiops
cat io.max
# 8:0 rbps=10485760 wbps=10485760 riops=max wiops=max
# 10 MB/s read + write, unlimited IOPS

# Set limits (device 8:0)
echo "8:0 rbps=52428800 wbps=52428800" > io.max   # 50MB/s

# I/O usage stats
cat io.stat
# 8:0 rbytes=123456 wbytes=654321 rios=100 wios=200 dbytes=0 dios=0

PID Limiting

# Prevent fork bombs
cat pids.max    # e.g. 1024
cat pids.current

# Set limit
echo 512 > pids.max

Hands-on: Inspect a Running Container

# Find container's cgroup path
CONTAINER_ID=$(docker inspect --format '{{.Id}}' mycontainer)
CGROUP=/sys/fs/cgroup/system.slice/docker-${CONTAINER_ID}.scope

# CPU quota and current throttling
cat $CGROUP/cpu.max
cat $CGROUP/cpu.stat | grep throttled

# Memory usage vs limit
echo "Limit: $(cat $CGROUP/memory.max)"
echo "Usage: $(cat $CGROUP/memory.current)"
echo "OOM events: $(grep oom_kill $CGROUP/memory.events)"

# Is this container under memory pressure right now?
cat $CGROUP/memory.pressure

cgroup v1 vs v2

Feature v1 v2
Hierarchy Multiple trees per subsystem Single unified tree
CPU shares file cpu.shares cpu.weight
CPU quota file cpu.cfs_quota_us cpu.max
Memory limit memory.limit_in_bytes memory.max
PSI support No Yes
Writeback control No Yes
K8s default since < 1.25 >= 1.25 (containerd)
# Check which version is active
stat -f -c %T /sys/fs/cgroup
# cgroup2fs → v2
# tmpfs     → v1 (hybrid if /sys/fs/cgroup/unified exists)