Skip to content

Commit 413bc1a

Browse files
authored
Merge pull request kubernetes#91138 from chendave/imagelocality
Define the thresholds per the size of container images
2 parents 46d08c8 + 42fbb1d commit 413bc1a

File tree

2 files changed

+166
-20
lines changed

2 files changed

+166
-20
lines changed

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ import (
2929
// The two thresholds are used as bounds for the image score range. They correspond to a reasonable size range for
3030
// container images compressed and stored in registries; 90%ile of images on dockerhub drops into this range.
3131
const (
32-
mb int64 = 1024 * 1024
33-
minThreshold int64 = 23 * mb
34-
maxThreshold int64 = 1000 * mb
32+
mb int64 = 1024 * 1024
33+
minThreshold int64 = 23 * mb
34+
maxContainerThreshold int64 = 1000 * mb
3535
)
3636

3737
// ImageLocality is a score plugin that favors nodes that already have requested pod container's images.
@@ -62,7 +62,7 @@ func (pl *ImageLocality) Score(ctx context.Context, state *framework.CycleState,
6262
}
6363
totalNumNodes := len(nodeInfos)
6464

65-
score := calculatePriority(sumImageScores(nodeInfo, pod.Spec.Containers, totalNumNodes))
65+
score := calculatePriority(sumImageScores(nodeInfo, pod.Spec.Containers, totalNumNodes), len(pod.Spec.Containers))
6666

6767
return score, nil
6868
}
@@ -79,7 +79,8 @@ func New(_ runtime.Object, h framework.FrameworkHandle) (framework.Plugin, error
7979

8080
// calculatePriority returns the priority of a node. Given the sumScores of requested images on the node, the node's
8181
// priority is obtained by scaling the maximum priority value with a ratio proportional to the sumScores.
82-
func calculatePriority(sumScores int64) int64 {
82+
func calculatePriority(sumScores int64, numContainers int) int64 {
83+
maxThreshold := maxContainerThreshold * int64(numContainers)
8384
if sumScores < minThreshold {
8485
sumScores = minThreshold
8586
} else if sumScores > maxThreshold {

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

Lines changed: 160 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,32 @@ func TestImageLocalityPriority(t *testing.T) {
5959
Image: "gcr.io/10",
6060
},
6161
{
62-
Image: "gcr.io/2000",
62+
Image: "gcr.io/4000",
63+
},
64+
},
65+
}
66+
67+
test300600900 := v1.PodSpec{
68+
Containers: []v1.Container{
69+
{
70+
Image: "gcr.io/300",
71+
},
72+
{
73+
Image: "gcr.io/600",
74+
},
75+
{
76+
Image: "gcr.io/900",
77+
},
78+
},
79+
}
80+
81+
test3040 := v1.PodSpec{
82+
Containers: []v1.Container{
83+
{
84+
Image: "gcr.io/30",
85+
},
86+
{
87+
Image: "gcr.io/40",
6388
},
6489
},
6590
}
@@ -108,6 +133,92 @@ func TestImageLocalityPriority(t *testing.T) {
108133
},
109134
}
110135

136+
node60040900 := v1.NodeStatus{
137+
Images: []v1.ContainerImage{
138+
{
139+
Names: []string{
140+
"gcr.io/600:latest",
141+
},
142+
SizeBytes: int64(600 * mb),
143+
},
144+
{
145+
Names: []string{
146+
"gcr.io/40:latest",
147+
},
148+
SizeBytes: int64(40 * mb),
149+
},
150+
{
151+
Names: []string{
152+
"gcr.io/900:latest",
153+
},
154+
SizeBytes: int64(900 * mb),
155+
},
156+
},
157+
}
158+
159+
node300600900 := v1.NodeStatus{
160+
Images: []v1.ContainerImage{
161+
{
162+
Names: []string{
163+
"gcr.io/300:latest",
164+
},
165+
SizeBytes: int64(300 * mb),
166+
},
167+
{
168+
Names: []string{
169+
"gcr.io/600:latest",
170+
},
171+
SizeBytes: int64(600 * mb),
172+
},
173+
{
174+
Names: []string{
175+
"gcr.io/900:latest",
176+
},
177+
SizeBytes: int64(900 * mb),
178+
},
179+
},
180+
}
181+
182+
node400030 := v1.NodeStatus{
183+
Images: []v1.ContainerImage{
184+
{
185+
Names: []string{
186+
"gcr.io/4000:latest",
187+
},
188+
SizeBytes: int64(4000 * mb),
189+
},
190+
{
191+
Names: []string{
192+
"gcr.io/30:latest",
193+
},
194+
SizeBytes: int64(30 * mb),
195+
},
196+
},
197+
}
198+
199+
node203040 := v1.NodeStatus{
200+
Images: []v1.ContainerImage{
201+
{
202+
Names: []string{
203+
"gcr.io/20:latest",
204+
},
205+
SizeBytes: int64(20 * mb),
206+
},
207+
{
208+
Names: []string{
209+
"gcr.io/30:latest",
210+
},
211+
SizeBytes: int64(30 * mb),
212+
},
213+
{
214+
Names: []string{
215+
"gcr.io/40:latest",
216+
},
217+
SizeBytes: int64(40 * mb),
218+
},
219+
},
220+
}
221+
111222
nodeWithNoImages := v1.NodeStatus{}
112223

113224
tests := []struct {
@@ -126,61 +237,95 @@ func TestImageLocalityPriority(t *testing.T) {
126237

127238
// Node2
128239
// Image: gcr.io/250:latest 250MB
129-
// Score: 100 * (250M/2 - 23M)/(1000M - 23M) = 100
240+
// Score: 100 * (250M/2 - 23M)/(1000M * 2 - 23M) = 5
130241
pod: &v1.Pod{Spec: test40250},
131242
nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010)},
132-
expectedList: []framework.NodeScore{{Name: "machine1", Score: 0}, {Name: "machine2", Score: 10}},
243+
expectedList: []framework.NodeScore{{Name: "machine1", Score: 0}, {Name: "machine2", Score: 5}},
133244
name: "two images spread on two nodes, prefer the larger image one",
134245
},
135246
{
136247
// Pod: gcr.io/40 gcr.io/300
137248

138249
// Node1
139250
// Image: gcr.io/40:latest 40MB, gcr.io/300:latest 300MB
140-
// Score: 100 * ((40M + 300M)/2 - 23M)/(1000M - 23M) = 15
251+
// Score: 100 * ((40M + 300M)/2 - 23M)/(1000M * 2 - 23M) = 7
141252

142253
// Node2
143254
// Image: not present
144255
// Score: 0
145256
pod: &v1.Pod{Spec: test40300},
146257
nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010)},
147-
expectedList: []framework.NodeScore{{Name: "machine1", Score: 15}, {Name: "machine2", Score: 0}},
258+
expectedList: []framework.NodeScore{{Name: "machine1", Score: 7}, {Name: "machine2", Score: 0}},
148259
name: "two images on one node, prefer this node",
149260
},
150261
{
151-
// Pod: gcr.io/2000 gcr.io/10
262+
// Pod: gcr.io/4000 gcr.io/10
152263

153264
// Node1
154-
// Image: gcr.io/2000:latest 2000MB
155-
// Score: 100 (2000M/2 >= 1000M, max-threshold)
265+
// Image: gcr.io/4000:latest 2000MB
266+
// Score: 100 (4000 * 1/2 >= 1000M * 2, max-threshold)
156267

157268
// Node2
158269
// Image: gcr.io/10:latest 10MB
159270
// Score: 0 (10M/2 < 23M, min-threshold)
160271
pod: &v1.Pod{Spec: testMinMax},
161-
nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010)},
272+
nodes: []*v1.Node{makeImageNode("machine1", node400030), makeImageNode("machine2", node25010)},
162273
expectedList: []framework.NodeScore{{Name: "machine1", Score: framework.MaxNodeScore}, {Name: "machine2", Score: 0}},
163274
name: "if exceed limit, use limit",
164275
},
165276
{
166-
// Pod: gcr.io/2000 gcr.io/10
277+
// Pod: gcr.io/4000 gcr.io/10
167278

168279
// Node1
169-
// Image: gcr.io/2000:latest 2000MB
170-
// Score: 100 * (2000M/3 - 23M)/(1000M - 23M) = 65
280+
// Image: gcr.io/4000:latest 4000MB
281+
// Score: 100 * (4000M/3 - 23M)/(1000M * 2 - 23M) = 66
171282

172283
// Node2
173284
// Image: gcr.io/10:latest 10MB
174-
// Score: 0 (10M/2 < 23M, min-threshold)
285+
// Score: 0 (10M*1/3 < 23M, min-threshold)
175286

176287
// Node3
177288
// Image:
178289
// Score: 0
179290
pod: &v1.Pod{Spec: testMinMax},
180-
nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010), makeImageNode("machine3", nodeWithNoImages)},
181-
expectedList: []framework.NodeScore{{Name: "machine1", Score: 65}, {Name: "machine2", Score: 0}, {Name: "machine3", Score: 0}},
291+
nodes: []*v1.Node{makeImageNode("machine1", node400030), makeImageNode("machine2", node25010), makeImageNode("machine3", nodeWithNoImages)},
292+
expectedList: []framework.NodeScore{{Name: "machine1", Score: 66}, {Name: "machine2", Score: 0}, {Name: "machine3", Score: 0}},
182293
name: "if exceed limit, use limit (with node which has no images present)",
183294
},
295+
{
296+
// Pod: gcr.io/300 gcr.io/600 gcr.io/900
297+
298+
// Node1
299+
// Image: gcr.io/600:latest 600MB, gcr.io/900:latest 900MB
300+
// Score: 100 * (600M * 2/3 + 900M * 2/3 - 23M) / (1000M * 3 - 23M) = 32
301+
302+
// Node2
303+
// Image: gcr.io/300:latest 300MB, gcr.io/600:latest 600MB, gcr.io/900:latest 900MB
304+
// Score: 100 * (300M * 1/3 + 600M * 2/3 + 900M * 2/3 - 23M) / (1000M *3 - 23M) = 36
305+
306+
// Node3
307+
// Image:
308+
// Score: 0
309+
pod: &v1.Pod{Spec: test300600900},
310+
nodes: []*v1.Node{makeImageNode("machine1", node60040900), makeImageNode("machine2", node300600900), makeImageNode("machine3", nodeWithNoImages)},
311+
expectedList: []framework.NodeScore{{Name: "machine1", Score: 32}, {Name: "machine2", Score: 36}, {Name: "machine3", Score: 0}},
312+
name: "pod with multiple large images, machine2 is preferred",
313+
},
314+
{
315+
// Pod: gcr.io/30 gcr.io/40
316+
317+
// Node1
318+
// Image: gcr.io/20:latest 20MB, gcr.io/30:latest 30MB gcr.io/40:latest 40MB
319+
// Score: 100 * (30M + 40M * 1/2 - 23M) / (1000M * 2 - 23M) = 1
320+
321+
// Node2
322+
// Image: 100 * (30M - 23M) / (1000M * 2 - 23M) = 0
323+
// Score: 0
324+
pod: &v1.Pod{Spec: test3040},
325+
nodes: []*v1.Node{makeImageNode("machine1", node203040), makeImageNode("machine2", node400030)},
326+
expectedList: []framework.NodeScore{{Name: "machine1", Score: 1}, {Name: "machine2", Score: 0}},
327+
name: "pod with multiple small images",
328+
},
184329
}
185330

186331
for _, test := range tests {

0 commit comments

Comments
 (0)