Skip to content

Commit 1e467ca

Browse files
committed
Update imageLocality plugin
to account for ImageVolume images when scoring and prioritizing nodes with required pod images Signed-off-by: bmordeha <bmordeha@redhat.com>
1 parent 931ad2a commit 1e467ca

File tree

2 files changed

+93
-34
lines changed

2 files changed

+93
-34
lines changed

pkg/scheduler/framework/plugins/imagelocality/image_locality.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323

2424
v1 "k8s.io/api/core/v1"
2525
"k8s.io/apimachinery/pkg/runtime"
26+
utilfeature "k8s.io/apiserver/pkg/util/feature"
27+
"k8s.io/kubernetes/pkg/features"
2628
"k8s.io/kubernetes/pkg/scheduler/framework"
2729
"k8s.io/kubernetes/pkg/scheduler/framework/plugins/names"
2830
)
@@ -92,7 +94,8 @@ func calculatePriority(sumScores int64, numContainers int) int64 {
9294
return framework.MaxNodeScore * (sumScores - minThreshold) / (maxThreshold - minThreshold)
9395
}
9496

95-
// sumImageScores returns the sum of image scores of all the containers that are already on the node.
97+
// sumImageScores returns the sum of image scores of all the containers, including init containers and
98+
// imageVolumes specified in the pod's spec, that are already on the node.
9699
// Each image receives a raw score of its size, scaled by scaledImageScore. The raw scores are later used to calculate
97100
// the final score.
98101
func sumImageScores(nodeInfo *framework.NodeInfo, pod *v1.Pod, totalNumNodes int) int64 {
@@ -107,6 +110,16 @@ func sumImageScores(nodeInfo *framework.NodeInfo, pod *v1.Pod, totalNumNodes int
107110
sum += scaledImageScore(state, totalNumNodes)
108111
}
109112
}
113+
if utilfeature.DefaultFeatureGate.Enabled(features.ImageVolume) {
114+
for _, volume := range pod.Spec.Volumes {
115+
if volume.Image == nil {
116+
continue
117+
}
118+
if state, ok := nodeInfo.ImageStates[normalizedImageName(volume.Image.Reference)]; ok {
119+
sum += scaledImageScore(state, totalNumNodes)
120+
}
121+
}
122+
}
110123
return sum
111124
}
112125

pkg/scheduler/framework/plugins/imagelocality/image_locality_test.go

Lines changed: 79 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ import (
2626

2727
v1 "k8s.io/api/core/v1"
2828
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
utilfeature "k8s.io/apiserver/pkg/util/feature"
30+
featuregatetesting "k8s.io/component-base/featuregate/testing"
2931
"k8s.io/klog/v2/ktesting"
32+
"k8s.io/kubernetes/pkg/features"
3033
"k8s.io/kubernetes/pkg/scheduler/backend/cache"
3134
"k8s.io/kubernetes/pkg/scheduler/framework"
3235
"k8s.io/kubernetes/pkg/scheduler/framework/runtime"
@@ -92,6 +95,24 @@ func TestImageLocalityPriority(t *testing.T) {
9295
},
9396
}
9497

98+
testImageVolume := v1.PodSpec{
99+
Containers: []v1.Container{
100+
{
101+
Image: "gcr.io/30",
102+
},
103+
},
104+
Volumes: []v1.Volume{
105+
{
106+
Name: "imageVolume",
107+
VolumeSource: v1.VolumeSource{
108+
Image: &v1.ImageVolumeSource{
109+
Reference: "gcr.io/300",
110+
},
111+
},
112+
},
113+
},
114+
}
115+
95116
test30Init300 := v1.PodSpec{
96117
Containers: []v1.Container{
97118
{
@@ -236,11 +257,12 @@ func TestImageLocalityPriority(t *testing.T) {
236257
nodeWithNoImages := v1.NodeStatus{}
237258

238259
tests := []struct {
239-
pod *v1.Pod
240-
pods []*v1.Pod
241-
nodes []*v1.Node
242-
expectedList framework.NodeScoreList
243-
name string
260+
featureEnabled bool
261+
pod *v1.Pod
262+
pods []*v1.Pod
263+
nodes []*v1.Node
264+
expectedList framework.NodeScoreList
265+
name string
244266
}{
245267
{
246268
// Pod: gcr.io/40 gcr.io/250
@@ -252,10 +274,11 @@ func TestImageLocalityPriority(t *testing.T) {
252274
// Node2
253275
// Image: gcr.io/250:latest 250MB
254276
// Score: 100 * (250M/2 - 23M)/(1000M * 2 - 23M) = 5
255-
pod: &v1.Pod{Spec: test40250},
256-
nodes: []*v1.Node{makeImageNode("node1", node403002000), makeImageNode("node2", node25010)},
257-
expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: 5}},
258-
name: "two images spread on two nodes, prefer the larger image one",
277+
featureEnabled: false,
278+
pod: &v1.Pod{Spec: test40250},
279+
nodes: []*v1.Node{makeImageNode("node1", node403002000), makeImageNode("node2", node25010)},
280+
expectedList: []framework.NodeScore{{Name: "node1", Score: 0}, {Name: "node2", Score: 5}},
281+
name: "two images spread on two nodes, prefer the larger image one",
259282
},
260283
{
261284
// Pod: gcr.io/40 gcr.io/300
@@ -267,10 +290,11 @@ func TestImageLocalityPriority(t *testing.T) {
267290
// Node2
268291
// Image: not present
269292
// Score: 0
270-
pod: &v1.Pod{Spec: test40300},
271-
nodes: []*v1.Node{makeImageNode("node1", node403002000), makeImageNode("node2", node25010)},
272-
expectedList: []framework.NodeScore{{Name: "node1", Score: 7}, {Name: "node2", Score: 0}},
273-
name: "two images on one node, prefer this node",
293+
featureEnabled: false,
294+
pod: &v1.Pod{Spec: test40300},
295+
nodes: []*v1.Node{makeImageNode("node1", node403002000), makeImageNode("node2", node25010)},
296+
expectedList: []framework.NodeScore{{Name: "node1", Score: 7}, {Name: "node2", Score: 0}},
297+
name: "two images on one node, prefer this node",
274298
},
275299
{
276300
// Pod: gcr.io/4000 gcr.io/10
@@ -282,10 +306,11 @@ func TestImageLocalityPriority(t *testing.T) {
282306
// Node2
283307
// Image: gcr.io/10:latest 10MB
284308
// Score: 0 (10M/2 < 23M, min-threshold)
285-
pod: &v1.Pod{Spec: testMinMax},
286-
nodes: []*v1.Node{makeImageNode("node1", node400030), makeImageNode("node2", node25010)},
287-
expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: 0}},
288-
name: "if exceed limit, use limit",
309+
featureEnabled: false,
310+
pod: &v1.Pod{Spec: testMinMax},
311+
nodes: []*v1.Node{makeImageNode("node1", node400030), makeImageNode("node2", node25010)},
312+
expectedList: []framework.NodeScore{{Name: "node1", Score: framework.MaxNodeScore}, {Name: "node2", Score: 0}},
313+
name: "if exceed limit, use limit",
289314
},
290315
{
291316
// Pod: gcr.io/4000 gcr.io/10
@@ -301,10 +326,11 @@ func TestImageLocalityPriority(t *testing.T) {
301326
// Node3
302327
// Image:
303328
// Score: 0
304-
pod: &v1.Pod{Spec: testMinMax},
305-
nodes: []*v1.Node{makeImageNode("node1", node400030), makeImageNode("node2", node25010), makeImageNode("node3", nodeWithNoImages)},
306-
expectedList: []framework.NodeScore{{Name: "node1", Score: 66}, {Name: "node2", Score: 0}, {Name: "node3", Score: 0}},
307-
name: "if exceed limit, use limit (with node which has no images present)",
329+
featureEnabled: false,
330+
pod: &v1.Pod{Spec: testMinMax},
331+
nodes: []*v1.Node{makeImageNode("node1", node400030), makeImageNode("node2", node25010), makeImageNode("node3", nodeWithNoImages)},
332+
expectedList: []framework.NodeScore{{Name: "node1", Score: 66}, {Name: "node2", Score: 0}, {Name: "node3", Score: 0}},
333+
name: "if exceed limit, use limit (with node which has no images present)",
308334
},
309335
{
310336
// Pod: gcr.io/300 gcr.io/600 gcr.io/900
@@ -320,10 +346,11 @@ func TestImageLocalityPriority(t *testing.T) {
320346
// Node3
321347
// Image:
322348
// Score: 0
323-
pod: &v1.Pod{Spec: test300600900},
324-
nodes: []*v1.Node{makeImageNode("node1", node60040900), makeImageNode("node2", node300600900), makeImageNode("node3", nodeWithNoImages)},
325-
expectedList: []framework.NodeScore{{Name: "node1", Score: 32}, {Name: "node2", Score: 36}, {Name: "node3", Score: 0}},
326-
name: "pod with multiple large images, node2 is preferred",
349+
featureEnabled: false,
350+
pod: &v1.Pod{Spec: test300600900},
351+
nodes: []*v1.Node{makeImageNode("node1", node60040900), makeImageNode("node2", node300600900), makeImageNode("node3", nodeWithNoImages)},
352+
expectedList: []framework.NodeScore{{Name: "node1", Score: 32}, {Name: "node2", Score: 36}, {Name: "node3", Score: 0}},
353+
name: "pod with multiple large images, node2 is preferred",
327354
},
328355
{
329356
// Pod: gcr.io/30 gcr.io/40
@@ -335,10 +362,27 @@ func TestImageLocalityPriority(t *testing.T) {
335362
// Node2
336363
// Image: 100 * (30M - 23M) / (1000M * 2 - 23M) = 0
337364
// Score: 0
338-
pod: &v1.Pod{Spec: test3040},
339-
nodes: []*v1.Node{makeImageNode("node1", node203040), makeImageNode("node2", node400030)},
340-
expectedList: []framework.NodeScore{{Name: "node1", Score: 1}, {Name: "node2", Score: 0}},
341-
name: "pod with multiple small images",
365+
featureEnabled: false,
366+
pod: &v1.Pod{Spec: test3040},
367+
nodes: []*v1.Node{makeImageNode("node1", node203040), makeImageNode("node2", node400030)},
368+
expectedList: []framework.NodeScore{{Name: "node1", Score: 1}, {Name: "node2", Score: 0}},
369+
name: "pod with multiple small images",
370+
},
371+
{
372+
// Pod: gcr.io/300 gcr.io/30
373+
374+
// Node1
375+
// Image: gcr.io/300:latest 300MB
376+
// Score: 100 * (300M * 1/2 - 23M) / (1000M - 23M) = 12
377+
378+
// Node2
379+
// Image: gcr.io/30:latest 30MB
380+
// Score: 100 * (30M - 23M) / (1000M - 23M) = 0
381+
featureEnabled: true,
382+
pod: &v1.Pod{Spec: testImageVolume},
383+
nodes: []*v1.Node{makeImageNode("node1", node300600900), makeImageNode("node2", node400030)},
384+
expectedList: []framework.NodeScore{{Name: "node1", Score: 12}, {Name: "node2", Score: 0}},
385+
name: "pod with ImageVolume",
342386
},
343387
{
344388
// Pod: gcr.io/30 InitContainers: gcr.io/300
@@ -350,10 +394,11 @@ func TestImageLocalityPriority(t *testing.T) {
350394
// Node2
351395
// Image: gcr.io/20:latest 20MB, gcr.io/30:latest 30MB, gcr.io/40:latest 40MB
352396
// Score: 100 * (30M * 1/2 - 23M) / (1000M * 2 - 23M) = 0
353-
pod: &v1.Pod{Spec: test30Init300},
354-
nodes: []*v1.Node{makeImageNode("node1", node403002000), makeImageNode("node2", node203040)},
355-
expectedList: []framework.NodeScore{{Name: "node1", Score: 6}, {Name: "node2", Score: 0}},
356-
name: "include InitContainers: two images spread on two nodes, prefer the larger image one",
397+
featureEnabled: false,
398+
pod: &v1.Pod{Spec: test30Init300},
399+
nodes: []*v1.Node{makeImageNode("node1", node403002000), makeImageNode("node2", node203040)},
400+
expectedList: []framework.NodeScore{{Name: "node1", Score: 6}, {Name: "node2", Score: 0}},
401+
name: "include InitContainers: two images spread on two nodes, prefer the larger image one",
357402
},
358403
}
359404

@@ -362,6 +407,7 @@ func TestImageLocalityPriority(t *testing.T) {
362407
_, ctx := ktesting.NewTestContext(t)
363408
ctx, cancel := context.WithCancel(ctx)
364409
defer cancel()
410+
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ImageVolume, test.featureEnabled)
365411

366412
snapshot := cache.NewSnapshot(nil, test.nodes)
367413
state := framework.NewCycleState()

0 commit comments

Comments
 (0)