Skip to content

Commit

Permalink
Merge pull request kubernetes#5470 from mwielgus/balancer-policy
Browse files Browse the repository at this point in the history
Balancer placement policies
  • Loading branch information
k8s-ci-robot authored Feb 3, 2023
2 parents 3b9c415 + f0eb1aa commit 60bda22
Show file tree
Hide file tree
Showing 10 changed files with 1,524 additions and 0 deletions.
55 changes: 55 additions & 0 deletions balancer/go.mod
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
)
490 changes: 490 additions & 0 deletions balancer/go.sum

Large diffs are not rendered by default.

59 changes: 59 additions & 0 deletions balancer/pkg/pods/summary.go
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
}
105 changes: 105 additions & 0 deletions balancer/pkg/pods/summary_test.go
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)
})
}
}
55 changes: 55 additions & 0 deletions balancer/pkg/policy/policy.go
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)
}
}
62 changes: 62 additions & 0 deletions balancer/pkg/policy/priority.go
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
}
Loading

0 comments on commit 60bda22

Please sign in to comment.