Skip to content

[RayService] Add skipGateway option for NewClusterWithIncrementalUpgrade#13

Draft
savitagm wants to merge 7 commits into
release-1.5from
savitagm/rayservice-skip-gateway
Draft

[RayService] Add skipGateway option for NewClusterWithIncrementalUpgrade#13
savitagm wants to merge 7 commits into
release-1.5from
savitagm/rayservice-skip-gateway

Conversation

@savitagm

Copy link
Copy Markdown

Summary

Adds skipGateway: true support to NewClusterWithIncrementalUpgrade, enabling zero-downtime rolling upgrades for deployments that use a client-side load balancer instead of a Gateway/HTTPRoute for traffic splitting.

When skipGateway: true is set:

  • The operator skips Gateway and HTTPRoute reconciliation entirely
  • Traffic split is communicated via targetCapacity in activeServiceStatus / pendingServiceStatus — the client-side LB reads these values to route traffic
  • TrafficRoutedPercent reflects the actual split only after the pending cluster's Serve deployments are HEALTHY and worker replica parity is met (capacity-ready gate)
  • Serve config blocking during upgrade is consistent with the gateway path: active cluster's serve config is frozen while UpgradeInProgress=true; the updated config goes to the pending cluster

Changes

Commit Description
6cbc10a0 Add skipGateway field to ClusterUpgradeOptions CRD; skip Gateway/HTTPRoute reconciliation when set
d85d6397 Replace timer-based TC step with serve deployment health gate (isPendingClusterCapacityReady)
3ee51aa4 Skip gateway-specific field validations (HTTPRoute, Gateway names) when skipGateway=true
dc97d621 Capacity-parity check: TC stalls until pending has ceil(TC% × activeWorkers) available workers; add upgradeTimeoutSeconds support
88b0001c / 951e4fbd Exempt skipGateway services from in-tree autoscaler requirement (gateway path requires it; skipGateway does not)
d68528ba Fix spurious "pending cluster not yet capacity-ready" log firing every reconcile when no upgrade is in progress

Key behaviors

Capacity-ready gate — TC advances only when:

  1. All pending cluster Serve deployments are HEALTHY
  2. pendingAvailableWorkers >= ceil(pendingTC% × activeAvailableWorkers) — or pending maxReplicas < activeAvailable (capacity reduction path, always passes)

TC stepping — With maxSurgePercent=20 and 2 active workers:

  • TC steps 0→20→40→60, then stalls at 60 (ceil(60%×2)=2 workers needed)
  • Unblocks once pending cluster has ≥2 available workers, then advances to 80→100

Serve config consistency — Both gateway and skipGateway paths use the same blocking logic: while UpgradeInProgress=true, serve config changes to the active cluster are held; they go to the pending cluster. Escape hatches:

  • Delete the pending cluster → exits upgrade mode, serve config propagates to active on next reconcile
  • Trigger a new spec change → old pending replaced, new pending picks up updated serve config

upgradeTimeoutSeconds — Acts as a per-attempt circuit breaker: after timeout, the pending cluster is deleted and a Warning event is emitted. The operator will retry (create a new pending cluster) on the next reconcile since the spec still differs. To permanently stop a timed-out upgrade, either revert the spec or delete the pending cluster manually.

Future work

Permanent-abort on timeout (upgradeTimeoutSeconds as a hard stop): currently the timeout is a per-attempt circuit breaker — after deleting the pending cluster, the operator immediately retries because shouldPrepareNewCluster detects the spec hash still differs. A future enhancement would annotate the RayService with the timed-out spec hash (ray.io/upgrade-timeout-hash) and check it in shouldPrepareNewCluster to skip retrying for the same hash. The annotation would be cleared when a new (different) spec change arrives. This was deferred to keep this PR focused.

Testing

Manually tested all 7 scenarios via the /skipgw-rollout-test skill against a live RayService (llm-experiments/aipts-1466-skip-gw-test) with A100 GPUs:

# Test Result
1 Steady-state: no spurious logs PASS
2 maxSurgePercent=100 (TC jumps 0→100) PASS
3 Re-trigger mid-upgrade PASS
4 Serve config blocked on active PASS
5 upgradeTimeoutSeconds PASS
6 TC stepping with multi-worker active cluster PASS
7 minReplicas=0 PASS

Unit tests added for isPendingClusterCapacityReady covering: nil apps, nil clusters, capacity reduction bypass, insufficient/sufficient workers, and TC=0.

savitagm and others added 7 commits June 3, 2026 15:58
…port

Introduces `skipGateway` field in `ClusterUpgradeOptions` for the
`NewClusterWithIncrementalUpgrade` strategy. When enabled:

- Gateway and HTTPRoute objects are not created or reconciled
- Gateway/HTTPRoute readiness checks are skipped in the target_capacity
  advancement gate (checkIfNeedTargetCapacityUpdate)
- TrafficRoutedPercent advances via the existing time-step logic
  (IntervalSeconds + StepSizePercent) instead of being read from
  HTTPRoute backend ref weights

This allows deployments using client-side load balancers to use
incremental upgrade without requiring a Gateway API implementation.
The client-side LB is responsible for reading targetCapacity from
RayService status and splitting traffic accordingly.

GatewayClassName is now optional when skipGateway is true.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
In skipGateway mode, TrafficRoutedPercent previously advanced on a fixed
timer (intervalSeconds + stepSizePercent). This caused capacity gaps when
pod cold-start exceeded intervalSeconds, routing traffic to pending before
replicas were ready.

Replace with a serve-deployment health gate: TrafficRoutedPercent is set
to match TargetCapacity only when the pending cluster's Serve deployments
report HEALTHY, meaning autoscaling has fully settled at the current
target_capacity. The existing reconcileServeTargetCapacity gate
(pendingTargetCapacity != pendingTrafficRoutedPercent) then naturally
advances to the next step — no timer needed.

IntervalSeconds and StepSizePercent are made optional (omitempty) since
they are not used in skipGateway mode. GatewayClassName was already
optional. MaxSurgePercent remains the only functional field for
skipGateway: it controls how large each target_capacity step is.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GatewayClassName, StepSizePercent, and IntervalSeconds are not needed
when skipGateway is enabled — guard the checks accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ixes

- Replace arePendingServeDeploymentsHealthy with isPendingClusterCapacityReady
  at the TrafficRoutedPercent gate so that the pending cluster must have enough
  available worker replicas (ceil(pendingTargetCap/100 * active.availableWorkers))
  before traffic routing advances. Includes deadlock-safe bypass when
  pendingMaxReplicas < activeAvailableWorkers (intentional capacity reduction).

- Add UpgradeTimeoutSeconds field to ClusterUpgradeOptions. When set, the operator
  aborts the upgrade by deleting the pending RayCluster and emitting a Warning event
  if the pending cluster's age exceeds the timeout.

- Add UpgradeTimeout event type constant.

- Add validation: upgradeTimeoutSeconds must be >= 0 when set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
autoscaler is required for gateway-based incremental traffic routing
but not for skipGateway mode, where traffic routing is handled by a
client-side load balancer reading targetCapacity from RayService status.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… waivers

autoscaler is required regardless of skipGateway: it drives targetCapacity
scaling so the pending cluster ramps up workers proportionally instead of
starting at full replicas. Without autoscaler the gradual capacity transfer
semantics are broken.

skipGateway only waives stepSizePercent, intervalSeconds, and gatewayClassName
which are gateway-routing-specific fields not used in the client-side LB path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rade in progress

When upgradeStrategy.type=NewClusterWithIncrementalUpgrade with skipGateway=true,
the TC/TRP gate code ran every reconcile regardless of whether a pending cluster
existed. With no pending cluster, arePendingServeDeploymentsHealthy(nil) returns
false, so the log fired on every reconcile post-upgrade.

Fix: guard the SkipGateway TC/TRP block with pendingCluster != nil. Initialize
activeWeight/pendingWeight to -1 (the sentinel for "don't update") so the no-
pending-cluster case correctly skips the update rather than falling through to
the HTTPRoute weight reader.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@savitagm savitagm marked this pull request as draft June 23, 2026 21:35
@savitagm savitagm changed the base branch from master to release-1.5 June 23, 2026 21:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant