From ba8b80a451f2f7f401fe6ffb01c6210e03f10f97 Mon Sep 17 00:00:00 2001 From: Tom Wieczorek Date: Tue, 11 Jul 2023 10:51:33 +0200 Subject: [PATCH] Revisit freezer controller detection for cgroup v2 The cgroup v2 freezer controller is not listed in the cgroup.controllers file and is deliberately not available in the root cgroup. Therefore, k0s sysinfo determines its presence based on the Linux kernel version. This is problematic for old kernels that have many backported features, such as RHEL and consorts. However, it is still possible to detect the freezer controller via the cgroups filesystem in cgroups other than the root group. To provide a more reliable result for all kernels, k0s now tries to detect the controller in its own cgroup. In the unlikely case that k0s is running in the root cgroup, it will try to create an empty, temporary cgroup for the freezer file detection. If it doesn't have sufficient permissions to do so, it simply assumes the presence of the freezer controller. See: 0655941 ("Add pre-flight checks and probes module") Signed-off-by: Tom Wieczorek --- .../pkg/sysinfo/probes/linux/cgroup_v2.go | 62 ++++++++++++++++--- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/internal/pkg/sysinfo/probes/linux/cgroup_v2.go b/internal/pkg/sysinfo/probes/linux/cgroup_v2.go index 9b2d9e8e5942..b45ca3631c90 100644 --- a/internal/pkg/sysinfo/probes/linux/cgroup_v2.go +++ b/internal/pkg/sysinfo/probes/linux/cgroup_v2.go @@ -22,6 +22,8 @@ package linux import ( "errors" "fmt" + "os" + "path/filepath" "regexp" "strconv" @@ -43,26 +45,66 @@ func (g *cgroupV2) probeController(controllerName string) (cgroupControllerAvail } func (g *cgroupV2) loadControllers(seen func(string, string)) error { - // Some controllers are implicitly enabled by the kernel. Those controllers - // do not appear in the cgroup.controllers file. Their availability is - // assumed based on the kernel version, as it is hard to detect them - // directly. - // https://github.com/torvalds/linux/blob/v5.3/kernel/cgroup/cgroup.c#L433-L434 + // The device controller has no interface files. Its availability is assumed + // based on the kernel version, as it is hard to detect it directly. + // https://github.com/torvalds/linux/blob/v5.3/Documentation/admin-guide/cgroup-v2.rst#device-controller if major, minor, err := parseKernelRelease(g.probeUname); err == nil { /* devices: since 4.15 */ if major > 4 || (major == 4 && minor >= 15) { seen("devices", "assumed") } - /* freezer: since 5.2 */ if major > 5 || (major == 5 && minor >= 2) { - seen("freezer", "assumed") - } } else { return err } - if err := g.detectListedRootControllers(seen); err != nil { - return err + return errors.Join( + g.detectFreezerController(seen), + g.detectListedRootControllers(seen), + ) +} + +// Detect the freezer controller. It doesn't appear in the cgroup.controllers +// file. Check for the existence of the cgroup.freeze file in the k0s cgroup +// instead, or try to create a dummy cgroup if k0s runs in the root cgroup. +// +// https://github.com/torvalds/linux/blob/v5.3/Documentation/admin-guide/cgroup-v2.rst#core-interface-files +func (g *cgroupV2) detectFreezerController(seen func(string, string)) (err error) { + + // Detect the freezer controller by checking k0s's cgroup for the existence + // of the cgroup.freeze file. + // https://github.com/torvalds/linux/blob/v5.3/Documentation/admin-guide/cgroup-v2.rst#processes + cgroupPath, err := cgroup2.NestedGroupPath("") + if err != nil { + return fmt.Errorf("failed to get k0s cgroup: %w", err) } + if cgroupPath != "/" { + cgroupPath = filepath.Join(g.mountPoint, cgroupPath) + } else { // The root cgroup cannot be frozen. Try to create a dummy cgroup. + tmpCgroupPath, err := os.MkdirTemp(g.mountPoint, "k0s-freezer-detection-*") + if err != nil { + if errors.Is(err, os.ErrPermission) && os.Geteuid() != 0 { + seen("freezer", "assumed, insufficient permissions, try with elevated permissions") + return nil + } + + return err + } + defer func() { err = errors.Join(err, os.Remove(tmpCgroupPath)) }() + cgroupPath = tmpCgroupPath + } + + // Check if the cgroup.freeze exists + freezePath := filepath.Join(cgroupPath, "cgroup.freeze") + stat, err := os.Stat(freezePath) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + if !stat.IsDir() { + seen("freezer", "cgroup.freeze exists") + } return nil }