Merge pull request #91138 from chendave/imagelocality

Define the thresholds per the size of container images
This commit is contained in:
Kubernetes Prow Robot 2020-05-31 16:17:53 -07:00 committed by GitHub
commit 413bc1a1d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 166 additions and 20 deletions

View File

@ -29,9 +29,9 @@ import (
// The two thresholds are used as bounds for the image score range. They correspond to a reasonable size range for // The two thresholds are used as bounds for the image score range. They correspond to a reasonable size range for
// container images compressed and stored in registries; 90%ile of images on dockerhub drops into this range. // container images compressed and stored in registries; 90%ile of images on dockerhub drops into this range.
const ( const (
mb int64 = 1024 * 1024 mb int64 = 1024 * 1024
minThreshold int64 = 23 * mb minThreshold int64 = 23 * mb
maxThreshold int64 = 1000 * mb maxContainerThreshold int64 = 1000 * mb
) )
// ImageLocality is a score plugin that favors nodes that already have requested pod container's images. // 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,
} }
totalNumNodes := len(nodeInfos) totalNumNodes := len(nodeInfos)
score := calculatePriority(sumImageScores(nodeInfo, pod.Spec.Containers, totalNumNodes)) score := calculatePriority(sumImageScores(nodeInfo, pod.Spec.Containers, totalNumNodes), len(pod.Spec.Containers))
return score, nil return score, nil
} }
@ -79,7 +79,8 @@ func New(_ runtime.Object, h framework.FrameworkHandle) (framework.Plugin, error
// calculatePriority returns the priority of a node. Given the sumScores of requested images on the node, the node's // 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. // priority is obtained by scaling the maximum priority value with a ratio proportional to the sumScores.
func calculatePriority(sumScores int64) int64 { func calculatePriority(sumScores int64, numContainers int) int64 {
maxThreshold := maxContainerThreshold * int64(numContainers)
if sumScores < minThreshold { if sumScores < minThreshold {
sumScores = minThreshold sumScores = minThreshold
} else if sumScores > maxThreshold { } else if sumScores > maxThreshold {

View File

@ -59,7 +59,32 @@ func TestImageLocalityPriority(t *testing.T) {
Image: "gcr.io/10", Image: "gcr.io/10",
}, },
{ {
Image: "gcr.io/2000", Image: "gcr.io/4000",
},
},
}
test300600900 := v1.PodSpec{
Containers: []v1.Container{
{
Image: "gcr.io/300",
},
{
Image: "gcr.io/600",
},
{
Image: "gcr.io/900",
},
},
}
test3040 := v1.PodSpec{
Containers: []v1.Container{
{
Image: "gcr.io/30",
},
{
Image: "gcr.io/40",
}, },
}, },
} }
@ -108,6 +133,92 @@ func TestImageLocalityPriority(t *testing.T) {
}, },
} }
node60040900 := v1.NodeStatus{
Images: []v1.ContainerImage{
{
Names: []string{
"gcr.io/600:latest",
},
SizeBytes: int64(600 * mb),
},
{
Names: []string{
"gcr.io/40:latest",
},
SizeBytes: int64(40 * mb),
},
{
Names: []string{
"gcr.io/900:latest",
},
SizeBytes: int64(900 * mb),
},
},
}
node300600900 := v1.NodeStatus{
Images: []v1.ContainerImage{
{
Names: []string{
"gcr.io/300:latest",
},
SizeBytes: int64(300 * mb),
},
{
Names: []string{
"gcr.io/600:latest",
},
SizeBytes: int64(600 * mb),
},
{
Names: []string{
"gcr.io/900:latest",
},
SizeBytes: int64(900 * mb),
},
},
}
node400030 := v1.NodeStatus{
Images: []v1.ContainerImage{
{
Names: []string{
"gcr.io/4000:latest",
},
SizeBytes: int64(4000 * mb),
},
{
Names: []string{
"gcr.io/30:latest",
},
SizeBytes: int64(30 * mb),
},
},
}
node203040 := v1.NodeStatus{
Images: []v1.ContainerImage{
{
Names: []string{
"gcr.io/20:latest",
},
SizeBytes: int64(20 * mb),
},
{
Names: []string{
"gcr.io/30:latest",
},
SizeBytes: int64(30 * mb),
},
{
Names: []string{
"gcr.io/40:latest",
},
SizeBytes: int64(40 * mb),
},
},
}
nodeWithNoImages := v1.NodeStatus{} nodeWithNoImages := v1.NodeStatus{}
tests := []struct { tests := []struct {
@ -126,10 +237,10 @@ func TestImageLocalityPriority(t *testing.T) {
// Node2 // Node2
// Image: gcr.io/250:latest 250MB // Image: gcr.io/250:latest 250MB
// Score: 100 * (250M/2 - 23M)/(1000M - 23M) = 100 // Score: 100 * (250M/2 - 23M)/(1000M * 2 - 23M) = 5
pod: &v1.Pod{Spec: test40250}, pod: &v1.Pod{Spec: test40250},
nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010)}, nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010)},
expectedList: []framework.NodeScore{{Name: "machine1", Score: 0}, {Name: "machine2", Score: 10}}, expectedList: []framework.NodeScore{{Name: "machine1", Score: 0}, {Name: "machine2", Score: 5}},
name: "two images spread on two nodes, prefer the larger image one", name: "two images spread on two nodes, prefer the larger image one",
}, },
{ {
@ -137,50 +248,84 @@ func TestImageLocalityPriority(t *testing.T) {
// Node1 // Node1
// Image: gcr.io/40:latest 40MB, gcr.io/300:latest 300MB // Image: gcr.io/40:latest 40MB, gcr.io/300:latest 300MB
// Score: 100 * ((40M + 300M)/2 - 23M)/(1000M - 23M) = 15 // Score: 100 * ((40M + 300M)/2 - 23M)/(1000M * 2 - 23M) = 7
// Node2 // Node2
// Image: not present // Image: not present
// Score: 0 // Score: 0
pod: &v1.Pod{Spec: test40300}, pod: &v1.Pod{Spec: test40300},
nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010)}, nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010)},
expectedList: []framework.NodeScore{{Name: "machine1", Score: 15}, {Name: "machine2", Score: 0}}, expectedList: []framework.NodeScore{{Name: "machine1", Score: 7}, {Name: "machine2", Score: 0}},
name: "two images on one node, prefer this node", name: "two images on one node, prefer this node",
}, },
{ {
// Pod: gcr.io/2000 gcr.io/10 // Pod: gcr.io/4000 gcr.io/10
// Node1 // Node1
// Image: gcr.io/2000:latest 2000MB // Image: gcr.io/4000:latest 2000MB
// Score: 100 (2000M/2 >= 1000M, max-threshold) // Score: 100 (4000 * 1/2 >= 1000M * 2, max-threshold)
// Node2 // Node2
// Image: gcr.io/10:latest 10MB // Image: gcr.io/10:latest 10MB
// Score: 0 (10M/2 < 23M, min-threshold) // Score: 0 (10M/2 < 23M, min-threshold)
pod: &v1.Pod{Spec: testMinMax}, pod: &v1.Pod{Spec: testMinMax},
nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010)}, nodes: []*v1.Node{makeImageNode("machine1", node400030), makeImageNode("machine2", node25010)},
expectedList: []framework.NodeScore{{Name: "machine1", Score: framework.MaxNodeScore}, {Name: "machine2", Score: 0}}, expectedList: []framework.NodeScore{{Name: "machine1", Score: framework.MaxNodeScore}, {Name: "machine2", Score: 0}},
name: "if exceed limit, use limit", name: "if exceed limit, use limit",
}, },
{ {
// Pod: gcr.io/2000 gcr.io/10 // Pod: gcr.io/4000 gcr.io/10
// Node1 // Node1
// Image: gcr.io/2000:latest 2000MB // Image: gcr.io/4000:latest 4000MB
// Score: 100 * (2000M/3 - 23M)/(1000M - 23M) = 65 // Score: 100 * (4000M/3 - 23M)/(1000M * 2 - 23M) = 66
// Node2 // Node2
// Image: gcr.io/10:latest 10MB // Image: gcr.io/10:latest 10MB
// Score: 0 (10M/2 < 23M, min-threshold) // Score: 0 (10M*1/3 < 23M, min-threshold)
// Node3 // Node3
// Image: // Image:
// Score: 0 // Score: 0
pod: &v1.Pod{Spec: testMinMax}, pod: &v1.Pod{Spec: testMinMax},
nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010), makeImageNode("machine3", nodeWithNoImages)}, nodes: []*v1.Node{makeImageNode("machine1", node400030), makeImageNode("machine2", node25010), makeImageNode("machine3", nodeWithNoImages)},
expectedList: []framework.NodeScore{{Name: "machine1", Score: 65}, {Name: "machine2", Score: 0}, {Name: "machine3", Score: 0}}, expectedList: []framework.NodeScore{{Name: "machine1", Score: 66}, {Name: "machine2", Score: 0}, {Name: "machine3", Score: 0}},
name: "if exceed limit, use limit (with node which has no images present)", name: "if exceed limit, use limit (with node which has no images present)",
}, },
{
// Pod: gcr.io/300 gcr.io/600 gcr.io/900
// Node1
// Image: gcr.io/600:latest 600MB, gcr.io/900:latest 900MB
// Score: 100 * (600M * 2/3 + 900M * 2/3 - 23M) / (1000M * 3 - 23M) = 32
// Node2
// Image: gcr.io/300:latest 300MB, gcr.io/600:latest 600MB, gcr.io/900:latest 900MB
// Score: 100 * (300M * 1/3 + 600M * 2/3 + 900M * 2/3 - 23M) / (1000M *3 - 23M) = 36
// Node3
// Image:
// Score: 0
pod: &v1.Pod{Spec: test300600900},
nodes: []*v1.Node{makeImageNode("machine1", node60040900), makeImageNode("machine2", node300600900), makeImageNode("machine3", nodeWithNoImages)},
expectedList: []framework.NodeScore{{Name: "machine1", Score: 32}, {Name: "machine2", Score: 36}, {Name: "machine3", Score: 0}},
name: "pod with multiple large images, machine2 is preferred",
},
{
// Pod: gcr.io/30 gcr.io/40
// Node1
// Image: gcr.io/20:latest 20MB, gcr.io/30:latest 30MB gcr.io/40:latest 40MB
// Score: 100 * (30M + 40M * 1/2 - 23M) / (1000M * 2 - 23M) = 1
// Node2
// Image: 100 * (30M - 23M) / (1000M * 2 - 23M) = 0
// Score: 0
pod: &v1.Pod{Spec: test3040},
nodes: []*v1.Node{makeImageNode("machine1", node203040), makeImageNode("machine2", node400030)},
expectedList: []framework.NodeScore{{Name: "machine1", Score: 1}, {Name: "machine2", Score: 0}},
name: "pod with multiple small images",
},
} }
for _, test := range tests { for _, test := range tests {