diff --git a/libcontainer/cgroups/fs/fs.go b/libcontainer/cgroups/fs/fs.go index a42ce4535e9..6b34a317129 100644 --- a/libcontainer/cgroups/fs/fs.go +++ b/libcontainer/cgroups/fs/fs.go @@ -36,6 +36,14 @@ var ( var errSubsystemDoesNotExist = errors.New("cgroup: subsystem does not exist") +func init() { + // If using cgroups-hybrid mode then add a "" controller indicating + // it should join the cgroups v2. + if cgroups.IsCgroup2HybridMode() { + subsystems = append(subsystems, &NameGroup{GroupName: "", Join: true}) + } +} + type subsystem interface { // Name returns the name of the subsystem. Name() string diff --git a/libcontainer/cgroups/systemd/v1.go b/libcontainer/cgroups/systemd/v1.go index 64af1d94b3e..36fad76e2e4 100644 --- a/libcontainer/cgroups/systemd/v1.go +++ b/libcontainer/cgroups/systemd/v1.go @@ -205,6 +205,19 @@ func (m *legacyManager) Apply(pid int) error { } paths[s.Name()] = subsystemPath } + + // If systemd is using cgroups-hybrid mode then add the slice path of + // this container to the paths so the following process executed with + // "runc exec" joins that cgroup as well. + if cgroups.IsCgroup2HybridMode() { + // "" means cgroup-hybrid path + cgroupsHybridPath, err := getSubsystemPath(m.cgroups, "") + if err != nil && cgroups.IsNotFound(err) { + return err + } + paths[""] = cgroupsHybridPath + } + m.paths = paths if err := m.joinCgroups(pid); err != nil { diff --git a/libcontainer/cgroups/utils.go b/libcontainer/cgroups/utils.go index 840817e398a..6b13e9bc87e 100644 --- a/libcontainer/cgroups/utils.go +++ b/libcontainer/cgroups/utils.go @@ -24,11 +24,14 @@ import ( const ( CgroupProcesses = "cgroup.procs" unifiedMountpoint = "/sys/fs/cgroup" + hybridMountpoint = "/sys/fs/cgroup/unified" ) var ( isUnifiedOnce sync.Once isUnified bool + isHybridOnce sync.Once + isHybrid bool ) // IsCgroup2UnifiedMode returns whether we are running in cgroup v2 unified mode. @@ -50,6 +53,24 @@ func IsCgroup2UnifiedMode() bool { return isUnified } +// IsCgroup2HybridMode returns whether we are running in cgroup v2 hybrid mode. +func IsCgroup2HybridMode() bool { + isHybridOnce.Do(func() { + var st unix.Statfs_t + err := unix.Statfs(hybridMountpoint, &st) + if err != nil { + if os.IsNotExist(err) { + // ignore the "not found" error + isHybrid = false + return + } + panic(fmt.Sprintf("cannot statfs cgroup root: %s", err)) + } + isHybrid = st.Type == unix.CGROUP2_SUPER_MAGIC + }) + return isHybrid +} + type Mount struct { Mountpoint string Root string diff --git a/libcontainer/cgroups/v1_utils.go b/libcontainer/cgroups/v1_utils.go index 95ec9dff028..615af2461d9 100644 --- a/libcontainer/cgroups/v1_utils.go +++ b/libcontainer/cgroups/v1_utils.go @@ -116,6 +116,12 @@ func FindCgroupMountpoint(cgroupPath, subsystem string) (string, error) { return "", errUnified } + // If subsystem is empty it means that we are looking for the + // cgroups2 path + if len(subsystem) == 0 { + return hybridMountpoint, nil + } + // Avoid parsing mountinfo by trying the default path first, if possible. if path := tryDefaultPath(cgroupPath, subsystem); path != "" { return path, nil @@ -226,6 +232,12 @@ func GetOwnCgroupPath(subsystem string) (string, error) { return "", err } + // If subsystem is empty it means that we are looking for the + // cgroups2 path + if len(subsystem) == 0 { + return hybridMountpoint, nil + } + return getCgroupPathHelper(subsystem, cgroup) } diff --git a/tests/integration/cgroups.bats b/tests/integration/cgroups.bats index a319ed04218..c3bb68111a5 100644 --- a/tests/integration/cgroups.bats +++ b/tests/integration/cgroups.bats @@ -270,3 +270,26 @@ function setup() { check_cpu_weight 42 } + +@test "runc exec (cgroup v2 hybrid joins correct cgroup)" { + requires root cgroups_hybrid + + set_cgroups_path "$BUSYBOX_BUNDLE" + set_cgroup_mount_writable "$BUSYBOX_BUNDLE" + + runc run --pid-file pid.txt -d --console-socket "$CONSOLE_SOCKET" test_cgroups_group + [ "$status" -eq 0 ] + + pid=$(cat pid.txt) + run_cgroup=$(tail -1