forked from kubernetes/autoscaler
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request kubernetes#5470 from mwielgus/balancer-policy
Balancer placement policies
- Loading branch information
Showing
10 changed files
with
1,524 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
module k8s.io/autoscaling/balancer | ||
|
||
go 1.19 | ||
|
||
require ( | ||
github.com/stretchr/testify v1.8.0 | ||
k8s.io/api v0.25.2 | ||
k8s.io/apimachinery v0.25.2 | ||
k8s.io/client-go v0.25.2 | ||
k8s.io/klog/v2 v2.70.1 | ||
) | ||
|
||
require ( | ||
github.com/PuerkitoBio/purell v1.1.1 // indirect | ||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/emicklei/go-restful/v3 v3.8.0 // indirect | ||
github.com/evanphx/json-patch v4.12.0+incompatible // indirect | ||
github.com/go-logr/logr v1.2.3 // indirect | ||
github.com/go-openapi/jsonpointer v0.19.5 // indirect | ||
github.com/go-openapi/jsonreference v0.19.5 // indirect | ||
github.com/go-openapi/swag v0.19.14 // indirect | ||
github.com/gogo/protobuf v1.3.2 // indirect | ||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | ||
github.com/golang/protobuf v1.5.2 // indirect | ||
github.com/google/gnostic v0.5.7-v3refs // indirect | ||
github.com/google/go-cmp v0.5.8 // indirect | ||
github.com/google/gofuzz v1.2.0 // indirect | ||
github.com/imdario/mergo v0.3.6 // indirect | ||
github.com/josharian/intern v1.0.0 // indirect | ||
github.com/json-iterator/go v1.1.12 // indirect | ||
github.com/mailru/easyjson v0.7.6 // indirect | ||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||
github.com/modern-go/reflect2 v1.0.2 // indirect | ||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | ||
github.com/pkg/errors v0.9.1 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
github.com/spf13/pflag v1.0.5 // indirect | ||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect | ||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect | ||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect | ||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect | ||
golang.org/x/text v0.3.7 // indirect | ||
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect | ||
google.golang.org/appengine v1.6.7 // indirect | ||
google.golang.org/protobuf v1.28.0 // indirect | ||
gopkg.in/inf.v0 v0.9.1 // indirect | ||
gopkg.in/yaml.v2 v2.4.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect | ||
k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 // indirect | ||
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect | ||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect | ||
sigs.k8s.io/yaml v1.3.0 // indirect | ||
) |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* | ||
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 pods | ||
|
||
import ( | ||
"time" | ||
|
||
v1 "k8s.io/api/core/v1" | ||
) | ||
|
||
// Summary contains information about the total observed number of pods within a | ||
// group, number of running, as well as the count of those who failed to | ||
// start within the deadline. | ||
type Summary struct { | ||
// Total is the total observed number of pods that are either running or | ||
// about to be started. | ||
Total int32 | ||
// Running is the number of running pods. | ||
Running int32 | ||
// NotStartedWithinDeadline is the number of pods that not only has not | ||
// fully stared (not scheduled or not fully started, in phase PodPending) | ||
// but also has been in the not started phase for a while. | ||
NotStartedWithinDeadline int32 | ||
} | ||
|
||
// CalculateSummary calculates the Summary structure for the given set of pod | ||
// and startup constraint. | ||
func CalculateSummary(podList []*v1.Pod, now time.Time, startupTimeout time.Duration) Summary { | ||
result := Summary{} | ||
for _, p := range podList { | ||
switch p.Status.Phase { | ||
case v1.PodRunning: | ||
result.Total++ | ||
result.Running++ | ||
break | ||
case v1.PodPending: | ||
result.Total++ | ||
if p.CreationTimestamp.Add(startupTimeout).Before(now) { | ||
result.NotStartedWithinDeadline++ | ||
} | ||
break | ||
} | ||
} | ||
return result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
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 pods | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
v1 "k8s.io/api/core/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
) | ||
|
||
func newPod(name string, phase v1.PodPhase, createTime time.Time) *v1.Pod { | ||
return &v1.Pod{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: name, | ||
Namespace: "default", | ||
CreationTimestamp: metav1.NewTime(createTime), | ||
}, | ||
Status: v1.PodStatus{ | ||
Phase: phase, | ||
}, | ||
} | ||
} | ||
|
||
func TestCalculateSummary(t *testing.T) { | ||
now := time.Now() | ||
timeout := time.Minute * 5 | ||
|
||
tests := []struct { | ||
name string | ||
pods []*v1.Pod | ||
expected Summary | ||
}{ | ||
{ | ||
name: "basic running/pending", | ||
pods: []*v1.Pod{ | ||
newPod("a", v1.PodRunning, now.Add(-time.Hour)), | ||
newPod("a2", v1.PodRunning, now.Add(-time.Hour)), | ||
newPod("b", v1.PodPending, now.Add(-time.Minute)), | ||
}, | ||
expected: Summary{ | ||
Total: 3, | ||
Running: 2, | ||
}, | ||
}, | ||
{ | ||
name: "skip completed", | ||
pods: []*v1.Pod{ | ||
newPod("a", v1.PodSucceeded, now.Add(-time.Hour)), | ||
newPod("c", v1.PodFailed, now.Add(-time.Hour)), | ||
}, | ||
expected: Summary{ | ||
Total: 0, | ||
}, | ||
}, | ||
{ | ||
name: "deadline", | ||
pods: []*v1.Pod{ | ||
newPod("a", v1.PodPending, now.Add(-time.Hour)), | ||
}, | ||
expected: Summary{ | ||
Total: 1, | ||
NotStartedWithinDeadline: 1, | ||
}, | ||
}, | ||
{ | ||
name: "mix", | ||
pods: []*v1.Pod{ | ||
newPod("problem", v1.PodPending, now.Add(-time.Hour)), | ||
newPod("b", v1.PodRunning, now.Add(-time.Hour)), | ||
newPod("b2", v1.PodRunning, now.Add(-time.Hour)), | ||
newPod("c", v1.PodFailed, now.Add(-time.Hour)), | ||
}, | ||
expected: Summary{ | ||
Total: 3, | ||
Running: 2, | ||
NotStartedWithinDeadline: 1, | ||
}, | ||
}, | ||
} | ||
|
||
for i, tc := range tests { | ||
t.Run(fmt.Sprintf("%d: %s", i, tc.name), func(t *testing.T) { | ||
result := CalculateSummary(tc.pods, now, timeout) | ||
assert.Equal(t, tc.expected, result) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/* | ||
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 policy | ||
|
||
import ( | ||
"fmt" | ||
"k8s.io/autoscaling/balancer/pkg/apis/balancer.x-k8s.io/v1alpha1" | ||
"k8s.io/autoscaling/balancer/pkg/pods" | ||
) | ||
|
||
// GetPlacement calculates the placement for the given balancer and pod summary | ||
// information for individual balancer targets. | ||
func GetPlacement(balancer *v1alpha1.Balancer, summaries map[string]pods.Summary) (ReplicaPlacement, PlacementProblems, error) { | ||
targetMap := buildTargetMap(balancer.Spec.Targets) | ||
switch balancer.Spec.Policy.PolicyName { | ||
case v1alpha1.PriorityPolicyName: | ||
if balancer.Spec.Policy.Priorities == nil { | ||
return nil, PlacementProblems{}, fmt.Errorf("incomplete policy definition: missing priorities") | ||
} | ||
if balancer.Spec.Policy.Priorities.TargetOrder == nil { | ||
return nil, PlacementProblems{}, fmt.Errorf("incomplete policy definition: missing targetOrder") | ||
} | ||
infos := buildTargetInfoMapForPriority(targetMap, summaries) | ||
placement, problems := distributeByPriority(balancer.Spec.Replicas, balancer.Spec.Policy.Priorities.TargetOrder, infos) | ||
return placement, problems, nil | ||
|
||
case v1alpha1.ProportionalPolicyName: | ||
if balancer.Spec.Policy.Proportions == nil { | ||
return nil, PlacementProblems{}, fmt.Errorf("incomplete policy definition: missing proportions") | ||
} | ||
if balancer.Spec.Policy.Proportions.TargetProportions == nil { | ||
return nil, PlacementProblems{}, fmt.Errorf("incomplete policy definition: missing targetProportions") | ||
} | ||
infos := buildTargetInfoMapForProportional(targetMap, summaries, balancer.Spec.Policy.Proportions.TargetProportions) | ||
placement, problems := distributeByProportions(balancer.Spec.Replicas, infos) | ||
return placement, problems, nil | ||
|
||
default: | ||
return nil, PlacementProblems{}, fmt.Errorf("policy not supported: %v", balancer.Spec.Policy.PolicyName) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* | ||
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 policy | ||
|
||
// Main algorithm of the priority policy. The function returns the | ||
// desired replica placement and information about problems that | ||
// possibly happened during placement. | ||
func distributeByPriority(replicas int32, | ||
priorities []string, infos map[string]*targetInfo) (ReplicaPlacement, PlacementProblems) { | ||
|
||
placement := make(ReplicaPlacement) | ||
problems := PlacementProblems{} | ||
|
||
// Place target minimums | ||
for k, info := range infos { | ||
placement[k] = info.min | ||
replicas -= placement[k] | ||
} | ||
// continue computations as there still may be fallbacks. | ||
if replicas < 0 { | ||
problems.MissingReplicas = -replicas | ||
replicas = 0 | ||
} | ||
|
||
for _, key := range priorities { | ||
info := infos[key] | ||
free := info.max - placement[key] | ||
if replicas < free { | ||
placement[key] += replicas | ||
replicas = 0 | ||
} else { | ||
placement[key] += free | ||
replicas -= free | ||
} | ||
// calculate how many may need to fall back to the other target = all new plus | ||
// and those that are past deadline. | ||
if infos[key].summary.NotStartedWithinDeadline > 0 { | ||
fallback := info.summary.NotStartedWithinDeadline + placement[key] - info.summary.Total | ||
if fallback > 0 { | ||
replicas += fallback | ||
} | ||
} | ||
} | ||
if replicas > 0 { | ||
problems.OverflowReplicas = replicas | ||
} | ||
return placement, problems | ||
} |
Oops, something went wrong.