ClusterAutoscaler: first fit decreasing estimate algorithm
This commit is contained in:
parent
771c92e989
commit
0561d0ea84
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package estimator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"k8s.io/contrib/cluster-autoscaler/simulator"
|
||||||
|
kube_api "k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/api/resource"
|
||||||
|
"k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// podInfo contains Pod and score that corresponds to how important it is to handle the pod first.
|
||||||
|
type podInfo struct {
|
||||||
|
score float64
|
||||||
|
pod *kube_api.Pod
|
||||||
|
}
|
||||||
|
|
||||||
|
type byScoreDesc []*podInfo
|
||||||
|
|
||||||
|
func (a byScoreDesc) Len() int { return len(a) }
|
||||||
|
func (a byScoreDesc) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a byScoreDesc) Less(i, j int) bool { return a[i].score > a[j].score }
|
||||||
|
|
||||||
|
// BinpackingNodeEstimator estimates the number of needed nodes to handle the given amount of pods.
|
||||||
|
type BinpackingNodeEstimator struct {
|
||||||
|
predicateChecker *simulator.PredicateChecker
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBinpackingNodeEstimator builds a new BinpackingNodeEstimator.
|
||||||
|
func NewBinpackingNodeEstimator(predicateChecker *simulator.PredicateChecker) *BinpackingNodeEstimator {
|
||||||
|
return &BinpackingNodeEstimator{
|
||||||
|
predicateChecker: predicateChecker,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estimate implements First Fit Decreasing bin-packing approximation algorithm.
|
||||||
|
// See https://en.wikipedia.org/wiki/Bin_packing_problem for more details.
|
||||||
|
// While it is a multi-dimensional bin packing (cpu, mem, ports) in most cases the main dimension
|
||||||
|
// will be cpu thus the estimated overprovisioning of 11/9 * optimal + 6/9 should be
|
||||||
|
// still be maintained.
|
||||||
|
// It is assumed that all pods from the given list can fit to nodeTemplate.
|
||||||
|
// Returns the number of nodes needed to accommodate all pods from the list.
|
||||||
|
func (estimator *BinpackingNodeEstimator) Estimate(pods []*kube_api.Pod, nodeTemplate *schedulercache.NodeInfo) int {
|
||||||
|
|
||||||
|
podInfos := calculatePodScore(pods, nodeTemplate)
|
||||||
|
sort.Sort(byScoreDesc(podInfos))
|
||||||
|
|
||||||
|
// nodeWithPod function returns NodeInfo, which is a copy of nodeInfo argument with an additional pod scheduled on it.
|
||||||
|
nodeWithPod := func(nodeInfo *schedulercache.NodeInfo, pod *kube_api.Pod) *schedulercache.NodeInfo {
|
||||||
|
podsOnNode := nodeInfo.Pods()
|
||||||
|
podsOnNode = append(podsOnNode, pod)
|
||||||
|
newNodeInfo := schedulercache.NewNodeInfo(podsOnNode...)
|
||||||
|
newNodeInfo.SetNode(nodeInfo.Node())
|
||||||
|
return newNodeInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
newNodes := make([]*schedulercache.NodeInfo, 0)
|
||||||
|
for _, podInfo := range podInfos {
|
||||||
|
found := false
|
||||||
|
for i, nodeInfo := range newNodes {
|
||||||
|
if err := estimator.predicateChecker.CheckPredicates(podInfo.pod, nodeInfo); err == nil {
|
||||||
|
found = true
|
||||||
|
newNodes[i] = nodeWithPod(nodeInfo, podInfo.pod)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
newNodes = append(newNodes, nodeWithPod(nodeTemplate, podInfo.pod))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len(newNodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculates score for all pods and returns podInfo structure.
|
||||||
|
// Score is defined as cpu_sum/node_capacity + mem_sum/node_capacity.
|
||||||
|
// Pods that have bigger requirements should be processed first, thus have higher scores.
|
||||||
|
func calculatePodScore(pods []*kube_api.Pod, nodeTemplate *schedulercache.NodeInfo) []*podInfo {
|
||||||
|
podInfos := make([]*podInfo, 0, len(pods))
|
||||||
|
|
||||||
|
for _, pod := range pods {
|
||||||
|
cpuSum := resource.Quantity{}
|
||||||
|
memorySum := resource.Quantity{}
|
||||||
|
|
||||||
|
for _, container := range pod.Spec.Containers {
|
||||||
|
if request, ok := container.Resources.Requests[kube_api.ResourceCPU]; ok {
|
||||||
|
cpuSum.Add(request)
|
||||||
|
}
|
||||||
|
if request, ok := container.Resources.Requests[kube_api.ResourceMemory]; ok {
|
||||||
|
memorySum.Add(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
score := float64(0)
|
||||||
|
if cpuAllocatable, ok := nodeTemplate.Node().Status.Allocatable[kube_api.ResourceCPU]; ok && cpuAllocatable.MilliValue() > 0 {
|
||||||
|
score += float64(cpuSum.MilliValue()) / float64(cpuAllocatable.MilliValue())
|
||||||
|
}
|
||||||
|
if memAllocatable, ok := nodeTemplate.Node().Status.Allocatable[kube_api.ResourceMemory]; ok && memAllocatable.Value() > 0 {
|
||||||
|
score += float64(memorySum.Value()) / float64(memAllocatable.Value())
|
||||||
|
}
|
||||||
|
|
||||||
|
podInfos = append(podInfos, &podInfo{
|
||||||
|
score: score,
|
||||||
|
pod: pod,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return podInfos
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package estimator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/contrib/cluster-autoscaler/simulator"
|
||||||
|
kube_api "k8s.io/kubernetes/pkg/api"
|
||||||
|
"k8s.io/kubernetes/pkg/api/resource"
|
||||||
|
"k8s.io/kubernetes/plugin/pkg/scheduler/schedulercache"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBinpackingEstimate(t *testing.T) {
|
||||||
|
estimator := NewBinpackingNodeEstimator(simulator.NewTestPredicateChecker())
|
||||||
|
|
||||||
|
cpuPerPod := int64(350)
|
||||||
|
memoryPerPod := int64(1000 * 1024 * 1024)
|
||||||
|
pod := &kube_api.Pod{
|
||||||
|
Spec: kube_api.PodSpec{
|
||||||
|
Containers: []kube_api.Container{
|
||||||
|
{
|
||||||
|
Resources: kube_api.ResourceRequirements{
|
||||||
|
Requests: kube_api.ResourceList{
|
||||||
|
kube_api.ResourceCPU: *resource.NewMilliQuantity(cpuPerPod, resource.DecimalSI),
|
||||||
|
kube_api.ResourceMemory: *resource.NewQuantity(memoryPerPod, resource.DecimalSI),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pods := make([]*kube_api.Pod, 0)
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
pods = append(pods, pod)
|
||||||
|
}
|
||||||
|
node := &kube_api.Node{
|
||||||
|
Status: kube_api.NodeStatus{
|
||||||
|
Capacity: kube_api.ResourceList{
|
||||||
|
kube_api.ResourceCPU: *resource.NewMilliQuantity(cpuPerPod*3-50, resource.DecimalSI),
|
||||||
|
kube_api.ResourceMemory: *resource.NewQuantity(2*memoryPerPod, resource.DecimalSI),
|
||||||
|
kube_api.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
node.Status.Allocatable = node.Status.Capacity
|
||||||
|
|
||||||
|
nodeInfo := schedulercache.NewNodeInfo()
|
||||||
|
nodeInfo.SetNode(node)
|
||||||
|
estimate := estimator.Estimate(pods, nodeInfo)
|
||||||
|
assert.Equal(t, 5, estimate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBinpackingEstimateWithPorts(t *testing.T) {
|
||||||
|
estimator := NewBinpackingNodeEstimator(simulator.NewTestPredicateChecker())
|
||||||
|
|
||||||
|
cpuPerPod := int64(200)
|
||||||
|
memoryPerPod := int64(1000 * 1024 * 1024)
|
||||||
|
pod := &kube_api.Pod{
|
||||||
|
Spec: kube_api.PodSpec{
|
||||||
|
Containers: []kube_api.Container{
|
||||||
|
{
|
||||||
|
Resources: kube_api.ResourceRequirements{
|
||||||
|
Requests: kube_api.ResourceList{
|
||||||
|
kube_api.ResourceCPU: *resource.NewMilliQuantity(cpuPerPod, resource.DecimalSI),
|
||||||
|
kube_api.ResourceMemory: *resource.NewQuantity(memoryPerPod, resource.DecimalSI),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ports: []kube_api.ContainerPort{
|
||||||
|
{
|
||||||
|
HostPort: 5555,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pods := make([]*kube_api.Pod, 0)
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
pods = append(pods, pod)
|
||||||
|
}
|
||||||
|
node := &kube_api.Node{
|
||||||
|
Status: kube_api.NodeStatus{
|
||||||
|
Capacity: kube_api.ResourceList{
|
||||||
|
kube_api.ResourceCPU: *resource.NewMilliQuantity(5*cpuPerPod, resource.DecimalSI),
|
||||||
|
kube_api.ResourceMemory: *resource.NewQuantity(5*memoryPerPod, resource.DecimalSI),
|
||||||
|
kube_api.ResourcePods: *resource.NewQuantity(10, resource.DecimalSI),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
node.Status.Allocatable = node.Status.Capacity
|
||||||
|
|
||||||
|
nodeInfo := schedulercache.NewNodeInfo()
|
||||||
|
nodeInfo.SetNode(node)
|
||||||
|
estimate := estimator.Estimate(pods, nodeInfo)
|
||||||
|
assert.Equal(t, 8, estimate)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue