From e3e69910a5f7e4e37be8936ba2e88968a744df91 Mon Sep 17 00:00:00 2001 From: Djalal Harouni Date: Mon, 28 Oct 2024 16:11:29 +0100 Subject: [PATCH] cgroupv1: check that cpuset and memory controllers are exported kernel v6.11 introduced new CONFIG_MEMCG_V1 and CONFIG_CPUSETS_V1 [1] that changed how userspace detects the compiled cgroup controllers, if the new CONFIG_MEMCG_V1=n is not set then the memory and same for cpuset controller will not be exported in /proc/cgroups. Update parseCgroupv1SubSysIds() that parses cgroup controllers to ensure that we have the memory and cpuset controllers compiled and exported, and in case of failures report that the user needs kernel configs: CONFIG_MEMCG=y and CONFIG_MEMCG_V1=y CONFIG_CPUSETS=y and CONFIG_CPUSETS_V1=y We need them to be compiled and exported in /proc/cgroups since we use the line order and number as an indication for their index in the css_set to fetch the related subsystem and its cgroup. They are compiled in configs, same for /proc/cgroups since its content is compile time generated. That index allows us to work and track the right cgroup hierarchy in cgroupv1 otherwise we will operate on the wrong hierarchy that probably does not have proper cgroup tracking. The css_set index is saved in the bpf map 'tg_conf_map' in field tg_cgrpv1_subsys_idx from user space during startup, and used later by bpf code to track processes and associate related cgroups. [1] "af000ce85293b8e60" "cgroup: Do not report unavailable v1 controllers in /proc/cgroups" Signed-off-by: Djalal Harouni --- pkg/cgroups/cgroups.go | 34 ++++++++++++++++++++-------------- pkg/cgroups/cgroups_test.go | 20 ++++++++++++++++++++ 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/pkg/cgroups/cgroups.go b/pkg/cgroups/cgroups.go index 3e24124beb3..6170476e844 100644 --- a/pkg/cgroups/cgroups.go +++ b/pkg/cgroups/cgroups.go @@ -187,6 +187,12 @@ func GetCgroupIdFromPath(cgroupPath string) (uint64, error) { return fh.Id, nil } +// parseCgroupv1SubSysIds() parse cgroupv1 controllers and save their +// hierarchy IDs and related css indexes. +// If the 'memory' or 'cpuset' are not detected we fail, as we use them +// from BPF side to gather cgroup information and we need them to be +// exported by the kernel since their corresponding index allows us to +// fetch the cgroup from the corresponding cgroup subsystem state. func parseCgroupv1SubSysIds(filePath string) error { var allcontrollers []string @@ -198,7 +204,6 @@ func parseCgroupv1SubSysIds(filePath string) error { defer file.Close() fscanner := bufio.NewScanner(file) - fixed := false idx := 0 fscanner.Scan() // ignore first entry for fscanner.Scan() { @@ -224,7 +229,6 @@ func parseCgroupv1SubSysIds(filePath string) error { CgroupControllers[i].Id = uint32(id) CgroupControllers[i].Idx = uint32(idx) CgroupControllers[i].Active = true - fixed = true } else { logger.GetLogger().WithFields(logrus.Fields{ "cgroup.fs": cgroupFSPath, @@ -241,17 +245,8 @@ func parseCgroupv1SubSysIds(filePath string) error { "cgroup.controllers": fmt.Sprintf("[%s]", strings.Join(allcontrollers, " ")), }).Debugf("Cgroupv1 available controllers") - // Could not find 'memory', 'pids' nor 'cpuset' controllers, are they compiled in? - if !fixed { - err = fmt.Errorf("detect cgroupv1 controllers IDs from '%s' failed", filePath) - logger.GetLogger().WithFields(logrus.Fields{ - "cgroup.fs": cgroupFSPath, - }).WithError(err).Warnf("Cgroupv1 controllers 'memory', 'pids' and 'cpuset' are missing") - return err - } - for _, controller := range CgroupControllers { - // Print again everything that is available or not + // Print again everything that is available and if not, fail with error if controller.Active { logger.GetLogger().WithFields(logrus.Fields{ "cgroup.fs": cgroupFSPath, @@ -260,9 +255,20 @@ func parseCgroupv1SubSysIds(filePath string) error { "cgroup.controller.index": controller.Idx, }).Infof("Cgroupv1 supported controller '%s' is active on the system", controller.Name) } else { + var err error // Warn with error - err = fmt.Errorf("controller '%s' is not active", controller.Name) - logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).WithError(err).Warnf("Cgroupv1 supported controller is missing") + if controller.Name == "memory" { + err = fmt.Errorf("Cgroupv1 controller 'memory' is not active, ensure kernel CONFIG_MEMCG=y and CONFIG_MEMCG_V1=y are set") + } else if controller.Name == "cpuset" { + err = fmt.Errorf("Cgroupv1 controller 'cpuset' is not active, ensure kernel CONFIG_CPUSETS=y and CONFIG_CPUSETS_V1=y are set") + } else { + logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).Warnf("Cgroupv1 '%s' supported controller is missing", controller.Name) + } + + if err != nil { + logger.GetLogger().WithField("cgroup.fs", cgroupFSPath).WithError(err).Warnf("Cgroupv1 '%s' supported controller is missing", controller.Name) + return err + } } } diff --git a/pkg/cgroups/cgroups_test.go b/pkg/cgroups/cgroups_test.go index bb2b9563dfb..da3b0da5099 100644 --- a/pkg/cgroups/cgroups_test.go +++ b/pkg/cgroups/cgroups_test.go @@ -102,6 +102,26 @@ func TestCgroupNameFromCStr(t *testing.T) { } } +// Ensure that Cgroupv1 controllers discovery fails if no 'cpuset' and no 'memory' +func TestParseCgroupSubSysIdsWithoutMemoryCpuset(t *testing.T) { + testDir := t.TempDir() + invalid_cgroupv1_controllers := + ` +#subsys_name hierarchy num_cgroups enabled +cpu 6 78 1 +cpuacct 6 78 1 +blkio 4 78 1 +perf_event 8 2 1 +` + + file := filepath.Join(testDir, "testfile") + err := os.WriteFile(file, []byte(invalid_cgroupv1_controllers), 0644) + require.NoError(t, err) + + err = parseCgroupv1SubSysIds(file) + require.Error(t, err) +} + func TestParseCgroupSubSysIds(t *testing.T) { testDir := t.TempDir()