mirror of
https://github.com/optim-enterprises-bv/kubernetes.git
synced 2025-11-03 03:38:15 +00:00
142 lines
4.9 KiB
Go
142 lines
4.9 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
/*
|
|
Copyright 2020 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package cm
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
|
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
|
|
"k8s.io/klog/v2"
|
|
kubefeatures "k8s.io/kubernetes/pkg/features"
|
|
"k8s.io/kubernetes/pkg/kubelet/winstats"
|
|
"k8s.io/utils/cpuset"
|
|
)
|
|
|
|
func (i *internalContainerLifecycleImpl) PreCreateContainer(pod *v1.Pod, container *v1.Container, containerConfig *runtimeapi.ContainerConfig) error {
|
|
if !utilfeature.DefaultFeatureGate.Enabled(kubefeatures.WindowsCPUAndMemoryAffinity) {
|
|
return nil
|
|
}
|
|
|
|
klog.V(4).Info("PreCreateContainer for Windows")
|
|
|
|
// retrieve CPU and NUMA affinity from CPU Manager and Memory Manager (if enabled)
|
|
var allocatedCPUs cpuset.CPUSet
|
|
if i.cpuManager != nil {
|
|
allocatedCPUs = i.cpuManager.GetCPUAffinity(string(pod.UID), container.Name)
|
|
}
|
|
|
|
var numaNodes sets.Set[int]
|
|
if i.memoryManager != nil {
|
|
numaNodes = i.memoryManager.GetMemoryNUMANodes(pod, container)
|
|
}
|
|
|
|
// Gather all CPUs associated with the selected NUMA nodes
|
|
var allNumaNodeCPUs []winstats.GroupAffinity
|
|
for _, numaNode := range sets.List(numaNodes) {
|
|
affinity, err := winstats.GetCPUsforNUMANode(uint16(numaNode))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get CPUs for NUMA node %d: %v", numaNode, err)
|
|
}
|
|
allNumaNodeCPUs = append(allNumaNodeCPUs, *affinity)
|
|
}
|
|
|
|
var finalCPUSet = computeFinalCpuSet(allocatedCPUs, allNumaNodeCPUs)
|
|
|
|
klog.V(4).InfoS("Setting CPU affinity", "affinity", finalCPUSet, "container", container.Name, "pod", pod.UID)
|
|
|
|
// Set CPU group affinities in the container config
|
|
if finalCPUSet != nil {
|
|
var cpusToGroupAffinities []*runtimeapi.WindowsCpuGroupAffinity
|
|
for group, mask := range groupMasks(finalCPUSet) {
|
|
|
|
cpusToGroupAffinities = append(cpusToGroupAffinities, &runtimeapi.WindowsCpuGroupAffinity{
|
|
CpuGroup: uint32(group),
|
|
CpuMask: uint64(mask),
|
|
})
|
|
}
|
|
containerConfig.Windows.Resources.AffinityCpus = cpusToGroupAffinities
|
|
}
|
|
|
|
// return nil if no CPUs were selected
|
|
return nil
|
|
}
|
|
|
|
// computeFinalCpuSet determines the final set of CPUs to use based on the CPU and memory managers
|
|
// and is extracted so that it can be tested
|
|
func computeFinalCpuSet(allocatedCPUs cpuset.CPUSet, allNumaNodeCPUs []winstats.GroupAffinity) sets.Set[int] {
|
|
if !allocatedCPUs.IsEmpty() && len(allNumaNodeCPUs) > 0 {
|
|
// Both CPU and memory managers are enabled
|
|
|
|
numaNodeAffinityCPUSet := computeCPUSet(allNumaNodeCPUs)
|
|
cpuManagerAffinityCPUSet := sets.New[int](allocatedCPUs.List()...)
|
|
|
|
// Determine which set of CPUs to use using the following logic outlined in the KEP:
|
|
// Case 1: CPU manager selects more CPUs than those available in the NUMA nodes selected by the memory manager
|
|
// Case 2: CPU manager selects fewer CPUs, and they all fall within the CPUs available in the NUMA nodes selected by the memory manager
|
|
// Case 3: CPU manager selects fewer CPUs, but some are outside of the CPUs available in the NUMA nodes selected by the memory manager
|
|
|
|
if cpuManagerAffinityCPUSet.Len() > numaNodeAffinityCPUSet.Len() {
|
|
// Case 1, use CPU manager selected CPUs
|
|
return cpuManagerAffinityCPUSet
|
|
} else if numaNodeAffinityCPUSet.IsSuperset(cpuManagerAffinityCPUSet) {
|
|
// case 2, use CPU manager selected CPUstry
|
|
return cpuManagerAffinityCPUSet
|
|
} else {
|
|
// Case 3, merge CPU manager and memory manager selected CPUs
|
|
return cpuManagerAffinityCPUSet.Union(numaNodeAffinityCPUSet)
|
|
}
|
|
} else if !allocatedCPUs.IsEmpty() {
|
|
// Only CPU manager is enabled, use CPU manager selected CPUs
|
|
return sets.New[int](allocatedCPUs.List()...)
|
|
} else if len(allNumaNodeCPUs) > 0 {
|
|
// Only memory manager is enabled, use CPUs associated with selected NUMA nodes
|
|
return computeCPUSet(allNumaNodeCPUs)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// computeCPUSet converts a list of GroupAffinity to a set of CPU IDs
|
|
func computeCPUSet(affinities []winstats.GroupAffinity) sets.Set[int] {
|
|
cpuSet := sets.New[int]()
|
|
for _, affinity := range affinities {
|
|
for i := 0; i < 64; i++ {
|
|
if (affinity.Mask>>i)&1 == 1 {
|
|
cpuID := int(affinity.Group)*64 + i
|
|
cpuSet.Insert(cpuID)
|
|
}
|
|
}
|
|
}
|
|
return cpuSet
|
|
}
|
|
|
|
// groupMasks converts a set of CPU IDs into group and mask representations
|
|
func groupMasks(cpuSet sets.Set[int]) map[int]uint64 {
|
|
groupMasks := make(map[int]uint64)
|
|
for cpu := range cpuSet {
|
|
group := cpu / 64
|
|
mask := uint64(1) << (cpu % 64)
|
|
groupMasks[group] |= mask
|
|
}
|
|
return groupMasks
|
|
}
|