@@ -26,6 +26,7 @@ import (
26
26
"k8s.io/apimachinery/pkg/api/resource"
27
27
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28
28
"k8s.io/apimachinery/pkg/types"
29
+ utilerrors "k8s.io/apimachinery/pkg/util/errors"
29
30
"k8s.io/klog/v2"
30
31
31
32
"github.com/koordinator-sh/koordinator/apis/extension"
@@ -309,6 +310,8 @@ func evictPodsFromSourceNodes(
309
310
nodePoolName string ,
310
311
sourceNodes , destinationNodes ,
311
312
prodSourceNodes , prodDestinationNodes , bothDestinationNodes []NodeInfo ,
313
+ nodeUsages map [string ]* NodeUsage ,
314
+ nodeThresholds map [string ]NodeThresholds ,
312
315
dryRun bool ,
313
316
nodeFit bool ,
314
317
resourceWeights map [corev1.ResourceName ]int64 ,
@@ -346,7 +349,8 @@ func evictPodsFromSourceNodes(
346
349
klog .V (4 ).InfoS ("Total node usage capacity to be moved" , nodeKeysAndValues ... )
347
350
348
351
targetNodes = append (targetNodes , bothTotalNodes ... )
349
- balancePods (ctx , nodePoolName , sourceNodes , targetNodes , nodeTotalAvailableUsages , dryRun , nodeFit , false , resourceWeights , podEvictor ,
352
+ balancePods (ctx , nodePoolName , sourceNodes , targetNodes , nodeUsages , nodeThresholds ,
353
+ nodeTotalAvailableUsages , dryRun , nodeFit , false , resourceWeights , podEvictor ,
350
354
podFilter , nodeIndexer , continueEviction , evictionReasonGenerator )
351
355
352
356
// bothLowNode will be used by nodeHigh and prodHigh nodes, needs sub resources used by pods on nodeHigh.
@@ -384,7 +388,8 @@ func evictPodsFromSourceNodes(
384
388
prodKeysAndValues = append (prodKeysAndValues , string (resourceName ), quantity .String ())
385
389
}
386
390
klog .V (4 ).InfoS ("Total prod usage capacity to be moved" , prodKeysAndValues ... )
387
- balancePods (ctx , nodePoolName , prodSourceNodes , prodTargetNodes , prodTotalAvailableUsages , dryRun , nodeFit , true , resourceWeights , podEvictor ,
391
+ balancePods (ctx , nodePoolName , prodSourceNodes , prodTargetNodes , nodeUsages , nodeThresholds ,
392
+ prodTotalAvailableUsages , dryRun , nodeFit , true , resourceWeights , podEvictor ,
388
393
podFilter , nodeIndexer , continueEviction , evictionReasonGenerator )
389
394
}
390
395
@@ -409,6 +414,8 @@ func balancePods(ctx context.Context,
409
414
nodePoolName string ,
410
415
sourceNodes []NodeInfo ,
411
416
targetNodes []* corev1.Node ,
417
+ nodeUsages map [string ]* NodeUsage ,
418
+ nodeThresholds map [string ]NodeThresholds ,
412
419
totalAvailableUsages map [corev1.ResourceName ]* resource.Quantity ,
413
420
dryRun bool ,
414
421
nodeFit , prod bool ,
@@ -431,7 +438,9 @@ func balancePods(ctx context.Context,
431
438
if ! nodeFit {
432
439
return true
433
440
}
434
- return nodeutil .PodFitsAnyNode (nodeIndexer , pod , targetNodes )
441
+ podNamespacedName := types.NamespacedName {Namespace : pod .Namespace , Name : pod .Name }
442
+ podMetric := srcNode .podMetrics [podNamespacedName ]
443
+ return podFitsAnyNodeWithThreshold (nodeIndexer , pod , targetNodes , nodeUsages , nodeThresholds , prod , podMetric )
435
444
}),
436
445
)
437
446
klog .V (4 ).InfoS ("Evicting pods from node" ,
@@ -701,3 +710,47 @@ func sortPodsOnOneOverloadedNode(srcNode NodeInfo, removablePods []*corev1.Pod,
701
710
weights ,
702
711
)
703
712
}
713
+
714
+ // podFitsAnyNodeWithThreshold checks if the given pod will fit any of the given nodes. It also checks if the node
715
+ // utilization will exceed the threshold after this pod was scheduled on it.
716
+ func podFitsAnyNodeWithThreshold (nodeIndexer podutil.GetPodsAssignedToNodeFunc , pod * corev1.Pod , nodes []* corev1.Node ,
717
+ nodeUsages map [string ]* NodeUsage , nodeThresholds map [string ]NodeThresholds , prod bool , podMetric * slov1alpha1.ResourceMap ) bool {
718
+ for _ , node := range nodes {
719
+ errors := nodeutil .NodeFit (nodeIndexer , pod , node )
720
+ if len (errors ) == 0 {
721
+ // check if node utilization exceeds threshold if pod scheduled
722
+ nodeUsage , usageOk := nodeUsages [node .Name ]
723
+ nodeThreshold , thresholdOk := nodeThresholds [node .Name ]
724
+ if usageOk && thresholdOk {
725
+ var usage , thresholds map [corev1.ResourceName ]* resource.Quantity
726
+ if prod {
727
+ usage = nodeUsage .prodUsage
728
+ thresholds = nodeThreshold .prodHighResourceThreshold
729
+ } else {
730
+ usage = nodeUsage .usage
731
+ thresholds = nodeThreshold .highResourceThreshold
732
+ }
733
+ exceeded := false
734
+ for resourceName , threshold := range thresholds {
735
+ if used := usage [resourceName ]; used != nil {
736
+ used .Add (podMetric .ResourceList [resourceName ])
737
+ if used .Cmp (* threshold ) > 0 {
738
+ exceeded = true
739
+ break
740
+ }
741
+ }
742
+
743
+ }
744
+ if exceeded {
745
+ klog .V (4 ).InfoS ("Pod may cause node over-utilized" , "pod" , klog .KObj (pod ), "node" , klog .KObj (node ))
746
+ continue
747
+ }
748
+ }
749
+ klog .V (4 ).InfoS ("Pod fits on node" , "pod" , klog .KObj (pod ), "node" , klog .KObj (node ))
750
+ return true
751
+ } else {
752
+ klog .V (4 ).InfoS ("Pod does not fit on node" , "pod" , klog .KObj (pod ), "node" , klog .KObj (node ), "errors" , utilerrors .NewAggregate (errors ))
753
+ }
754
+ }
755
+ return false
756
+ }
0 commit comments