Зачем cgroups v2
[[cgroups|cgroups v1]] (с 2007) - per-controller hierarchy: для
каждого resource (cpu, memory, blkio, net_cls...) - отдельное дерево
в /sys/fs/cgroup/<controller>/.... Процесс мог быть в разных
cgroup'ах в каждой иерархии.
Это породило проблемы:
- Несогласованность семантики между controllers - cpuset работал одним, memory другим способом
- Сложно делегировать управление - sub-tree contains только subset controllers
- Нельзя надёжно описать "лимиты для процесса" - нужно вычислять intersection
- net_cls/net_prio устарели в пользу eBPF
В 2016 (kernel 4.5) появился cgroups v2 с единым деревом и чистой семантикой. После 5 лет полировки cgroups v2 - default в:
- systemd 247+ (2020-) - hybrid mode
- RHEL 9 (2022) - pure v2
- Ubuntu 21.10+ - pure v2
- Kubernetes 1.25+ - support
- Docker 20.10+ - поддержка
Unified hierarchy
Единое дерево в /sys/fs/cgroup/:
/sys/fs/cgroup/
├── cgroup.controllers # доступные controllers
├── cgroup.subtree_control # которые включены для children
├── system.slice/
│ ├── cgroup.controllers
│ ├── cpu.weight
│ ├── memory.high
│ ├── memory.max
│ ├── nginx.service/
│ │ ├── cpu.stat
│ │ ├── memory.current
│ │ └── pids.current
│ └── postgresql.service/
│ └── ...
└── user.slice/
└── user-1000.slice/
└── session-1.scope/
Каждый узел - directory с одинаковым набором файлов (зависит от включённых controllers).
Процесс находится только в одном узле дерева (через
cgroup.procs).
Controllers - что доступно в v2
| Controller | v1 эквивалент | Что делает |
|---|---|---|
| cpu | cpu+cpuacct | weight, max, period - CPU shares и hard limit |
| cpuset | cpuset | привязка к CPU/NUMA-node |
| memory | memory | usage, low, high, max, soft limit |
| io | blkio | weight, max BW, BW limits |
| pids | pids | ограничение по числу процессов |
| rdma | rdma | InfiniBand resources |
| misc | - | gpu hpu - кастомизируемые лимиты |
| hugetlb | hugetlb | huge page allocations |
v1 controllers, которых нет в v2 (deprecated):
net_cls,net_prio→ перенесли в [[ebpf-basics|eBPF cgroup-attached programs]]freezer→ теперь черезcgroup.freezeфайлdevices→ через eBPF (BPF_PROG_TYPE_CGROUP_DEVICE)perf_event,cpuacct→ внутри cpu
Включить controller в subtree - писать в cgroup.subtree_control:
echo "+memory +pids" > /sys/fs/cgroup/system.slice/cgroup.subtree_control
Внутри children теперь доступны memory.* и pids.* файлы.
Memory controller - новые fields
В v2 управление памятью более гибкое:
| File | Семантика |
|---|---|
memory.current | текущее использование, байты |
memory.min | reserved, никогда не reclaim'ится (если есть chance) |
memory.low | best-effort protected - reclaim только под pressure |
memory.high | soft limit - kernel начинает throttling/reclaim |
memory.max | hard limit - превышение = OOM в этой cgroup |
memory.swap.max | swap-лимит отдельно |
memory.events | counters: low, high, max, oom, oom_kill |
memory.stat | детальный breakdown (anon, file, slab, sock, ...) |
memory.high - главная новая ручка - в отличие от v1 hard-limit:
- Превышение → kernel замедляет cgroup (sleep на page-fault), активно reclaim'ит page cache, но не убивает процессы
- Если давление длится - eventually OOM-killer (oom-killer)
Это даёт graceful backpressure вместо OOMK-цикла.
CPU controller - weight и max
cpu.weight # 1-10000 (default 100), относительный вес
cpu.max # "max <quota> <period>", напр. "100000 100000" = 1 CPU
cpu.stat # usage_usec, user_usec, system_usec, throttled_usec
В v1 было два контролятора: cpu (weight) + cpuacct (statistics). В v2 объединено.
I/O controller - weight, max, cost-based
io.weight # default 100, относительный
io.max # rbps/wbps/riops/wiops лимиты per-device
io.stat # счётчики per-device
io.cost.qos # cost-based QoS (RHEL 9+)
io.cost (новинка): не лимиты в IOPS/bps, а "cost budget" с учётом device performance characteristics. Лучше адаптируется к разным дискам (NVMe vs HDD).
PSI - Pressure Stall Information
В v1 нельзя было сказать: "перегружена ли система или конкретная cgroup?" Лоад average был один на хост, неточный.
PSI (Linux 4.20+) - три файла на root и каждой cgroup:
/proc/pressure/cpu- CPU pressure/proc/pressure/memory- память/proc/pressure/io- I/O
Формат:
cat /sys/fs/cgroup/system.slice/postgresql.service/io.pressure
some avg10=12.34 avg60=8.91 avg300=5.12 total=12345678
full avg10=3.45 avg60=2.10 avg300=1.05 total=3456789
- some - хоть один task в cgroup ждёт ресурс
- full - все tasks ждут (cgroup полностью застряла)
- avg10/60/300 - проценты времени за 10/60/300 секунд
- total - microseconds total
Применение:
- Auto-scaling Kubernetes по PSI вместо CPU%
- Out-of-memory prediction - если memory.pressure full > 50% - вот-вот OOM
- systemd-oomd использует PSI для proactive killing задолго до [[oom-killer|OOM]]
systemd и cgroup делегирование
systemd - единственный writer в cgroup-дерево по умолчанию:
system.slice- системные сервисыuser.slice- пользовательские сессииmachine.slice- VM/контейнеры (machinectl)
Каждый unit получает свой cgroup. Лимиты задаются unit-свойствами:
[Service]
CPUWeight=200
MemoryHigh=512M
MemoryMax=1G
IOWeight=300
TasksMax=200
Эквивалентно записям в cgroup.subtree_control + *.max/weight.
Делегирование под-tree другому процессу (Docker, k8s):
Delegate=yes
Тогда process может сам создавать sub-cgroups и менять лимиты в своём sub-дереве. Используется для контейнеров.
eBPF + cgroup - программируемый control
v2 интегрируется с [[ebpf-basics|eBPF]] через cgroup-attached программы:
BPF_PROG_TYPE_CGROUP_SKB- L3/L4 фильтрация per-cgroupBPF_PROG_TYPE_CGROUP_SOCK- control при создании socketBPF_PROG_TYPE_CGROUP_SOCKOPT- перехват setsockopt/getsockoptBPF_PROG_TYPE_CGROUP_DEVICE- device whitelistBPF_PROG_TYPE_LSM_CGROUP- LSM hook per-cgroup
Заменяет старые v1 controllers (devices, net_cls/net_prio).
Cilium использует cgroup-eBPF для service routing per-pod.
Прицепляется через bpftool cgroup attach:
bpftool cgroup attach /sys/fs/cgroup/system.slice/myapp.service \
skb_egress my_prog.bpf.o sec egress
v1 vs v2 - сравнение
| Свойство | v1 | v2 |
|---|---|---|
| Дерево | per-controller | unified |
| Process в нескольких? | да (один на controller) | нет |
| Threaded mode | нет | да (cgroup.type = threaded) |
| Soft memory limit | да (memory.soft_limit_in_bytes) | нет (используй memory.high) |
| OOM behavior | OOM в cgroup | + memory.high throttling |
| PSI | нет | да |
| eBPF integration | минимальная | первоклассная |
| Default in distros (2025) | RHEL 7-8, Ubuntu < 21 | RHEL 9, Ubuntu 21+, всё новое |
Hybrid mode - переход
systemd может работать в hybrid mode: v1 для legacy controllers
(cpuset, freezer) и v2 для новых. Файл /sys/fs/cgroup/cgroup.controllers
показывает только controllers в v2-дереве. Этот режим default в
Ubuntu 20.04, RHEL 8.
С systemd.unified_cgroup_hierarchy=1 (kernel cmdline) - чистый v2.
Проверить:
stat -fc %T /sys/fs/cgroup/
# cgroup2fs = pure v2
# tmpfs = hybrid v1+v2
Когда что-то пошло не так
cgroup.subtree_controlпустой - нет включённых controllers. Не получится создать child с*.maxфайлами. Сначала включи в parent:echo "+cpu +memory" > cgroup.subtree_control.- No space left on device при создании cgroup - kernel.threads-max или pids.max в parent. Проверь.
- Контейнер не видит лимиты - старый container runtime, который знает только v1. crun/containerd >= 1.5, runc >= 1.0 знают v2.
- systemd-oomd убивает не то - проверь
oomd.conf, по умолчанию оценивает по 50% memory pressure. cpu.weight=1000не даёт priority - другие cgroups в parent тоже подняли weight. Это относительная scheduling, проверь siblings.- PSI везде нули - kernel < 4.20 или PSI не включён
(
CONFIG_PSI=y). Может быть отключён в cmdlinepsi=0. - kubelet ругается на cgroup driver - mismatch между runtime
(cgroupfs vs systemd). Поставь обоих на
systemd.
Полезные команды/файлы
systemd-cgls- дерево cgroup'ов с unit'амиsystemd-cgtop- top по cgroup-resource usagesystemctl set-property nginx.service MemoryMax=1G- runtime changecat /sys/fs/cgroup/.../cgroup.procs- какие PID в cgroupcat /proc/<pid>/cgroup- в какой cgroup процессcgroup.events- notifications (low, high, max, oom, populated)