-
Notifications
You must be signed in to change notification settings - Fork 664
[Feat] Consume StormService spec.mode in the controller update path and PodAutoscaler #2617
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c320d81
7e15a22
d419b4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,8 +39,34 @@ import ( | |
| "github.com/vllm-project/aibrix/pkg/controller/constants" | ||
| ) | ||
|
|
||
| // AutoscalingStormServiceModeAnnotationKey is the PodAutoscaler annotation that | ||
| // historically selected how role-level scaling applies to a StormService target: | ||
| // "replica" scales StormService.spec.replicas, anything else scales the targeted | ||
| // role's replicas. | ||
| // | ||
| // Deprecated: declare StormService.spec.mode instead. The annotation is only | ||
| // honored as a compatibility fallback when the target StormService does not | ||
| // declare spec.mode, and may be removed once spec.mode is broadly adopted. | ||
| const AutoscalingStormServiceModeAnnotationKey = "autoscaling.aibrix.ai/storm-service-mode" | ||
|
|
||
| // stormServiceScalingMode resolves the deployment mode used to route role-level | ||
| // scaling for a StormService target. A declared StormService.spec.mode is the | ||
| // source of truth. When spec.mode is unset, the deprecated | ||
| // autoscaling.aibrix.ai/storm-service-mode annotation on the PodAutoscaler is | ||
| // honored as a compatibility fallback ("replica" selects replica mode), and any | ||
| // other value keeps the legacy pooled default. spec.replicas is intentionally | ||
| // not used for inference here: replicas == 1 cannot distinguish a pooled | ||
| // StormService from a scaled-down replica-mode one. | ||
| func stormServiceScalingMode(pa *autoscalingv1alpha1.PodAutoscaler, ss *orchestrationv1alpha1.StormService) orchestrationv1alpha1.StormServiceMode { | ||
| if ss.Spec.Mode != "" { | ||
| return ss.Spec.Mode | ||
| } | ||
| if pa.Annotations[AutoscalingStormServiceModeAnnotationKey] == "replica" { | ||
| return orchestrationv1alpha1.StormServiceReplicaMode | ||
| } | ||
| return orchestrationv1alpha1.StormServicePooledMode | ||
| } | ||
|
|
||
| // WorkloadScale provides scaling operations for different workload types. | ||
| // It provides the mechanism to get/set replica counts on workload resources, | ||
| // while AutoScaler provides the intelligence to compute desired replica counts. | ||
|
|
@@ -152,9 +178,11 @@ func (s *workloadScale) getCurrentReplicasForRole(ctx context.Context, pa *autos | |
| return 0, err | ||
| } | ||
|
|
||
| // replica mode, return the replicas directly | ||
| // we can not easily use `*ss.Spec.Replicas > 1` as condition since 1 could be pool or replica both case under autoscaling scenarios | ||
| if pa.Annotations[AutoscalingStormServiceModeAnnotationKey] == "replica" { | ||
| // Replica mode scales the whole StormService, so report spec.replicas directly. | ||
| if stormServiceScalingMode(pa, ss) == orchestrationv1alpha1.StormServiceReplicaMode { | ||
| if ss.Spec.Replicas == nil { | ||
| return 0, nil | ||
| } | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please double-check this nil branch? Since |
||
| return *ss.Spec.Replicas, nil | ||
| } | ||
|
|
||
|
|
@@ -246,9 +274,9 @@ func (s *workloadScale) setDesiredReplicasForRole(ctx context.Context, pa *autos | |
| return err | ||
| } | ||
| upd := cur.DeepCopy() | ||
| // TODO: tricky part. it's hard to know replica=1 is pooling or replica mode, we can use autoscaling to limit it. | ||
| // we can extract the method and fallback to ss object annotation to check as well. | ||
| if pa.Annotations[AutoscalingStormServiceModeAnnotationKey] == "replica" { | ||
| // Replica mode scales the whole StormService through spec.replicas; pooled mode | ||
| // scales the targeted role. See stormServiceScalingMode for the resolution order. | ||
| if stormServiceScalingMode(pa, cur) == orchestrationv1alpha1.StormServiceReplicaMode { | ||
| upd.Spec.Replicas = ptr.To(replicas) | ||
| return s.client.Patch(ctx, upd, client.MergeFrom(cur)) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -272,17 +272,16 @@ func (r *StormServiceReconciler) rollout(ctx context.Context, stormService, curr | |
| if len(updated) == int(expectReplica) { | ||
| return nil | ||
| } | ||
| switch stormService.Spec.UpdateStrategy.Type { | ||
| case "": | ||
| // By default use RollingUpdate strategy | ||
| fallthrough | ||
| case orchestrationv1alpha1.RollingUpdateStormServiceStrategyType: | ||
| return r.rollingUpdate(allRoleSets, stormService, current, currentCR, updateCR) | ||
| case orchestrationv1alpha1.InPlaceUpdateStormServiceStrategyType: | ||
| // The update path follows the declared spec.mode when it is set and falls back to | ||
| // the legacy updateStrategy.type selection otherwise, see EffectiveUpdateStrategyType. | ||
| strategyType, err := EffectiveUpdateStrategyType(stormService) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While reviewing the update path changes, we noticed a critical issue regarding how In stormService.Status.UpdatedReplicas == *stormService.Spec.Replicas
stormService.Status.Replicas == *stormService.Spec.ReplicasThis will cause a critical nil pointer dereference panic and crash the controller whenever a Additionally, in Please ensure that
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please double-check the |
||
| if err != nil { | ||
| return err | ||
| } | ||
| if strategyType == orchestrationv1alpha1.InPlaceUpdateStormServiceStrategyType { | ||
| return r.inPlaceUpdate(allRoleSets, stormService, current, currentCR, updateCR) | ||
| default: | ||
| return fmt.Errorf("unexpected stormService strategy type: %s", stormService.Spec.UpdateStrategy.Type) | ||
| } | ||
| return r.rollingUpdate(allRoleSets, stormService, current, currentCR, updateCR) | ||
| } | ||
|
|
||
| // rollingUpdate: rolling update logic for replica mode | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since
spec.replicasdefaults to1when omitted (as documented instormservice_types.go), returning0here whenss.Spec.Replicasisnilcan lead to incorrect autoscaling calculations (e.g., division by zero or incorrect scaling ratios). It should return1instead to reflect the default replica count.