Hello,
I am using LXC 6.0.4 on a Linux 6.12.74-2 / Debian 13 host and I am having a lot of difficulty enabling cgroup.subtree_control in my Alpine Linux containers. I have a pure unified cgroupv2 host and expected that in a container I would be able to use subtree_control to enable various controllers to control resource usage in child cgroups inside the container. The problem is that when LXC boots up Alpine, OpenRC’s init is placed into the root cgroup and thus prevents the container itself from setting cgroup.subtree_control as it isn’t possible to make modifications once a cgroup has processes in it (“no internal processes” rule). This then prevents any child cgroups from enabling controllers, causing issues for services like Docker when configured with resource limits as it has controllers enabled for its cgroups.
This is not an issue on systemd-based images, as systemd itself moves its own init process to a init.scope and enables controllers on the root cgroup. As Alpine Linux is popularly used for container images, I expected there to be some similar function to perform this as well, but I was unable to find anything. The /etc/init.d/cgroups script doesn’t help as it expects to be used on a non-container system as it tries to mount the cgroup2 and delegate controllers - again, too late in a container-booted environment.
LXC does seem to enable all available controllers for itself, however I could not find a way to enable delegation - I tried setting lxc.cgroup2.cgroup.subtree_control but it seems to execute too late as LXC has already spawned the init process into the payload cgroup.
lxc-start dev 20260407155630.568 TRACE sync - ../src/lxc/sync.c:lxc_sync_init:139 - Initialized synchronization infrastructure
lxc-start dev 20260407155630.568 TRACE cgfsng - ../src/lxc/cgroups/cgfsng.c:__cgroup_tree_create:726 - Created 8(lxc.payload.dev) cgroup
lxc-start dev 20260407155630.568 TRACE cgfsng - ../src/lxc/cgroups/cgfsng.c:__cgroup_tree_create:741 - Opened newly created cgroup lxc.payload.dev as 14
lxc-start dev 20260407155630.568 INFO cgfsng - ../src/lxc/cgroups/cgfsng.c:cgfsng_payload_create:1790 - The container process uses "lxc.payload.dev" as inner and "lxc.payload.dev" as limit cgroup
lxc-start dev 20260407155630.568 TRACE start - ../src/lxc/start.c:lxc_spawn:1714 - Spawned container directly into target cgroup via cgroup2 fd 14
lxc-start dev 20260407155630.568 TRACE start - ../src/lxc/start.c:lxc_spawn:1754 - Cloned child process 596376
lxc-start dev 20260407155630.568 TRACE start - ../src/lxc/start.c:core_scheduling:1572 - No new core scheduling domain requested
lxc-start dev 20260407155630.568 TRACE utils - ../src/lxc/utils.c:lxc_can_use_pidfd:1935 - Kernel supports pidfds
lxc-start dev 20260407155630.568 INFO start - ../src/lxc/start.c:lxc_spawn:1774 - Cloned CLONE_NEWUSER
lxc-start dev 20260407155630.568 INFO start - ../src/lxc/start.c:lxc_spawn:1774 - Cloned CLONE_NEWNS
lxc-start dev 20260407155630.568 INFO start - ../src/lxc/start.c:lxc_spawn:1774 - Cloned CLONE_NEWPID
lxc-start dev 20260407155630.568 INFO start - ../src/lxc/start.c:lxc_spawn:1774 - Cloned CLONE_NEWUTS
lxc-start dev 20260407155630.568 INFO start - ../src/lxc/start.c:lxc_spawn:1774 - Cloned CLONE_NEWIPC
lxc-start dev 20260407155630.568 INFO start - ../src/lxc/start.c:lxc_spawn:1774 - Cloned CLONE_NEWCGROUP
lxc-start dev 20260407155630.568 DEBUG start - ../src/lxc/start.c:lxc_try_preserve_namespace:140 - Preserved user namespace via fd 16 and stashed path as user:/proc/596375/fd/16
lxc-start dev 20260407155630.568 DEBUG start - ../src/lxc/start.c:lxc_try_preserve_namespace:140 - Preserved mnt namespace via fd 17 and stashed path as mnt:/proc/596375/fd/17
lxc-start dev 20260407155630.568 DEBUG start - ../src/lxc/start.c:lxc_try_preserve_namespace:140 - Preserved pid namespace via fd 18 and stashed path as pid:/proc/596375/fd/18
lxc-start dev 20260407155630.568 DEBUG start - ../src/lxc/start.c:lxc_try_preserve_namespace:140 - Preserved uts namespace via fd 19 and stashed path as uts:/proc/596375/fd/19
lxc-start dev 20260407155630.568 DEBUG start - ../src/lxc/start.c:lxc_try_preserve_namespace:140 - Preserved ipc namespace via fd 20 and stashed path as ipc:/proc/596375/fd/20
lxc-start dev 20260407155630.568 TRACE start - ../src/lxc/start.c:lxc_spawn:1714 - Spawned container directly into target cgroup via cgroup2 fd 14
lxc-start dev 20260407155630.568 DEBUG start - ../src/lxc/start.c:lxc_try_preserve_namespace:140 - Preserved cgroup namespace via fd 21 and stashed path as cgroup:/proc/596375/fd/21
lxc-start dev 20260407155630.568 DEBUG idmap_utils - ../src/lxc/idmap_utils.c:idmaptool_on_path_and_privileged:93 - The binary "/usr/bin/newuidmap" does have the setuid bit set
lxc-start dev 20260407155630.568 DEBUG idmap_utils - ../src/lxc/idmap_utils.c:idmaptool_on_path_and_privileged:93 - The binary "/usr/bin/newgidmap" does have the setuid bit set
lxc-start dev 20260407155630.568 DEBUG idmap_utils - ../src/lxc/idmap_utils.c:lxc_map_ids:178 - Functional newuidmap and newgidmap binary found
lxc-start dev 20260407155630.568 TRACE sync - ../src/lxc/sync.c:lxc_sync_wait_parent:110 - Child waiting for parent with sequence startup
lxc-start dev 20260407155630.570 TRACE idmap_utils - ../src/lxc/idmap_utils.c:lxc_map_ids:246 - newuidmap wrote mapping "newuidmap 596376 0 493216 65536"
lxc-start dev 20260407155630.571 TRACE idmap_utils - ../src/lxc/idmap_utils.c:lxc_map_ids:246 - newgidmap wrote mapping "newgidmap 596376 0 493216 65536"
lxc-start dev 20260407155630.571 TRACE cgfsng - ../src/lxc/cgroups/cgfsng.c:__cgfsng_delegate_controllers:3633 - Enabled "+cpuset +cpu +io +memory +hugetlb +pids +rdma +misc" controllers in the unified cgroup 8
# cat /sys/fs/cgroup/lxc.payload.dev/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma misc
# cat /sys/fs/cgroup/lxc.payload.dev/cgroup.subtree_control
# cat /sys/fs/cgroup/lxc.payload.dev/cgroup.procs
596376
# lxc-attach -n dev
~ # cat /sys/fs/cgroup/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma misc
~ # cat /sys/fs/cgroup/cgroup.subtree_control
~ # cat /sys/fs/cgroup/cgroup.procs
1
I have created a workaround for now where I replaced lxc.init.cmd with a shell script that moves itself to a child group, writes subtree_control and then invokes OpenRC’s init, but this seems like it shouldn’t be necessary. Is Alpine / OpenRC just not cgroup-aware for containers and shouldn’t be used like this, or is there functionality in LXC to enable delegation that I am just completely missing?