[RayCluster][Kueue] Delete Services during suspension - #4935
Conversation
AndySung320
left a comment
There was a problem hiding this comment.
Agree with the potential follow-up, I have also proposed that in #4896 (comment)
Just to make sure I understand the intended scope: is this issue about RayCluster-owned resources only, or about the cross-controller case where the suspended RayCluster is owned by a RayJob and we also need to clean up RayJob-owned resources such as the submitter Job/Pod? If it is the latter, I think the test should probably involve a RayJob-owned RayCluster rather than only a standalone RayCluster.
Thanks for pointing this out. I agree the scope is a bit easy to mix up. My understanding is that this PR is scoped to the standalone RayCluster suspend path, not the RayJob-owned / cross-controller cleanup path. That’s why this PR uses a standalone RayCluster and only checks that RayCluster-owned Pods are deleted after suspend. Happy to adjust if maintainers intended this to cover the broader RayJob-owned cleanup path instead. |
a995063 to
3418e9a
Compare
|
I am actually thinking that we might not need this PR, since this PR already does the same thing. |
3418e9a to
49ec22d
Compare
Thanks for checking. I agree there is overlap in the observable outcome that RayCluster-related Pods are gone. The distinction I had in mind is that #4896 covers the RayJob suspend path, where the RayCluster CR is deleted and child resources are cleaned up by Kubernetes GC. This PR covers standalone RayCluster If you think the #4896 coverage is sufficient for #4876, I’m happy to close this PR as duplicate! |
Future-Outlier
left a comment
There was a problem hiding this comment.
- plz update the pr description
- plz don't link the original rayjob suspend issue
Done. Updated the PR description and kept it focused on RayCluster suspend (#4876). I also removed the RayJob suspend cross-reference from my previous comment where possible. Thanks! |
|
cc @ruiean to merge, tks! |
49ec22d to
1eced7d
Compare
1eced7d to
9c41925
Compare
@win5923 Thanks, I see! I went back through #4876 and expanded this beyond the original test-only scope. It now deletes the RayCluster-owned Services on suspend and brings them back on resume. I also kept the existing suspend commit-point semantics, but moved Pod teardown ahead of Service teardown so a failed Service deletion can't hold up quota release. The status path now handles the missing head Service as well, and the suspend coverage exercises the full suspend/resume path. For now I've kept this to the Pods + Services called out in #4876. The other owned resources have different lifecycle semantics, especially the GCS PVC, so I'd rather treat those separately if we want to extend suspend further. |
9c41925 to
dbbbf78
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
Reviewed by Cursor Bugbot for commit dbbbf78. Configure here.
| // rayClusterSuspendDeletesServices stays armed for as long as a cluster is suspended, | ||
| // unlike the Pod teardown, which ends the moment suspension completes. Pods cannot | ||
| // reappear on their own; Services left behind by an older operator still have to | ||
| // converge, without asking the user to resume first. | ||
| func rayClusterSuspendDeletesServices(instance *rayv1.RayCluster) bool { | ||
| return rayClusterSuspendCommitted(instance) || | ||
| utils.FindRayClusterSuspendStatus(instance) == rayv1.RayClusterSuspended | ||
| } |
There was a problem hiding this comment.
Just wondering, should we follow the same suspend state machine as RayJob and RayService?
(no suspend) --Spec.Suspend=true--> Suspending --owned resources deleted--> Suspended --Spec.Suspend=false--> (no suspend)
The current implementation transitions to RayClusterSuspended based only on the Pod count:
kuberay/ray-operator/controllers/ray/raycluster_controller.go
Lines 2065 to 2072 in 215b98f
This means a K8s Service deletion can still be in progress or failing in the Suspended state.
Would it make more sense to wait until all owned resources, are deleted before transitioning to RayClusterSuspended, consistent with RayJob and RayService?
There was a problem hiding this comment.
Thanks! I think that makes sense and ended up going with that model. I changed the condition to follow that model: RayClusterSuspended now waits until the Pods and owned Services are actually gone, including Services left behind by an older operator. A terminating Service keeps it in Suspending and is reported in the condition message.
I kept .Status.State Pod-gated, though. Kueue still uses State == Ready as the RayCluster's active signal, so waiting on Service teardown there could delay quota release behind a stuck finalizer.
I think this gives us the stricter condition semantics without changing the existing Kueue contract, but curious what you think?
I also moved the suspend Pod/Service reconciliation ahead of unrelated resources so failures there can't block teardown. The wait stays scoped to the Pods + Services from #4876; the other resource lifecycles are unchanged.
Suspending a RayCluster deleted its Pods but left the head, serve and headless Services behind, which made standalone RayCluster inconsistent with the other suspend paths. Delete those Services while the cluster is suspended and recreate them on resume. The existing suspend commit point is unchanged. The RayClusterSuspending -> RayClusterSuspended transition now waits for those Services to be deleted, so the condition means the teardown this path performs is done. While it waits, the Suspending condition names the Services that are still going, so a deletion that never finishes is visible rather than an unexplained stall. A cluster suspended by an operator that predates this teardown gives up its Suspended condition until its leftover Services are collected, rather than reporting a teardown that has not happened. .Status.State keeps its older, Pod-gated meaning. It is deprecated in favour of the conditions, but Kueue reads it as the RayCluster's active signal and releases an evicted Workload's quota only once it stops reading Ready, so gating it on the Services as well would hold that quota behind a Service that is slow, or unable, to go away. During a committed suspend the Pods and Services are reconciled ahead of the other resources. The pass stops at the first error, so an unrelated permanent failure would otherwise leave the Services undeleted and the cluster stuck in RayClusterSuspending, unable to resume. The status path now tolerates the head Service being absent while the suspension explains it, or while a failed reconcile does. Without that, calculateStatus would discard the status it had just computed and the cluster could never finish suspending or resuming. Scope is the Pods and Services called out in ray-project#4876. Other owned resources have different lifecycle semantics and are left unchanged.
dbbbf78 to
17ccc02
Compare

Why are these changes needed?
Suspending a RayCluster deleted its Pods but left the head, serve, and headless Services behind. This made standalone RayCluster inconsistent with the other suspend paths.
This PR deletes those Services while suspended and recreates them on resume. It preserves the existing suspend commit point, but tears Pods down before Services so a failed Service deletion cannot block quota release.
The status path now tolerates the head Service being absent during suspension, allowing the suspend/resume transition to complete normally.
Scope is limited to the Pods and Services called out in #4876. Other owned resources have different lifecycle semantics and are left unchanged.
One behavior worth calling out:
RayClusterSuspendedstill becomes true once the Pods are gone, rather than waiting for every owned resource to be deleted.Related issue number
Closes #4876
Checks