diff --git a/libcontainer/cgroups/fs2/cpu.go b/libcontainer/cgroups/fs2/cpu.go index f0f5df09..8ec85571 100644 --- a/libcontainer/cgroups/fs2/cpu.go +++ b/libcontainer/cgroups/fs2/cpu.go @@ -14,12 +14,14 @@ import ( ) func setCpu(dirPath string, cgroup *configs.Cgroup) error { + // NOTE: .CpuShares is not used here. Conversion is the caller's responsibility. if cgroup.Resources.CpuWeight != 0 { if err := fscommon.WriteFile(dirPath, "cpu.weight", strconv.FormatUint(cgroup.Resources.CpuWeight, 10)); err != nil { return err } } + // NOTE: .CpuQuota and .CpuPeriod are not used here. Conversion is the caller's responsibility. if cgroup.Resources.CpuMax != "" { if err := fscommon.WriteFile(dirPath, "cpu.max", cgroup.Resources.CpuMax); err != nil { return err diff --git a/libcontainer/cgroups/utils.go b/libcontainer/cgroups/utils.go index 704941cd..1bedda04 100644 --- a/libcontainer/cgroups/utils.go +++ b/libcontainer/cgroups/utils.go @@ -609,3 +609,18 @@ func ConvertCPUSharesToCgroupV2Value(cpuShares uint64) uint64 { } return (1 + ((cpuShares-2)*9999)/262142) } + +// ConvertCPUQuotaCPUPeriodToCgroupV2Value generates cpu.max string. +func ConvertCPUQuotaCPUPeriodToCgroupV2Value(quota int64, period uint64) string { + if quota <= 0 && period == 0 { + return "" + } + if period == 0 { + // This default value is documented in https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html + period = 100000 + } + if quota <= 0 { + return fmt.Sprintf("max %d", period) + } + return fmt.Sprintf("%d %d", quota, period) +} diff --git a/libcontainer/cgroups/utils_test.go b/libcontainer/cgroups/utils_test.go index 3dfa6a0d..14a76bf7 100644 --- a/libcontainer/cgroups/utils_test.go +++ b/libcontainer/cgroups/utils_test.go @@ -485,3 +485,48 @@ func TestConvertCPUSharesToCgroupV2Value(t *testing.T) { } } } + +func TestConvertCPUQuotaCPUPeriodToCgroupV2Value(t *testing.T) { + cases := []struct { + quota int64 + period uint64 + expected string + }{ + { + quota: 0, + period: 0, + expected: "", + }, + { + quota: -1, + period: 0, + expected: "", + }, + { + quota: 1000, + period: 5000, + expected: "1000 5000", + }, + { + quota: 0, + period: 5000, + expected: "max 5000", + }, + { + quota: -1, + period: 5000, + expected: "max 5000", + }, + { + quota: 1000, + period: 0, + expected: "1000 100000", + }, + } + for _, c := range cases { + got := ConvertCPUQuotaCPUPeriodToCgroupV2Value(c.quota, c.period) + if got != c.expected { + t.Errorf("expected ConvertCPUQuotaCPUPeriodToCgroupV2Value(%d, %d) to be %s, got %s", c.quota, c.period, c.expected, got) + } + } +} diff --git a/libcontainer/specconv/spec_linux.go b/libcontainer/specconv/spec_linux.go index 90731f50..88463e47 100644 --- a/libcontainer/specconv/spec_linux.go +++ b/libcontainer/specconv/spec_linux.go @@ -492,6 +492,9 @@ func CreateCgroupConfig(opts *CreateOpts) (*configs.Cgroup, error) { if r.CPU.Period != nil { c.Resources.CpuPeriod = *r.CPU.Period } + //CpuMax is used for cgroupv2 and should be converted + c.Resources.CpuMax = cgroups.ConvertCPUQuotaCPUPeriodToCgroupV2Value(c.Resources.CpuQuota, c.Resources.CpuPeriod) + if r.CPU.RealtimeRuntime != nil { c.Resources.CpuRtRuntime = *r.CPU.RealtimeRuntime } diff --git a/update.go b/update.go index 84306b29..42f3ecf0 100644 --- a/update.go +++ b/update.go @@ -257,6 +257,8 @@ other options are ignored. config.Cgroups.Resources.CpuShares = *r.CPU.Shares //CpuWeight is used for cgroupv2 and should be converted config.Cgroups.Resources.CpuWeight = cgroups.ConvertCPUSharesToCgroupV2Value(*r.CPU.Shares) + //CpuMax is used for cgroupv2 and should be converted + config.Cgroups.Resources.CpuMax = cgroups.ConvertCPUQuotaCPUPeriodToCgroupV2Value(*r.CPU.Quota, *r.CPU.Period) config.Cgroups.Resources.CpuRtPeriod = *r.CPU.RealtimePeriod config.Cgroups.Resources.CpuRtRuntime = *r.CPU.RealtimeRuntime config.Cgroups.Resources.CpusetCpus = r.CPU.Cpus