Fix scaling adjustment edge case (#782)

* Update GH actions build

Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>

* Fix scaling adjustment edge case

Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>

---------

Signed-off-by: Mikkel Oscar Lyderik Larsen <mikkel.larsen@zalando.de>
This commit is contained in:
Mikkel Oscar Lyderik Larsen
2025-01-29 10:58:33 +01:00
committed by GitHub
parent 4204daa44f
commit d416441688
3 changed files with 21 additions and 8 deletions

View File

@ -10,10 +10,10 @@ jobs:
tests: tests:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions/setup-go@v2 - uses: actions/setup-go@v5
with: with:
go-version: '^1.19' go-version: '^1.23'
- run: go version - run: go version
- run: go install github.com/mattn/goveralls@latest - run: go install github.com/mattn/goveralls@latest
- run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

View File

@ -266,13 +266,13 @@ func (c *Controller) adjustHPAScaling(ctx context.Context, hpa *autoscalingv2.Ho
return nil return nil
} }
highestExpected, highestObject := highestActiveSchedule(hpa, activeSchedules) highestExpected, usageRatio, highestObject := highestActiveSchedule(hpa, activeSchedules, current)
highestExpected = int64(math.Min(float64(highestExpected), float64(hpa.Spec.MaxReplicas))) highestExpected = int64(math.Min(float64(highestExpected), float64(hpa.Spec.MaxReplicas)))
var change float64 var change float64
if highestExpected > current { if highestExpected > current {
change = (float64(highestExpected) - float64(current)) / float64(current) change = math.Abs(1.0 - usageRatio)
} }
if change > 0 && change <= c.hpaTolerance { if change > 0 && change <= c.hpaTolerance {
@ -304,8 +304,9 @@ func (c *Controller) adjustHPAScaling(ctx context.Context, hpa *autoscalingv2.Ho
// highestActiveSchedule returns the highest active schedule value and // highestActiveSchedule returns the highest active schedule value and
// corresponding object. // corresponding object.
func highestActiveSchedule(hpa *autoscalingv2.HorizontalPodAutoscaler, activeSchedules map[string]int64) (int64, autoscalingv2.CrossVersionObjectReference) { func highestActiveSchedule(hpa *autoscalingv2.HorizontalPodAutoscaler, activeSchedules map[string]int64, currentReplicas int64) (int64, float64, autoscalingv2.CrossVersionObjectReference) {
var highestExpected int64 var highestExpected int64
var usageRatio float64
var highestObject autoscalingv2.CrossVersionObjectReference var highestObject autoscalingv2.CrossVersionObjectReference
for _, metric := range hpa.Spec.Metrics { for _, metric := range hpa.Spec.Metrics {
if metric.Type != autoscalingv2.ObjectMetricSourceType { if metric.Type != autoscalingv2.ObjectMetricSourceType {
@ -340,11 +341,12 @@ func highestActiveSchedule(hpa *autoscalingv2.HorizontalPodAutoscaler, activeSch
expected := int64(math.Ceil(float64(value) / float64(target))) expected := int64(math.Ceil(float64(value) / float64(target)))
if expected > highestExpected { if expected > highestExpected {
highestExpected = expected highestExpected = expected
usageRatio = float64(value) / (float64(target) * float64(currentReplicas))
highestObject = metric.Object.DescribedObject highestObject = metric.Object.DescribedObject
} }
} }
return highestExpected, highestObject return highestExpected, usageRatio, highestObject
} }
func (c *Controller) adjustScaling(ctx context.Context, schedules []v1.ScalingScheduler) error { func (c *Controller) adjustScaling(ctx context.Context, schedules []v1.ScalingScheduler) error {

View File

@ -337,24 +337,35 @@ func TestAdjustScaling(t *testing.T) {
currentReplicas int32 currentReplicas int32
desiredReplicas int32 desiredReplicas int32
targetValue int64 targetValue int64
scheduleTarget int64
}{ }{
{
msg: "current less than 10%% below desired (target 10000)",
currentReplicas: 28, // 7.1% increase to desired
desiredReplicas: 31,
targetValue: 333, // 10000/333 ~= 31
scheduleTarget: 10000,
},
{ {
msg: "current less than 10%% below desired", msg: "current less than 10%% below desired",
currentReplicas: 95, // 5.3% increase to desired currentReplicas: 95, // 5.3% increase to desired
desiredReplicas: 100, desiredReplicas: 100,
targetValue: 10, // 1000/10 = 100 targetValue: 10, // 1000/10 = 100
scheduleTarget: 1000,
}, },
{ {
msg: "current more than 10%% below desired, no adjustment", msg: "current more than 10%% below desired, no adjustment",
currentReplicas: 90, // 11% increase to desired currentReplicas: 90, // 11% increase to desired
desiredReplicas: 90, desiredReplicas: 90,
targetValue: 10, // 1000/10 = 100 targetValue: 10, // 1000/10 = 100
scheduleTarget: 1000,
}, },
{ {
msg: "invalid HPA should not do any adjustment", msg: "invalid HPA should not do any adjustment",
currentReplicas: 95, currentReplicas: 95,
desiredReplicas: 95, desiredReplicas: 95,
targetValue: 0, // this is treated as invalid in the test, thus the HPA is ingored and no adjustment happens. targetValue: 0, // this is treated as invalid in the test, thus the HPA is ingored and no adjustment happens.
scheduleTarget: 1000,
}, },
} { } {
t.Run(tc.msg, func(t *testing.T) { t.Run(tc.msg, func(t *testing.T) {
@ -384,7 +395,7 @@ func TestAdjustScaling(t *testing.T) {
Type: v1.OneTimeSchedule, Type: v1.OneTimeSchedule,
Date: &scheduleDate, Date: &scheduleDate,
DurationMinutes: 15, DurationMinutes: 15,
Value: 1000, Value: tc.scheduleTarget,
}, },
}, },
}, },