Skip to content

Commit 4cd84f3

Browse files
Add Fallback option behavior for dynamic fallback calculation (#6464)
Signed-off-by: rickbrouwer <[email protected]> Signed-off-by: Rick Brouwer <[email protected]> Co-authored-by: Jan Wozniak <[email protected]>
1 parent 767eb3d commit 4cd84f3

File tree

9 files changed

+598
-52
lines changed

9 files changed

+598
-52
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio
6363

6464
### New
6565

66+
- **General**: Add Fallback option `behavior` for dynamic fallback calculation ([#6450](https://github.com/kedacore/keda/issues/6450))
6667
- **General**: Enable OpenSSF Scorecard to enhance security practices across the project ([#5913](https://github.com/kedacore/keda/issues/5913))
6768
- **General**: Introduce new NSQ scaler ([#3281](https://github.com/kedacore/keda/issues/3281))
6869
- **General**: Operator flag to control patching of webhook resources certificates ([#6184](https://github.com/kedacore/keda/issues/6184))

apis/keda/v1alpha1/scaledobject_types.go

+8
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ const ScaledObjectTransferHpaOwnershipAnnotation = "scaledobject.keda.sh/transfe
5959
const ValidationsHpaOwnershipAnnotation = "validations.keda.sh/hpa-ownership"
6060
const PausedReplicasAnnotation = "autoscaling.keda.sh/paused-replicas"
6161
const PausedAnnotation = "autoscaling.keda.sh/paused"
62+
const FallbackBehaviorStatic = "static"
63+
const FallbackBehaviorCurrentReplicas = "currentReplicas"
64+
const FallbackBehaviorCurrentReplicasIfHigher = "currentReplicasIfHigher"
65+
const FallbackBehaviorCurrentReplicasIfLower = "currentReplicasIfLower"
6266

6367
// HealthStatus is the status for a ScaledObject's health
6468
type HealthStatus struct {
@@ -112,6 +116,10 @@ type ScaledObjectSpec struct {
112116
type Fallback struct {
113117
FailureThreshold int32 `json:"failureThreshold"`
114118
Replicas int32 `json:"replicas"`
119+
// +optional
120+
// +kubebuilder:default=static
121+
// +kubebuilder:validation:Enum=static;currentReplicas;currentReplicasIfHigher;currentReplicasIfLower
122+
Behavior string `json:"behavior,omitempty"`
115123
}
116124

117125
// AdvancedConfig specifies advance scaling options

config/crd/bases/keda.sh_scaledobjects.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,14 @@ spec:
225225
fallback:
226226
description: Fallback is the spec for fallback options
227227
properties:
228+
behavior:
229+
default: static
230+
enum:
231+
- static
232+
- currentReplicas
233+
- currentReplicasIfHigher
234+
- currentReplicasIfLower
235+
type: string
228236
failureThreshold:
229237
format: int32
230238
type: integer

pkg/fallback/fallback.go

+48-5
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ import (
2424
v2 "k8s.io/api/autoscaling/v2"
2525
"k8s.io/apimachinery/pkg/api/resource"
2626
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/client-go/scale"
2728
"k8s.io/metrics/pkg/apis/external_metrics"
2829
runtimeclient "sigs.k8s.io/controller-runtime/pkg/client"
2930
logf "sigs.k8s.io/controller-runtime/pkg/log"
3031

3132
kedav1alpha1 "github.com/kedacore/keda/v2/apis/keda/v1alpha1"
33+
"github.com/kedacore/keda/v2/pkg/scaling/resolver"
3234
)
3335

3436
var log = logf.Log.WithName("fallback")
@@ -51,7 +53,7 @@ func isFallbackEnabled(scaledObject *kedav1alpha1.ScaledObject, metricSpec v2.Me
5153
return true
5254
}
5355

54-
func GetMetricsWithFallback(ctx context.Context, client runtimeclient.Client, metrics []external_metrics.ExternalMetricValue, suppressedError error, metricName string, scaledObject *kedav1alpha1.ScaledObject, metricSpec v2.MetricSpec) ([]external_metrics.ExternalMetricValue, bool, error) {
56+
func GetMetricsWithFallback(ctx context.Context, client runtimeclient.Client, scaleClient scale.ScalesGetter, metrics []external_metrics.ExternalMetricValue, suppressedError error, metricName string, scaledObject *kedav1alpha1.ScaledObject, metricSpec v2.MetricSpec) ([]external_metrics.ExternalMetricValue, bool, error) {
5557
status := scaledObject.Status.DeepCopy()
5658

5759
initHealthStatus(status)
@@ -81,7 +83,16 @@ func GetMetricsWithFallback(ctx context.Context, client runtimeclient.Client, me
8183
log.Info("Failed to validate ScaledObject Spec. Please check that parameters are positive integers", "scaledObject.Namespace", scaledObject.Namespace, "scaledObject.Name", scaledObject.Name)
8284
return nil, false, suppressedError
8385
case *healthStatus.NumberOfFailures > scaledObject.Spec.Fallback.FailureThreshold:
84-
return doFallback(scaledObject, metricSpec, metricName, suppressedError), true, nil
86+
var currentReplicas int32
87+
var err error
88+
89+
if scaledObject.Spec.Fallback.Behavior != kedav1alpha1.FallbackBehaviorStatic {
90+
currentReplicas, err = resolver.GetCurrentReplicas(ctx, client, scaleClient, scaledObject)
91+
if err != nil {
92+
return nil, false, suppressedError
93+
}
94+
}
95+
return doFallback(scaledObject, metricSpec, metricName, currentReplicas, suppressedError), true, nil
8596
default:
8697
return nil, false, suppressedError
8798
}
@@ -108,8 +119,34 @@ func HasValidFallback(scaledObject *kedav1alpha1.ScaledObject) bool {
108119
modifierChecking
109120
}
110121

111-
func doFallback(scaledObject *kedav1alpha1.ScaledObject, metricSpec v2.MetricSpec, metricName string, suppressedError error) []external_metrics.ExternalMetricValue {
112-
replicas := int64(scaledObject.Spec.Fallback.Replicas)
122+
func doFallback(scaledObject *kedav1alpha1.ScaledObject, metricSpec v2.MetricSpec, metricName string, currentReplicas int32, suppressedError error) []external_metrics.ExternalMetricValue {
123+
fallbackBehavior := scaledObject.Spec.Fallback.Behavior
124+
fallbackReplicas := int64(scaledObject.Spec.Fallback.Replicas)
125+
var replicas int64
126+
127+
switch fallbackBehavior {
128+
case kedav1alpha1.FallbackBehaviorStatic:
129+
replicas = fallbackReplicas
130+
case kedav1alpha1.FallbackBehaviorCurrentReplicas:
131+
replicas = int64(currentReplicas)
132+
case kedav1alpha1.FallbackBehaviorCurrentReplicasIfHigher:
133+
currentReplicasCount := int64(currentReplicas)
134+
if currentReplicasCount > fallbackReplicas {
135+
replicas = currentReplicasCount
136+
} else {
137+
replicas = fallbackReplicas
138+
}
139+
case kedav1alpha1.FallbackBehaviorCurrentReplicasIfLower:
140+
currentReplicasCount := int64(currentReplicas)
141+
if currentReplicasCount < fallbackReplicas {
142+
replicas = currentReplicasCount
143+
} else {
144+
replicas = fallbackReplicas
145+
}
146+
default:
147+
replicas = fallbackReplicas
148+
}
149+
113150
var normalisationValue int64
114151
if !scaledObject.IsUsingModifiers() {
115152
normalisationValue = int64(metricSpec.External.Target.AverageValue.AsApproximateFloat64())
@@ -126,7 +163,13 @@ func doFallback(scaledObject *kedav1alpha1.ScaledObject, metricSpec v2.MetricSpe
126163
}
127164
fallbackMetrics := []external_metrics.ExternalMetricValue{metric}
128165

129-
log.Info("Suppressing error, falling back to fallback.replicas", "scaledObject.Namespace", scaledObject.Namespace, "scaledObject.Name", scaledObject.Name, "suppressedError", suppressedError, "fallback.replicas", replicas)
166+
log.Info("Suppressing error, using fallback metrics",
167+
"scaledObject.Namespace", scaledObject.Namespace,
168+
"scaledObject.Name", scaledObject.Name,
169+
"suppressedError", suppressedError,
170+
"fallback.behavior", fallbackBehavior,
171+
"fallback.replicas", fallbackReplicas,
172+
"workload.currentReplicas", currentReplicas)
130173
return fallbackMetrics
131174
}
132175

0 commit comments

Comments
 (0)