Add previous scale down candidate sorting
This commit is contained in:
parent
97159df69b
commit
541ce04e4b
|
|
@ -49,6 +49,7 @@ import (
|
||||||
ca_processors "k8s.io/autoscaler/cluster-autoscaler/processors"
|
ca_processors "k8s.io/autoscaler/cluster-autoscaler/processors"
|
||||||
"k8s.io/autoscaler/cluster-autoscaler/processors/nodegroupset"
|
"k8s.io/autoscaler/cluster-autoscaler/processors/nodegroupset"
|
||||||
"k8s.io/autoscaler/cluster-autoscaler/processors/nodeinfosprovider"
|
"k8s.io/autoscaler/cluster-autoscaler/processors/nodeinfosprovider"
|
||||||
|
"k8s.io/autoscaler/cluster-autoscaler/processors/nodes"
|
||||||
"k8s.io/autoscaler/cluster-autoscaler/simulator/clustersnapshot"
|
"k8s.io/autoscaler/cluster-autoscaler/simulator/clustersnapshot"
|
||||||
"k8s.io/autoscaler/cluster-autoscaler/utils/errors"
|
"k8s.io/autoscaler/cluster-autoscaler/utils/errors"
|
||||||
kube_util "k8s.io/autoscaler/cluster-autoscaler/utils/kubernetes"
|
kube_util "k8s.io/autoscaler/cluster-autoscaler/utils/kubernetes"
|
||||||
|
|
@ -403,6 +404,11 @@ func buildAutoscaler(debuggingSnapshotter debuggingsnapshot.DebuggingSnapshotter
|
||||||
podlistprocessor.NewCurrentlyDrainedNodesPodListProcessor(),
|
podlistprocessor.NewCurrentlyDrainedNodesPodListProcessor(),
|
||||||
podlistprocessor.NewFilterOutSchedulablePodListProcessor(opts.PredicateChecker),
|
podlistprocessor.NewFilterOutSchedulablePodListProcessor(opts.PredicateChecker),
|
||||||
)
|
)
|
||||||
|
if autoscalingOptions.ParallelDrain {
|
||||||
|
sdProcessor := nodes.NewScaleDownCandidatesSortingProcessor()
|
||||||
|
opts.Processors.ScaleDownNodeProcessor = sdProcessor
|
||||||
|
opts.Processors.ScaleDownCandidatesNotifier.Register(sdProcessor)
|
||||||
|
}
|
||||||
|
|
||||||
var nodeInfoComparator nodegroupset.NodeInfoComparator
|
var nodeInfoComparator nodegroupset.NodeInfoComparator
|
||||||
if len(autoscalingOptions.BalancingLabels) > 0 {
|
if len(autoscalingOptions.BalancingLabels) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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 nodes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
apiv1 "k8s.io/api/core/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PreviousCandidates is a struct that store scale down candidates from previous loop.
|
||||||
|
type PreviousCandidates struct {
|
||||||
|
candidates map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPreviousCandidates return empty PreviousCandidates struct.
|
||||||
|
func NewPreviousCandidates() *PreviousCandidates {
|
||||||
|
return &PreviousCandidates{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateScaleDownCandidates updates scale down candidates.
|
||||||
|
func (p *PreviousCandidates) UpdateScaleDownCandidates(nodes []*apiv1.Node, now time.Time) {
|
||||||
|
result := make(map[string]bool)
|
||||||
|
for _, node := range nodes {
|
||||||
|
result[node.Name] = true
|
||||||
|
}
|
||||||
|
p.candidates = result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScaleDownEarlierThan return true if node1 is in candidate list and node2 isn't.
|
||||||
|
func (p *PreviousCandidates) ScaleDownEarlierThan(node1, node2 *apiv1.Node) bool {
|
||||||
|
if p.isPreviousCandidate(node1) && !(p.isPreviousCandidate(node2)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PreviousCandidates) isPreviousCandidate(node *apiv1.Node) bool {
|
||||||
|
return p.candidates[node.Name]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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 nodes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
apiv1 "k8s.io/api/core/v1"
|
||||||
|
. "k8s.io/autoscaler/cluster-autoscaler/utils/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestScaleDownEarlierThan(t *testing.T) {
|
||||||
|
candidate1 := BuildTestNode("candidate1", 100, 0)
|
||||||
|
candidate2 := BuildTestNode("candidate2", 100, 0)
|
||||||
|
nonCandidate1 := BuildTestNode("non-candidate1", 100, 0)
|
||||||
|
nonCandidate2 := BuildTestNode("non-candidate2", 100, 0)
|
||||||
|
|
||||||
|
p := NewPreviousCandidates()
|
||||||
|
p.UpdateScaleDownCandidates([]*apiv1.Node{candidate1, candidate2}, time.Now())
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
node1 *apiv1.Node
|
||||||
|
node2 *apiv1.Node
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Compare two candidates",
|
||||||
|
node1: candidate1,
|
||||||
|
node2: candidate2,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Compare two non-candidates",
|
||||||
|
node1: nonCandidate1,
|
||||||
|
node2: nonCandidate2,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Compare candidate and non-candidate",
|
||||||
|
node1: candidate1,
|
||||||
|
node2: nonCandidate2,
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Compare non-candidate and candidate",
|
||||||
|
node1: nonCandidate1,
|
||||||
|
node2: candidate2,
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
got := p.ScaleDownEarlierThan(test.node1, test.node2)
|
||||||
|
assert.Equal(t, got, test.want)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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 nodes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
apiv1 "k8s.io/api/core/v1"
|
||||||
|
|
||||||
|
"k8s.io/autoscaler/cluster-autoscaler/context"
|
||||||
|
"k8s.io/autoscaler/cluster-autoscaler/utils/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ScaleDownCandidatesSortingProcessor is a wrapper for preFilteringProcessor that takes into account previous
|
||||||
|
// scale down candidates. This is necessary for efficient parallel scale down.
|
||||||
|
type ScaleDownCandidatesSortingProcessor struct {
|
||||||
|
preFilter *PreFilteringScaleDownNodeProcessor
|
||||||
|
previousCandidates *PreviousCandidates
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPodDestinationCandidates returns nodes that potentially could act as destinations for pods
|
||||||
|
// that would become unscheduled after a scale down.
|
||||||
|
func (p *ScaleDownCandidatesSortingProcessor) GetPodDestinationCandidates(ctx *context.AutoscalingContext,
|
||||||
|
nodes []*apiv1.Node) ([]*apiv1.Node, errors.AutoscalerError) {
|
||||||
|
return p.preFilter.GetPodDestinationCandidates(ctx, nodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetScaleDownCandidates returns filter nodes and move previous scale down candidates to the beginning of the list.
|
||||||
|
func (p *ScaleDownCandidatesSortingProcessor) GetScaleDownCandidates(ctx *context.AutoscalingContext,
|
||||||
|
nodes []*apiv1.Node) ([]*apiv1.Node, errors.AutoscalerError) {
|
||||||
|
candidates, err := p.preFilter.GetScaleDownCandidates(ctx, nodes)
|
||||||
|
if err != nil {
|
||||||
|
return candidates, err
|
||||||
|
}
|
||||||
|
sort.Slice(candidates, func(i, j int) bool {
|
||||||
|
return p.previousCandidates.ScaleDownEarlierThan(candidates[i], candidates[j])
|
||||||
|
})
|
||||||
|
return candidates, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanUp is called at CA termination.
|
||||||
|
func (p *ScaleDownCandidatesSortingProcessor) CleanUp() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewScaleDownCandidatesSortingProcessor returns a new PreFilteringScaleDownNodeProcessor.
|
||||||
|
func NewScaleDownCandidatesSortingProcessor() *ScaleDownCandidatesSortingProcessor {
|
||||||
|
return &ScaleDownCandidatesSortingProcessor{preFilter: NewPreFilteringScaleDownNodeProcessor(), previousCandidates: NewPreviousCandidates()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateScaleDownCandidates updates scale down candidates.
|
||||||
|
func (p *ScaleDownCandidatesSortingProcessor) UpdateScaleDownCandidates(nodes []*apiv1.Node, now time.Time) {
|
||||||
|
p.previousCandidates.UpdateScaleDownCandidates(nodes, now)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue