migration2024-08-05·14 min·280/348

cgroup v2 Migration Guide: Upgrading Linux Resource Control

Step-by-step guide to migrating from cgroup v1 to cgroup v2 on Linux, including compatibility checks, migration procedures, and troubleshooting.

Introduction

cgroup v2 is the unified hierarchy for Linux resource control, replacing the fragmented cgroup v1 approach. It provides consistent resource management, better memory accounting, and native support for pressure stall information (PSI). Most modern Linux distributions support cgroup v2, and migrating from v1 brings benefits in resource isolation and observability. This post covers the migration process.

Environment

The examples migrate from cgroup v1 on Ubuntu 20.04 LTS to cgroup v2 on Ubuntu 22.04 LTS. The system runs Docker containers and systemd services with resource limits.

# Check current cgroup version
stat -f -c %T /sys/fs/cgroup/
# cgroup2fs    # cgroup v2
# tmpfs        # cgroup v1 (mounted as tmpfs)

# Or check mount points
mount | grep cgroup
# cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw,nosuid,nodev,noexec)

Problem

The system is running cgroup v1, but you need cgroup v2 for:

  • Better memory accounting
  • Pressure stall information (PSI)
  • Improved container isolation
  • systemd v252+ features
  • Modern Kubernetes requirements
# cgroup v1 hierarchy (fragmented)
ls /sys/fs/cgroup/
# blkio  cpu  cpuacct  cpuset  devices  freezer  memory  net_cls  net_prio  pids

# cgroup v2 hierarchy (unified)
ls /sys/fs/cgroup/
# cgroup.controllers  cgroup.max.depth  cgroup.max.descendants
# cgroup.procs        cgroup.stat       cpu.max

Analysis

cgroup v1 vs v2 differences:

Featurecgroup v1cgroup v2
HierarchyMultiple separateSingle unified
Memory accountingPer controllerUnified
Pressure informationNot availablePSI support
Thread supportLimitedFull support
Socket-level controlNot availablesocket-level BPF

Check which version your kernel supports:

# Check kernel config
grep -i cgroup /boot/config-$(uname -r)
# CONFIG_CGROUPS=y
# CONFIG_CGROUP_V2=y

Solution

1. Check prerequisites

# Verify kernel version (need 4.15+ for basic, 5.2+ for full support)
uname -r
# 5.15.0-91-generic

# Check if kernel supports cgroup v2
grep -i cgroup /boot/config-$(uname -r) | grep -i v2
# CONFIG_CGROUP_V2=y

# Check systemd version (need 232+)
systemctl --version
# systemd 249

2. Enable cgroup v2 at boot

# Edit GRUB configuration
sudo nano /etc/default/grub

# Add to GRUB_CMDLINE_LINUX
GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=1"

# Update GRUB and reboot
sudo update-grub
sudo reboot

3. Verify cgroup v2 is active

# After reboot
stat -f -c %T /sys/fs/cgroup/
# cgroup2fs

mount | grep cgroup
# cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)

4. Migrate Docker to cgroup v2

# Check Docker cgroup driver
docker info | grep -i cgroup
# Cgroup Driver: cgroupfs
# Cgroup Version: 2

# If still using cgroupfs, update daemon.json
sudo nano /etc/docker/daemon.json
# {
#   "exec-opts": ["native.cgroupdriver=cgroupfs"],
#   "cgroup-parent": "system.slice"
# }

sudo systemctl restart docker

5. Migrate systemd services

systemd automatically uses cgroup v2 when available. Verify service resource limits:

# Check a service's cgroup
systemctl show myapp.service | grep -i cgroup
# ControlGroup=/system.slice/myapp.service
# MemoryMax=5368709120

# Check cgroup controllers
cat /sys/fs/cgroup/system.slice/myapp.service/cgroup.controllers
# cpu io memory pids

6. Use new cgroup v2 features

# Check memory pressure (PSI)
cat /sys/fs/cgroup/system.slice/myapp.service/memory.pressure
# some avg10=0.00 avg60=0.00 avg300=0.00 total=0
# full avg10=0.00 avg60=0.00 avg300=0.00 total=0

# Check IO pressure
cat /sys/fs/cgroup/system.slice/myapp.service/io.pressure

# Check CPU pressure
cat /sys/fs/cgroup/system.slice/myapp.service/cpu.pressure

7. Set resource limits with cgroup v2

# Create a cgroup
sudo mkdir /sys/fs/cgroup/mygroup

# Set memory limit
echo "2G" | sudo tee /sys/fs/cgroup/mygroup/memory.max

# Set CPU limit (100ms per 1000ms = 10% CPU)
echo "100000 1000000" | sudo tee /sys/fs/cgroup/mygroup/cpu.max

# Set IO weight
echo "100" | sudo tee /sys/fs/cgroup/mygroup/io.weight

# Move process to cgroup
echo $PID | sudo tee /sys/fs/cgroup/mygroup/cgroup.procs

8. Check for compatibility issues

# Some applications may need updates for cgroup v2
# Check Docker container cgroup version
docker inspect --format '{{.HostConfig.Cgroupns}}' container_name

# For Kubernetes, check kubelet cgroup driver
kubectl get kubelet -o yaml | grep cgroup

9. Troubleshoot cgroup v2 migration

# If Docker fails after migration
sudo journalctl -u docker.service | tail -50

# Common error: cgroup not mounted
# Solution: Ensure cgroup2 is mounted
sudo mount -t cgroup2 none /sys/fs/cgroup

# Check for leftover cgroup v1 mounts
mount | grep cgroup
# Remove old mounts if needed

10. Validate the migration

# Run a stress test
docker run -d --memory=512m --cpus=1 nginx

# Check resource limits are enforced
docker stats --no-stream
# CONTAINER ID   NAME   CPU %   MEM USAGE / LIMIT
# 1234567890ab   nginx  0.01%   5.234MiB / 512MiB

# Check cgroup v2 files exist
ls /sys/fs/cgroup/system.slice/docker-*

11. Rollback if needed

# If issues occur, revert to cgroup v1
sudo nano /etc/default/grub
# Remove: systemd.unified_cgroup_hierarchy=1

sudo update-grub
sudo reboot

Lessons Learned

cgroup v2 migration is generally straightforward on modern systems. The main risks are compatibility with older applications and container runtimes. Always test in a staging environment first. The benefits of cgroup v2, especially PSI support and unified hierarchy, make the migration worthwhile. And remember that Docker and systemd handle cgroup v2 automatically when the kernel supports it.


This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.