k8s.io/kubernetes@v1.29.3/pkg/kubelet/kuberuntime/kuberuntime_container_windows.go (about) 1 //go:build windows 2 // +build windows 3 4 /* 5 Copyright 2018 The Kubernetes Authors. 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 */ 19 20 package kuberuntime 21 22 import ( 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/apimachinery/pkg/api/resource" 25 runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" 26 "k8s.io/klog/v2" 27 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 28 "k8s.io/kubernetes/pkg/kubelet/winstats" 29 "k8s.io/kubernetes/pkg/securitycontext" 30 ) 31 32 // applyPlatformSpecificContainerConfig applies platform specific configurations to runtimeapi.ContainerConfig. 33 func (m *kubeGenericRuntimeManager) applyPlatformSpecificContainerConfig(config *runtimeapi.ContainerConfig, container *v1.Container, pod *v1.Pod, uid *int64, username string, _ *kubecontainer.ContainerID) error { 34 windowsConfig, err := m.generateWindowsContainerConfig(container, pod, uid, username) 35 if err != nil { 36 return err 37 } 38 config.Windows = windowsConfig 39 40 return nil 41 } 42 43 // generateContainerResources generates platform specific (windows) container resources config for runtime 44 func (m *kubeGenericRuntimeManager) generateContainerResources(pod *v1.Pod, container *v1.Container) *runtimeapi.ContainerResources { 45 return &runtimeapi.ContainerResources{ 46 Windows: m.generateWindowsContainerResources(pod, container), 47 } 48 } 49 50 // generateWindowsContainerResources generates windows container resources config for runtime 51 func (m *kubeGenericRuntimeManager) generateWindowsContainerResources(pod *v1.Pod, container *v1.Container) *runtimeapi.WindowsContainerResources { 52 wcr := m.calculateWindowsResources(container.Resources.Limits.Cpu(), container.Resources.Limits.Memory()) 53 54 return wcr 55 } 56 57 // calculateWindowsResources will create the windowsContainerResources type based on the provided CPU and memory resource requests, limits 58 func (m *kubeGenericRuntimeManager) calculateWindowsResources(cpuLimit, memoryLimit *resource.Quantity) *runtimeapi.WindowsContainerResources { 59 resources := runtimeapi.WindowsContainerResources{} 60 61 memLimit := memoryLimit.Value() 62 63 if !cpuLimit.IsZero() { 64 // Since Kubernetes doesn't have any notion of weight in the Pod/Container API, only limits/reserves, then applying CpuMaximum only 65 // will better follow the intent of the user. At one point CpuWeights were set, but this prevented limits from having any effect. 66 67 // There are 3 parts to how this works: 68 // Part one - Windows kernel 69 // cpuMaximum is documented at https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/resource-controls 70 // the range and how it relates to number of CPUs is at https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-jobobject_cpu_rate_control_information 71 // For process isolation, these are applied to the job object setting JOB_OBJECT_CPU_RATE_CONTROL_ENABLE, which can be set to either 72 // JOB_OBJECT_CPU_RATE_CONTROL_WEIGHT_BASED or JOB_OBJECT_CPU_RATE_CONTROL_HARD_CAP. This is why the settings are mutually exclusive. 73 // Part two - Docker (doc: https://docs.docker.com/engine/api/v1.30) 74 // If both CpuWeight and CpuMaximum are passed to Docker, then it sets 75 // JOB_OBJECT_CPU_RATE_CONTROL_ENABLE = JOB_OBJECT_CPU_RATE_CONTROL_WEIGHT_BASED ignoring CpuMaximum. 76 // Option a: Set HostConfig.CpuPercent. The units are whole percent of the total CPU capacity of the system, meaning the resolution 77 // is different based on the number of cores. 78 // Option b: Set HostConfig.NanoCpus integer <int64> - CPU quota in units of 10e-9 CPUs. Moby scales this to the Windows job object 79 // resolution of 1-10000, so it's higher resolution than option a. 80 // src: https://github.com/moby/moby/blob/10866714412aea1bb587d1ad14b2ce1ba4cf4308/daemon/oci_windows.go#L426 81 // Part three - CRI & ContainerD's implementation 82 // The kubelet sets these directly on CGroups in Linux, but needs to pass them across CRI on Windows. 83 // There is an existing cpu_maximum field, with a range of percent * 100, so 1-10000. This is different from Docker, but consistent with OCI 84 // https://github.com/kubernetes/kubernetes/blob/56d1c3b96d0a544130a82caad33dd57629b8a7f8/staging/src/k8s.io/cri-api/pkg/apis/runtime/v1/api.proto#L681-L682 85 // https://github.com/opencontainers/runtime-spec/blob/ad53dcdc39f1f7f7472b10aa0a45648fe4865496/config-windows.md#cpu 86 // If both CpuWeight and CpuMaximum are set - ContainerD catches this invalid case and returns an error instead. 87 resources.CpuMaximum = calculateCPUMaximum(cpuLimit, int64(winstats.ProcessorCount())) 88 } 89 90 // The processor resource controls are mutually exclusive on 91 // Windows Server Containers, the order of precedence is 92 // CPUCount first, then CPUMaximum. 93 if resources.CpuCount > 0 { 94 if resources.CpuMaximum > 0 { 95 resources.CpuMaximum = 0 96 klog.InfoS("Mutually exclusive options: CPUCount priority > CPUMaximum priority on Windows Server Containers. CPUMaximum should be ignored") 97 } 98 } 99 100 if memLimit != 0 { 101 resources.MemoryLimitInBytes = memLimit 102 } 103 104 return &resources 105 } 106 107 // generateWindowsContainerConfig generates windows container config for kubelet runtime v1. 108 // Refer https://github.com/kubernetes/community/blob/master/contributors/design-proposals/node/cri-windows.md. 109 func (m *kubeGenericRuntimeManager) generateWindowsContainerConfig(container *v1.Container, pod *v1.Pod, uid *int64, username string) (*runtimeapi.WindowsContainerConfig, error) { 110 wc := &runtimeapi.WindowsContainerConfig{ 111 Resources: m.generateWindowsContainerResources(pod, container), 112 SecurityContext: &runtimeapi.WindowsContainerSecurityContext{}, 113 } 114 115 // setup security context 116 effectiveSc := securitycontext.DetermineEffectiveSecurityContext(pod, container) 117 118 if username != "" { 119 wc.SecurityContext.RunAsUsername = username 120 } 121 if effectiveSc.WindowsOptions != nil && 122 effectiveSc.WindowsOptions.GMSACredentialSpec != nil { 123 wc.SecurityContext.CredentialSpec = *effectiveSc.WindowsOptions.GMSACredentialSpec 124 } 125 126 // override with Windows options if present 127 if effectiveSc.WindowsOptions != nil && effectiveSc.WindowsOptions.RunAsUserName != nil { 128 wc.SecurityContext.RunAsUsername = *effectiveSc.WindowsOptions.RunAsUserName 129 } 130 131 if securitycontext.HasWindowsHostProcessRequest(pod, container) { 132 wc.SecurityContext.HostProcess = true 133 } 134 135 return wc, nil 136 } 137 138 // calculateCPUMaximum calculates the maximum CPU given a limit and a number of cpus while ensuring it's in range [1,10000]. 139 func calculateCPUMaximum(cpuLimit *resource.Quantity, cpuCount int64) int64 { 140 cpuMaximum := 10 * cpuLimit.MilliValue() / cpuCount 141 142 // ensure cpuMaximum is in range [1, 10000]. 143 if cpuMaximum < 1 { 144 cpuMaximum = 1 145 } else if cpuMaximum > 10000 { 146 cpuMaximum = 10000 147 } 148 return cpuMaximum 149 } 150 151 func toKubeContainerResources(statusResources *runtimeapi.ContainerResources) *kubecontainer.ContainerResources { 152 var cStatusResources *kubecontainer.ContainerResources 153 runtimeStatusResources := statusResources.GetWindows() 154 if runtimeStatusResources != nil { 155 var memLimit, cpuLimit *resource.Quantity 156 157 // Used the reversed formula from the calculateCPUMaximum function 158 if runtimeStatusResources.CpuMaximum > 0 { 159 cpuLimitValue := runtimeStatusResources.CpuMaximum * int64(winstats.ProcessorCount()) / 10 160 cpuLimit = resource.NewMilliQuantity(cpuLimitValue, resource.DecimalSI) 161 } 162 163 if runtimeStatusResources.MemoryLimitInBytes > 0 { 164 memLimit = resource.NewQuantity(runtimeStatusResources.MemoryLimitInBytes, resource.BinarySI) 165 } 166 167 if cpuLimit != nil || memLimit != nil { 168 cStatusResources = &kubecontainer.ContainerResources{ 169 CPULimit: cpuLimit, 170 MemoryLimit: memLimit, 171 } 172 } 173 } 174 return cStatusResources 175 }