From b52a44717e4eadfd07f381346a6b612484f5df9f Mon Sep 17 00:00:00 2001 From: Luiz Aoqui Date: Fri, 9 Feb 2024 16:29:14 -0500 Subject: [PATCH] executor: limit the value of CPU shares (#19935) The value for the executor cgroup CPU weight must be within the limits imposed by the Linux kernel. Nomad used the task `resource.cpu`, an unbounded value, directly as the cgroup CPU weight, causing it to potentially go outside the imposed values. This commit clamps the CPU shares values to be within the limits allowed. Co-authored-by: Tim Gross --- .changelog/19935.txt | 3 +++ drivers/shared/executor/executor_linux.go | 29 +++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 .changelog/19935.txt diff --git a/.changelog/19935.txt b/.changelog/19935.txt new file mode 100644 index 000000000..eebede259 --- /dev/null +++ b/.changelog/19935.txt @@ -0,0 +1,3 @@ +```release-note:bug +client: Ensure the value for CPU shares are within the allowed range +``` diff --git a/drivers/shared/executor/executor_linux.go b/drivers/shared/executor/executor_linux.go index fe18c3192..ff5d03cfb 100644 --- a/drivers/shared/executor/executor_linux.go +++ b/drivers/shared/executor/executor_linux.go @@ -43,6 +43,13 @@ import ( "golang.org/x/sys/unix" ) +const ( + // CPU shares limits are defined by the Linux kernel. + // https://github.com/torvalds/linux/blob/0dd3ee31125508cd67f7e7172247f05b7fd1753a/kernel/sched/sched.h#L409-L418 + MinCPUShares = 2 + MaxCPUShares = 262_144 +) + var ( // ExecutorCgroupV1MeasuredMemStats is the list of memory stats captured by the executor with cgroup-v1 ExecutorCgroupV1MeasuredMemStats = []string{"RSS", "Cache", "Swap", "Usage", "Max Usage", "Kernel Usage", "Kernel Max Usage"} @@ -717,7 +724,7 @@ func (l *LibcontainerExecutor) configureCgroupMemory(cfg *runc.Config, command * func (l *LibcontainerExecutor) configureCG1(cfg *runc.Config, command *ExecCommand, cgroup string) error { - cpuShares := command.Resources.LinuxResources.CPUShares + cpuShares := l.clampCpuShares(command.Resources.LinuxResources.CPUShares) cpusetPath := command.Resources.LinuxResources.CpusetCgroupPath cpuCores := command.Resources.LinuxResources.CpusetCpus @@ -749,7 +756,7 @@ func (l *LibcontainerExecutor) cpusetCG1(cpusetCgroupPath, cores string) error { } func (l *LibcontainerExecutor) configureCG2(cfg *runc.Config, command *ExecCommand, cg string) error { - cpuShares := command.Resources.LinuxResources.CPUShares + cpuShares := l.clampCpuShares(command.Resources.LinuxResources.CPUShares) cpuCores := command.Resources.LinuxResources.CpusetCpus // Set the v2 specific unified path @@ -801,6 +808,24 @@ func (l *LibcontainerExecutor) newLibcontainerConfig(command *ExecCommand) (*run return cfg, nil } +func (l *LibcontainerExecutor) clampCpuShares(shares int64) int64 { + if shares < MinCPUShares { + l.logger.Warn( + "task CPU is lower than minimum allowed, using minimum value instead", + "task_cpu", shares, "min", MinCPUShares, + ) + return MinCPUShares + } + if shares > MaxCPUShares { + l.logger.Warn( + "task CPU is greater than maximum allowed, using maximum value instead", + "task_cpu", shares, "max", MaxCPUShares, + ) + return MaxCPUShares + } + return shares +} + // cmdDevices converts a list of driver.DeviceConfigs into excutor.Devices. func cmdDevices(driverDevices []*drivers.DeviceConfig) ([]*devices.Device, error) { if len(driverDevices) == 0 {