...

Source file src/k8s.io/kubernetes/pkg/kubelet/kuberuntime/kuberuntime_container_windows.go

Documentation: k8s.io/kubernetes/pkg/kubelet/kuberuntime

     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  }
   176  

View as plain text