Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bf87764
[Feature] Support recreate pods for RayCluster using RayClusterSpec
win5923 Nov 18, 2025
9359c5f
Add test
win5923 Nov 20, 2025
acd7739
improve readability
win5923 Nov 22, 2025
f8102f3
Remove deepcopy in GeneratePodTemplateHash
win5923 Dec 2, 2025
c93e25b
Refactor ValidateRayClusterUpgradeOptions
win5923 Dec 2, 2025
9b622e5
add kubebuilder:validation
win5923 Dec 6, 2025
5ec2fff
Rename the RayServiceUpgradeType and RayClusterUpgradeType constants
win5923 Dec 7, 2025
4c724b5
add ray.io/kuberay-version annotations for head pod and worker pods
win5923 Dec 7, 2025
d159038
Update ray-operator/controllers/ray/common/pod.go
win5923 Dec 8, 2025
9661177
Revert "add ray.io/kuberay-version annotations for head pod and worke…
win5923 Dec 10, 2025
1923c25
add rayClusterScaleExpectation.Delete for deleteAllPods
win5923 Dec 10, 2025
b829245
Apply suggestions
win5923 Dec 10, 2025
199c021
Merge branch 'upstream-master' into raycluster-upgradeStrategy
win5923 Dec 17, 2025
90bd2a0
better logic
Future-Outlier Dec 22, 2025
4b2147f
Merge branch 'master' into raycluster-upgradeStrategy
Future-Outlier Dec 22, 2025
70bf2f3
solve ci err
Future-Outlier Dec 22, 2025
bde102c
update
Future-Outlier Dec 22, 2025
8da2fed
Merge remote-tracking branch 'upstream/master' into raycluster-upgrad…
Future-Outlier Dec 23, 2025
6755388
better yaml file
Future-Outlier Dec 23, 2025
f2854fc
Commented out upgradeStrategy for sample yaml
win5923 Dec 23, 2025
54db00f
Update container image for TestRayClusterUpgradeStrategy test
win5923 Dec 23, 2025
bf27916
Compare RayClusterSpec
win5923 Dec 27, 2025
ee549d4
Merge branch 'upstream-master' into raycluster-upgradeStrategy
win5923 Dec 27, 2025
4f7c460
Remove WorkerGroupSpecs.IdleTimeoutSeconds and Suspend to follow RayS…
win5923 Dec 27, 2025
fe87a41
Follow RayService's solution
win5923 Dec 27, 2025
9974a0a
Trigger CI
Future-Outlier Dec 29, 2025
48c7288
Merge branch 'upstream-master' into raycluster-upgradeStrategy
win5923 Dec 30, 2025
248dfe9
update the head pod to get the cluster hash and new KubeRay version w…
win5923 Dec 30, 2025
48cdf98
Use UpgradeStrategyRecreateHashKey annotations for RayCluster upgrade…
win5923 Dec 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion docs/reference/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ _Appears in:_

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `upgradeStrategy` _[RayClusterUpgradeStrategy](#rayclusterupgradestrategy)_ | UpgradeStrategy defines the scaling policy used when upgrading the RayCluster | | |
| `authOptions` _[AuthOptions](#authoptions)_ | AuthOptions specifies the authentication options for the RayCluster. | | |
| `suspend` _boolean_ | Suspend indicates whether a RayCluster should be suspended.<br />A suspended RayCluster will have head pods and worker pods deleted. | | |
| `managedBy` _string_ | ManagedBy is an optional configuration for the controller or entity that manages a RayCluster.<br />The value must be either 'ray.io/kuberay-operator' or 'kueue.x-k8s.io/multikueue'.<br />The kuberay-operator reconciles a RayCluster which doesn't have this field at all or<br />the field value is the reserved string 'ray.io/kuberay-operator',<br />but delegates reconciling the RayCluster with 'kueue.x-k8s.io/multikueue' to the Kueue.<br />The field is immutable. | | |
Expand All @@ -313,6 +314,36 @@ _Appears in:_
| `workerGroupSpecs` _[WorkerGroupSpec](#workergroupspec) array_ | WorkerGroupSpecs are the specs for the worker pods | | |


#### RayClusterUpgradeStrategy







_Appears in:_
- [RayClusterSpec](#rayclusterspec)

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `type` _[RayClusterUpgradeType](#rayclusterupgradetype)_ | Type represents the strategy used when upgrading the RayCluster Pods. Currently supports `Recreate` and `None`. | | Enum: [Recreate None] <br /> |


#### RayClusterUpgradeType

_Underlying type:_ _string_



_Validation:_
- Enum: [Recreate None]

_Appears in:_
- [RayClusterUpgradeStrategy](#rayclusterupgradestrategy)



#### RayCronJob


Expand Down Expand Up @@ -465,7 +496,7 @@ _Appears in:_

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `type` _[RayServiceUpgradeType](#rayserviceupgradetype)_ | Type represents the strategy used when upgrading the RayService. Currently supports `NewCluster` and `None`. | | |
| `type` _[RayServiceUpgradeType](#rayserviceupgradetype)_ | Type represents the strategy used when upgrading the RayService. Currently supports `NewCluster`, `NewClusterWithIncrementalUpgrade` and `None`. | | |
| `clusterUpgradeOptions` _[ClusterUpgradeOptions](#clusterupgradeoptions)_ | ClusterUpgradeOptions defines the behavior of a NewClusterWithIncrementalUpgrade type.<br />RayServiceIncrementalUpgrade feature gate must be enabled to set ClusterUpgradeOptions. | | |


Expand Down
8 changes: 8 additions & 0 deletions helm-chart/kuberay-operator/crds/ray.io_rayclusters.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions helm-chart/kuberay-operator/crds/ray.io_raycronjobs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4614,6 +4614,14 @@ spec:
type: string
suspend:
type: boolean
upgradeStrategy:
properties:
type:
enum:
- Recreate
- None
type: string
type: object
workerGroupSpecs:
items:
properties:
Expand Down
8 changes: 8 additions & 0 deletions helm-chart/kuberay-operator/crds/ray.io_rayjobs.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions helm-chart/kuberay-operator/crds/ray.io_rayservices.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions ray-operator/apis/ray/v1/raycluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (

// RayClusterSpec defines the desired state of RayCluster
type RayClusterSpec struct {
// UpgradeStrategy defines the scaling policy used when upgrading the RayCluster
// +optional
UpgradeStrategy *RayClusterUpgradeStrategy `json:"upgradeStrategy,omitempty"`
// AuthOptions specifies the authentication options for the RayCluster.
// +optional
AuthOptions *AuthOptions `json:"authOptions,omitempty"`
Expand Down Expand Up @@ -49,6 +52,22 @@ type RayClusterSpec struct {
WorkerGroupSpecs []WorkerGroupSpec `json:"workerGroupSpecs,omitempty"`
}

// +kubebuilder:validation:Enum=Recreate;None
type RayClusterUpgradeType string

const (
// During upgrade, Recreate strategy will delete all existing pods before creating new ones
RayClusterRecreate RayClusterUpgradeType = "Recreate"
// No new pod will be created while the strategy is set to None
RayClusterUpgradeNone RayClusterUpgradeType = "None"
)
Comment thread
win5923 marked this conversation as resolved.

type RayClusterUpgradeStrategy struct {
// Type represents the strategy used when upgrading the RayCluster Pods. Currently supports `Recreate` and `None`.
// +optional
Type *RayClusterUpgradeType `json:"type,omitempty"`
}

// AuthMode describes the authentication mode for the Ray cluster.
type AuthMode string

Expand Down
8 changes: 4 additions & 4 deletions ray-operator/apis/ray/v1/rayservice_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ type RayServiceUpgradeType string
const (
// During upgrade, NewClusterWithIncrementalUpgrade strategy will create an upgraded cluster to gradually scale
// and migrate traffic to using Gateway API.
NewClusterWithIncrementalUpgrade RayServiceUpgradeType = "NewClusterWithIncrementalUpgrade"
RayServiceNewClusterWithIncrementalUpgrade RayServiceUpgradeType = "NewClusterWithIncrementalUpgrade"
// During upgrade, NewCluster strategy will create new upgraded cluster and switch to it when it becomes ready
NewCluster RayServiceUpgradeType = "NewCluster"
RayServiceNewCluster RayServiceUpgradeType = "NewCluster"
// No new cluster will be created while the strategy is set to None
None RayServiceUpgradeType = "None"
RayServiceUpgradeNone RayServiceUpgradeType = "None"
Comment thread
win5923 marked this conversation as resolved.
)

// These statuses should match Ray Serve's application statuses
Expand Down Expand Up @@ -75,7 +75,7 @@ type ClusterUpgradeOptions struct {
}

type RayServiceUpgradeStrategy struct {
// Type represents the strategy used when upgrading the RayService. Currently supports `NewCluster` and `None`.
// Type represents the strategy used when upgrading the RayService. Currently supports `NewCluster`, `NewClusterWithIncrementalUpgrade` and `None`.
// +optional
Type *RayServiceUpgradeType `json:"type,omitempty"`
// ClusterUpgradeOptions defines the behavior of a NewClusterWithIncrementalUpgrade type.
Expand Down
25 changes: 25 additions & 0 deletions ray-operator/apis/ray/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions ray-operator/config/crd/bases/ray.io_rayclusters.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions ray-operator/config/crd/bases/ray.io_raycronjobs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4614,6 +4614,14 @@ spec:
type: string
suspend:
type: boolean
upgradeStrategy:
properties:
type:
enum:
- Recreate
- None
type: string
type: object
workerGroupSpecs:
items:
properties:
Expand Down
8 changes: 8 additions & 0 deletions ray-operator/config/crd/bases/ray.io_rayjobs.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions ray-operator/config/crd/bases/ray.io_rayservices.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions ray-operator/config/samples/ray-cluster.sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@ kind: RayCluster
metadata:
name: raycluster-kuberay
spec:
# UpgradeStrategy defines the pod recreation behavior when RayCluster configuration changes
# Available since: KubeRay 1.6.0
# Type of upgrade strategy to use:
# - "None" (default):
# * Configuration changes do not trigger pod recreation
# * Existing pods continue running with old configuration
# * New pods (from scaling) will use new configuration
# * Use this for maximum stability
# - "Recreate":
# * All pods are deleted and recreated when configuration changes
# * Ensures all pods run with the latest configuration
# * Useful for GitOps system like ArgoCD
# * May cause temporary service disruption during recreation
# upgradeStrategy:
# type: None
rayVersion: '2.52.0' # should match the Ray version in the image of the containers
# Ray head pod template
headGroupSpec:
Expand Down
39 changes: 39 additions & 0 deletions ray-operator/controllers/ray/common/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,27 @@ func configureGCSFaultTolerance(podTemplate *corev1.PodTemplateSpec, instance ra
}
}

func GeneratePodTemplateHash(template corev1.PodTemplateSpec) (string, error) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, is it necessary to have this wrapper function, or is it redundant?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is for better readability.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really consider only the Pod template? I feel that most fields should actually be taken into account, with only a few exceptions in the worker group spec, such as ScaleStrategy, Suspend, Replicas, MinReplicas, MaxReplicas, and IdleTimeoutSeconds.

@Future-Outlier Future-Outlier Dec 26, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t have a strong preference since RayCluster is a custom resource.
For me, it’s a 51/49 decision, and I lean toward Rueian’s idea because those fields matter.

Also, in Kubernetes, both Deployments and StatefulSets primarily compare/check the Pod template.

source:

  1. deployment:
    https://github.com/kubernetes/kubernetes/blob/1e2817d5890ac5056e770cbdebdadfb7fc6ef54c/pkg/controller/deployment/util/deployment_util.go#L614-L642

  2. statefulset:
    https://github.com/kubernetes/kubernetes/blob/46cc610e6fe7e9a933a13d77538b0c220c5414a6/pkg/controller/statefulset/stateful_set_utils.go#L542-L608

@win5923 win5923 Dec 26, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, most of the requirements raised by users at the moment are primarily related to image updates, which is why my starting point is at the Pod level rather than the RayCluster level.

#4051
#3905
#2534

But your point makes sense, there are many configurations that should trigger a Pod recreate (like HeadGroupSpec.Resources, RayClusterSpec.AutoscalerOptions). I think we could compare the entire RayClusterSpec directly and exclude certain settings (like rayStartParams, WorkerGroupSpec.Min/Max/Replicas, etc.). WDYT?

@Future-Outlier Future-Outlier Dec 26, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can implement Rueian's advice, since RayCluster is a custom resource, therefore we have custom behavior make sense.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and either we support which version, in the future, if we want to change to another version are all breaking change, so I would vote to Rueian's solution.

@Future-Outlier Future-Outlier Dec 26, 2025

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just talked with @win5923
we can use RayService's solution to achieve this!

@win5923 win5923 Dec 27, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Future-Outlier, @rueian, @CheyuWu, @machichima

I followed the RayService approach to implement the UpgradeStrategy for RayCluster.
Currently, we compute a hash from the entire RayCluster.Spec (excluding these fields) and store it as an annotation on the head Pod.

During reconciliation, we only need to compare the hash on the head Pod to determine whether an upgrade is required. This allows us to avoid re-comparing the spec across all head and worker Pods, simplifying the upgrade detection logic and reducing unnecessary overhead.

return utils.GenerateJsonHash(template)
}

// DefaultHeadPodTemplate sets the config values
func DefaultHeadPodTemplate(ctx context.Context, instance rayv1.RayCluster, headSpec rayv1.HeadGroupSpec, podName string, headPort string) corev1.PodTemplateSpec {
// TODO (Dmitri) The argument headPort is essentially unused;
// headPort is passed into setMissingRayStartParams but unused there for the head pod.
// To mitigate this awkwardness and reduce code redundancy, unify head and worker pod configuration logic.

log := ctrl.LoggerFrom(ctx)
// Calculate the pod template hash before any modifications
// This ensures the hash reflects the original user-defined template for upgrade detection
templateHash := ""
podTemplate := headSpec.Template
if hash, err := GeneratePodTemplateHash(podTemplate); err == nil {
templateHash = hash
} else {
log.Error(err, "Failed to generate pod template hash for head group")
}
Comment thread
win5923 marked this conversation as resolved.
Outdated

if utils.IsDeterministicHeadPodNameEnabled() {
podTemplate.Name = podName
} else {
Expand All @@ -173,6 +188,13 @@ func DefaultHeadPodTemplate(ctx context.Context, instance rayv1.RayCluster, head
// This ensures privilege of KubeRay users are contained within the namespace of the RayCluster.
podTemplate.ObjectMeta.Namespace = instance.Namespace

if templateHash != "" {
if podTemplate.Annotations == nil {
podTemplate.Annotations = make(map[string]string)
}
podTemplate.Annotations[utils.PodTemplateHashKey] = templateHash
}

// Update rayStartParams with top-level Resources for head group.
updateRayStartParamsResources(ctx, headSpec.RayStartParams, headSpec.Resources)

Expand Down Expand Up @@ -296,12 +318,29 @@ func getEnableProbesInjection() bool {

// DefaultWorkerPodTemplate sets the config values
func DefaultWorkerPodTemplate(ctx context.Context, instance rayv1.RayCluster, workerSpec rayv1.WorkerGroupSpec, podName string, fqdnRayIP string, headPort string, replicaGrpName string, replicaIndex int, numHostIndex int) corev1.PodTemplateSpec {
log := ctrl.LoggerFrom(ctx)

podTemplate := workerSpec.Template
// Calculate the pod template hash before any modifications
// This ensures the hash reflects the original user-defined template for upgrade detection
templateHash := ""
if hash, err := GeneratePodTemplateHash(podTemplate); err == nil {
templateHash = hash
} else {
log.Error(err, "Failed to generate pod template hash for worker group", "groupName", workerSpec.GroupName)
}
podTemplate.GenerateName = podName

// Pods created by RayCluster should be restricted to the namespace of the RayCluster.
// This ensures privilege of KubeRay users are contained within the namespace of the RayCluster.
podTemplate.ObjectMeta.Namespace = instance.Namespace

if templateHash != "" {
if podTemplate.Annotations == nil {
podTemplate.Annotations = make(map[string]string)
}
podTemplate.Annotations[utils.PodTemplateHashKey] = templateHash
}
// The Ray worker should only start once the GCS server is ready.
// only inject init container only when ENABLE_INIT_CONTAINER_INJECTION is true
enableInitContainerInjection := getEnableInitContainerInjection()
Expand Down
Loading
Loading