Skip to content

$ cat ./posts/arch/understanding-cgroups-v2.md

-rwxr-xr-x 2.7K #arch #kernel #systemd

Understanding cgroups v2

by chris · 2 min read · /arch /kernel


cgroups v2 finally makes resource control unified. One hierarchy instead of the v1 mess of per-controller trees, one writer per subtree, and a delegation model that survives contact with systemd.

Before touching any knob, look at what already exists:

systemd-cgls --no-pager
├─user.slice
│ └─user-1000.slice
│   └─app.slice
│     └─code.scope
│       └─1234 nvim
└─system.slice
  └─docker.service
    └─abc123 nginx

1. Why v2?

v1 had race conditions and delegated controllers inconsistently. Each controller carried its own hierarchy, so a process could sit in one place for memory accounting and somewhere else entirely for CPU. v2 enforces a single unified tree and proper delegation through cgroup.subtree_control.

The practical consequence: you can hand a subtree to an unprivileged manager and trust that it cannot escape. That is what makes rootless containers workable.

2. Limits that matter

Three interfaces cover almost every real workload:

  • memory.max — a hard limit with reclaim before the OOM killer runs.
  • cpu.max — a quota and period pair rather than an abstract share value.
  • io.max — per-device bytes-per-second and IOPS ceilings.

Setting them through systemd keeps the state declarative instead of scattered across boot scripts:

# limit a slice to 512M + 50% CPU
sudo systemctl set-property app.slice MemoryMax=512M CPUQuota=50%
cat /sys/fs/cgroup/app.slice/memory.peak

Read memory.peak after a load test rather than guessing. Most limits people set are wrong by an order of magnitude in one direction or the other.

3. Pressure Stall Information

PSI tells you when tasks are stalled waiting on a resource, which is far more useful than load average for autoscaling decisions. Load average counts runnable tasks; PSI counts lost time.

cat /proc/pressure/memory
some avg10=1.23 avg60=0.42 total=123456
full avg10=0.11 avg60=0.04 total=23456

The some line means at least one task stalled; full means every task stalled. If full avg10 stays above two percent, your containers are throttling and no amount of replica tuning will hide it.

4. What to monitor

Export memory.peak, cpu.stat throttling counters and the PSI totals per slice. Three numbers per workload are enough to tell whether a limit is doing its job or quietly strangling the service.

← cd .. fris@linux:~/blog$ man arch