Define the thresholds per the size of container images

Given the assumption that 90% of images on dockerhub drops into this range (23~1000)MB,
this assumption is based on the container images instead of the pod.

pod might hold multiple container images, it's better to multiply the assumption by the size
of container images.

Signed-off-by: Dave Chen <dave.chen@arm.com>
This commit is contained in:
Dave Chen 2020-05-15 18:30:57 +08:00
parent 84915d1623
commit 42fbb1d72f
2 changed files with 97 additions and 30 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
// container images compressed and stored in registries; 90%ile of images on dockerhub drops into this range.
const (
mb int64 = 1024 * 1024
minThreshold int64 = 23 * mb
maxThreshold int64 = 1000 * mb
mb int64 = 1024 * 1024
minThreshold int64 = 23 * mb
maxContainerThreshold int64 = 1000 * mb
)
// 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)
score := calculatePriority(sumImageScores(nodeInfo, pod.Spec.Containers, totalNumNodes))
score := calculatePriority(sumImageScores(nodeInfo, pod.Spec.Containers, totalNumNodes), len(pod.Spec.Containers))
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
// 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 {
sumScores = minThreshold
} else if sumScores > maxThreshold {

View File

@ -59,7 +59,7 @@ func TestImageLocalityPriority(t *testing.T) {
Image: "gcr.io/10",
},
{
Image: "gcr.io/2000",
Image: "gcr.io/4000",
},
},
}
@ -78,6 +78,17 @@ func TestImageLocalityPriority(t *testing.T) {
},
}
test3040 := v1.PodSpec{
Containers: []v1.Container{
{
Image: "gcr.io/30",
},
{
Image: "gcr.io/40",
},
},
}
node403002000 := v1.NodeStatus{
Images: []v1.ContainerImage{
{
@ -126,19 +137,19 @@ func TestImageLocalityPriority(t *testing.T) {
Images: []v1.ContainerImage{
{
Names: []string{
"gcr.io/600:" + parsers.DefaultImageTag,
"gcr.io/600:latest",
},
SizeBytes: int64(600 * mb),
},
{
Names: []string{
"gcr.io/40:" + parsers.DefaultImageTag,
"gcr.io/40:latest",
},
SizeBytes: int64(40 * mb),
},
{
Names: []string{
"gcr.io/900:" + parsers.DefaultImageTag,
"gcr.io/900:latest",
},
SizeBytes: int64(900 * mb),
},
@ -149,25 +160,65 @@ func TestImageLocalityPriority(t *testing.T) {
Images: []v1.ContainerImage{
{
Names: []string{
"gcr.io/300:" + parsers.DefaultImageTag,
"gcr.io/300:latest",
},
SizeBytes: int64(300 * mb),
},
{
Names: []string{
"gcr.io/600:" + parsers.DefaultImageTag,
"gcr.io/600:latest",
},
SizeBytes: int64(600 * mb),
},
{
Names: []string{
"gcr.io/900:" + parsers.DefaultImageTag,
"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{}
tests := []struct {
@ -186,10 +237,10 @@ func TestImageLocalityPriority(t *testing.T) {
// Node2
// 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},
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",
},
{
@ -197,48 +248,48 @@ func TestImageLocalityPriority(t *testing.T) {
// Node1
// 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
// Image: not present
// Score: 0
pod: &v1.Pod{Spec: test40300},
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",
},
{
// Pod: gcr.io/2000 gcr.io/10
// Pod: gcr.io/4000 gcr.io/10
// Node1
// Image: gcr.io/2000:latest 2000MB
// Score: 100 (2000M/2 >= 1000M, max-threshold)
// Image: gcr.io/4000:latest 2000MB
// Score: 100 (4000 * 1/2 >= 1000M * 2, max-threshold)
// Node2
// Image: gcr.io/10:latest 10MB
// Score: 0 (10M/2 < 23M, min-threshold)
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}},
name: "if exceed limit, use limit",
},
{
// Pod: gcr.io/2000 gcr.io/10
// Pod: gcr.io/4000 gcr.io/10
// Node1
// Image: gcr.io/2000:latest 2000MB
// Score: 100 * (2000M/3 - 23M)/(1000M - 23M) = 65
// Image: gcr.io/4000:latest 4000MB
// Score: 100 * (4000M/3 - 23M)/(1000M * 2 - 23M) = 66
// Node2
// Image: gcr.io/10:latest 10MB
// Score: 0 (10M/2 < 23M, min-threshold)
// Score: 0 (10M*1/3 < 23M, min-threshold)
// Node3
// Image:
// Score: 0
pod: &v1.Pod{Spec: testMinMax},
nodes: []*v1.Node{makeImageNode("machine1", node403002000), makeImageNode("machine2", node25010), makeImageNode("machine3", nodeWithNoImages)},
expectedList: []framework.NodeScore{{Name: "machine1", Score: 65}, {Name: "machine2", Score: 0}, {Name: "machine3", Score: 0}},
nodes: []*v1.Node{makeImageNode("machine1", node400030), makeImageNode("machine2", node25010), makeImageNode("machine3", nodeWithNoImages)},
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)",
},
{
@ -246,19 +297,34 @@ func TestImageLocalityPriority(t *testing.T) {
// Node1
// Image: gcr.io/600:latest 600MB, gcr.io/900:latest 900MB
// Score: 100 (600M * 2/3 + 900M * 2/3 = 1000M >= 1000M, max-threshold)
// 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) >= 1000M, max-threshold)
// 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: 100}, {Name: "machine2", Score: 100}, {Name: "machine3", Score: 0}},
name: "max threshold haven't considered the size of containers to be run for the pod",
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",
},
}