diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go b/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go index 58b7ae248fa..f8956acf728 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go @@ -26,6 +26,7 @@ import ( libcontainercgroups "github.com/opencontainers/runc/libcontainer/cgroups" cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" utilfeature "k8s.io/apiserver/pkg/util/feature" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" "k8s.io/klog/v2" @@ -62,42 +63,10 @@ func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *v1.C } // set linux container resources - var cpuShares int64 - cpuRequest := container.Resources.Requests.Cpu() - cpuLimit := container.Resources.Limits.Cpu() - memoryLimit := container.Resources.Limits.Memory().Value() - memoryRequest := container.Resources.Requests.Memory().Value() - oomScoreAdj := int64(qos.GetContainerOOMScoreAdjust(pod, container, - int64(m.machineInfo.MemoryCapacity))) - // If request is not specified, but limit is, we want request to default to limit. - // API server does this for new containers, but we repeat this logic in Kubelet - // for containers running on existing Kubernetes clusters. - if cpuRequest.IsZero() && !cpuLimit.IsZero() { - cpuShares = milliCPUToShares(cpuLimit.MilliValue()) - } else { - // if cpuRequest.Amount is nil, then milliCPUToShares will return the minimal number - // of CPU shares. - cpuShares = milliCPUToShares(cpuRequest.MilliValue()) - } - lc.Resources.CpuShares = cpuShares - if memoryLimit != 0 { - lc.Resources.MemoryLimitInBytes = memoryLimit - } - // Set OOM score of the container based on qos policy. Processes in lower-priority pods should - // be killed first if the system runs out of memory. - lc.Resources.OomScoreAdj = oomScoreAdj + lc.Resources = m.calculateLinuxResources(container.Resources.Requests.Cpu(), container.Resources.Limits.Cpu(), container.Resources.Limits.Memory()) - if m.cpuCFSQuota { - // if cpuLimit.Amount is nil, then the appropriate default value is returned - // to allow full usage of cpu resource. - cpuPeriod := int64(quotaPeriod) - if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CPUCFSQuotaPeriod) { - cpuPeriod = int64(m.cpuCFSQuotaPeriod.Duration / time.Microsecond) - } - cpuQuota := milliCPUToQuota(cpuLimit.MilliValue(), cpuPeriod) - lc.Resources.CpuQuota = cpuQuota - lc.Resources.CpuPeriod = cpuPeriod - } + lc.Resources.OomScoreAdj = int64(qos.GetContainerOOMScoreAdjust(pod, container, + int64(m.machineInfo.MemoryCapacity))) lc.Resources.HugepageLimits = GetHugepageLimitsFromResources(container.Resources) @@ -121,7 +90,8 @@ func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *v1.C // Set memory.min and memory.high to enforce MemoryQoS if enforceMemoryQoS { unified := map[string]string{} - + memoryRequest := container.Resources.Requests.Memory().Value() + memoryLimit := container.Resources.Limits.Memory().Value() if memoryRequest != 0 { unified[cm.MemoryMin] = strconv.FormatInt(memoryRequest, 10) } @@ -158,6 +128,43 @@ func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *v1.C return lc } +// calculateLinuxResources will create the linuxContainerResources type based on the provided CPU and memory resource requests, limits +func (m *kubeGenericRuntimeManager) calculateLinuxResources(cpuRequest, cpuLimit, memoryLimit *resource.Quantity) *runtimeapi.LinuxContainerResources { + resources := runtimeapi.LinuxContainerResources{} + var cpuShares int64 + + memLimit := memoryLimit.Value() + + // If request is not specified, but limit is, we want request to default to limit. + // API server does this for new containers, but we repeat this logic in Kubelet + // for containers running on existing Kubernetes clusters. + if cpuRequest.IsZero() && !cpuLimit.IsZero() { + cpuShares = milliCPUToShares(cpuLimit.MilliValue()) + } else { + // if cpuRequest.Amount is nil, then milliCPUToShares will return the minimal number + // of CPU shares. + cpuShares = milliCPUToShares(cpuRequest.MilliValue()) + } + resources.CpuShares = cpuShares + if memLimit != 0 { + resources.MemoryLimitInBytes = memLimit + } + + if m.cpuCFSQuota { + // if cpuLimit.Amount is nil, then the appropriate default value is returned + // to allow full usage of cpu resource. + cpuPeriod := int64(quotaPeriod) + if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CPUCFSQuotaPeriod) { + cpuPeriod = int64(m.cpuCFSQuotaPeriod.Duration / time.Microsecond) + } + cpuQuota := milliCPUToQuota(cpuLimit.MilliValue(), cpuPeriod) + resources.CpuQuota = cpuQuota + resources.CpuPeriod = cpuPeriod + } + + return &resources +} + // GetHugepageLimitsFromResources returns limits of each hugepages from resources. func GetHugepageLimitsFromResources(resources v1.ResourceRequirements) []*runtimeapi.HugepageLimit { var hugepageLimits []*runtimeapi.HugepageLimit diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go b/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go index 9598e9edcb6..3ef2f98fde0 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go @@ -223,6 +223,50 @@ func TestGenerateLinuxContainerConfigResources(t *testing.T) { } } +func TestCalculateLinuxResources(t *testing.T) { + _, _, m, err := createTestRuntimeManager() + m.cpuCFSQuota = true + + assert.NoError(t, err) + + tests := []struct { + name string + cpuReq resource.Quantity + cpuLim resource.Quantity + memLim resource.Quantity + expected *runtimeapi.LinuxContainerResources + }{ + { + name: "Request128MBLimit256MB", + cpuReq: resource.MustParse("1"), + cpuLim: resource.MustParse("2"), + memLim: resource.MustParse("128Mi"), + expected: &runtimeapi.LinuxContainerResources{ + CpuPeriod: 100000, + CpuQuota: 200000, + CpuShares: 1024, + MemoryLimitInBytes: 134217728, + }, + }, + { + name: "RequestNoMemory", + cpuReq: resource.MustParse("2"), + cpuLim: resource.MustParse("8"), + memLim: resource.MustParse("0"), + expected: &runtimeapi.LinuxContainerResources{ + CpuPeriod: 100000, + CpuQuota: 800000, + CpuShares: 2048, + MemoryLimitInBytes: 0, + }, + }, + } + for _, test := range tests { + linuxContainerResources := m.calculateLinuxResources(&test.cpuReq, &test.cpuLim, &test.memLim) + assert.Equal(t, test.expected, linuxContainerResources) + } +} + func TestGenerateContainerConfigWithMemoryQoSEnforced(t *testing.T) { _, _, m, err := createTestRuntimeManager() assert.NoError(t, err)