diff --git a/pkg/scheduler/framework/plugins/imagelocality/image_locality.go b/pkg/scheduler/framework/plugins/imagelocality/image_locality.go index 6733c09851e..35acba98edc 100644 --- a/pkg/scheduler/framework/plugins/imagelocality/image_locality.go +++ b/pkg/scheduler/framework/plugins/imagelocality/image_locality.go @@ -74,8 +74,8 @@ func (pl *ImageLocality) Score(ctx context.Context, state fwk.CycleState, pod *v } totalNumNodes := len(nodeInfos) - imageScores := sumImageScores(nodeInfo, pod, totalNumNodes) - score := calculatePriority(imageScores, len(pod.Spec.InitContainers)+len(pod.Spec.Containers)) + imageScores, imageCount := sumImageScores(nodeInfo, pod, totalNumNodes) + score := calculatePriority(imageScores, imageCount) return score, nil } @@ -90,35 +90,41 @@ func New(_ context.Context, _ runtime.Object, h fwk.Handle) (fwk.Plugin, error) return &ImageLocality{handle: h}, nil } -// calculatePriority returns the priority of a node. Given the sumScores of requested images on the node, the node's -// priority is obtained by scaling the maximum priority value with a ratio proportional to the sumScores. -func calculatePriority(sumScores int64, numContainers int) int64 { - maxThreshold := maxContainerThreshold * int64(numContainers) +// calculatePriority returns the priority of a node. Given the sumScores of requested +// images on the node and the Pod's image count, the node's priority is obtained by +// scaling the maximum priority value with a ratio derived from the clamped sumScores. +func calculatePriority(sumScores int64, imageCount int) int64 { + maxThreshold := maxContainerThreshold * int64(imageCount) if sumScores < minThreshold { sumScores = minThreshold } else if sumScores > maxThreshold { sumScores = maxThreshold } - return fwk.MaxNodeScore * (sumScores - minThreshold) / (maxThreshold - minThreshold) + return fwk.MaxScore * (sumScores - minThreshold) / (maxThreshold - minThreshold) } -// sumImageScores returns the total image score for all container images in the Pod spec, -// including regular containers, init containers, and image volumes, that already exist on the node. -// Each image receives a raw score of its size, scaled by scaledImageScore. The raw scores are later used to calculate -// the final score. -func sumImageScores(nodeInfo fwk.NodeInfo, pod *v1.Pod, totalNumNodes int) int64 { +// sumImageScores returns the total image score and the number of image sources in the Pod spec, +// including regular containers, init containers, and image volumes. Images that already exist on the node +// receive a raw score of their size, scaled by scaledImageScore. +// The raw score and image count are later used to calculate the final score. +func sumImageScores(nodeInfo fwk.NodeInfo, pod *v1.Pod, totalNumNodes int) (int64, int) { var sum int64 + for _, container := range pod.Spec.InitContainers { if state, ok := nodeInfo.GetImageStates()[normalizedImageName(container.Image)]; ok { sum += scaledImageScore(state, totalNumNodes) } } + for _, container := range pod.Spec.Containers { if state, ok := nodeInfo.GetImageStates()[normalizedImageName(container.Image)]; ok { sum += scaledImageScore(state, totalNumNodes) } } + + imageCount := len(pod.Spec.InitContainers) + len(pod.Spec.Containers) + for _, volume := range pod.Spec.Volumes { if volume.Image == nil { continue @@ -126,8 +132,9 @@ func sumImageScores(nodeInfo fwk.NodeInfo, pod *v1.Pod, totalNumNodes int) int64 if state, ok := nodeInfo.GetImageStates()[normalizedImageName(volume.Image.Reference)]; ok { sum += scaledImageScore(state, totalNumNodes) } + imageCount++ } - return sum + return sum, imageCount } // scaledImageScore returns an adaptively scaled score for the given state of an image. diff --git a/pkg/scheduler/framework/plugins/imagelocality/image_locality_test.go b/pkg/scheduler/framework/plugins/imagelocality/image_locality_test.go index 193a427848f..1fd69c0a81e 100644 --- a/pkg/scheduler/framework/plugins/imagelocality/image_locality_test.go +++ b/pkg/scheduler/framework/plugins/imagelocality/image_locality_test.go @@ -111,6 +111,17 @@ func TestImageLocalityPriority(t *testing.T) { }, } + test30300AsContainers := v1.PodSpec{ + Containers: []v1.Container{ + { + Image: "gcr.io/30", + }, + { + Image: "gcr.io/300", + }, + }, + } + test30Init300 := v1.PodSpec{ Containers: []v1.Container{ { @@ -360,20 +371,35 @@ func TestImageLocalityPriority(t *testing.T) { name: "pod with multiple small images", }, { - // Pod: gcr.io/300 gcr.io/30 + // Pod: gcr.io/30 ImageVolume: gcr.io/300 // Node1 // Image: gcr.io/300:latest 300MB - // Score: 100 * (300M * 1/2 - 23M) / (1000M - 23M) = 12 + // Score: 100 * (300M * 1/2 - 23M) / (1000M * 2 - 23M) = 6 // Node2 // Image: gcr.io/30:latest 30MB - // Score: 100 * (30M - 23M) / (1000M - 23M) = 0 + // Score: 0 (30M * 1/2 < 23M, min-threshold) pod: &v1.Pod{Spec: testImageVolume}, nodes: []*v1.Node{makeImageNode("node1", node300600900), makeImageNode("node2", node400030)}, - expectedList: []fwk.NodeScore{{Name: "node1", Score: 12}, {Name: "node2", Score: 0}}, + expectedList: []fwk.NodeScore{{Name: "node1", Score: 6}, {Name: "node2", Score: 0}}, name: "pod with ImageVolume", }, + { + // Pod: gcr.io/30 gcr.io/300 + + // Node1 + // Image: gcr.io/300:latest 300MB + // Score: 100 * (300M * 1/2 - 23M) / (1000M * 2 - 23M) = 6 + + // Node2 + // Image: gcr.io/30:latest 30MB + // Score: 0 (30M * 1/2 < 23M, min-threshold) + pod: &v1.Pod{Spec: test30300AsContainers}, + nodes: []*v1.Node{makeImageNode("node1", node300600900), makeImageNode("node2", node400030)}, + expectedList: []fwk.NodeScore{{Name: "node1", Score: 6}, {Name: "node2", Score: 0}}, + name: "same images as ImageVolume pod but as regular container images", + }, { // Pod: gcr.io/30 InitContainers: gcr.io/300