Skip to content

Commit cbda4ef

Browse files
authored
✨ introduce failure_tolerance_timeout parameter for KubeRay resources (#247)
1 parent 0301580 commit cbda4ef

6 files changed

Lines changed: 50 additions & 8 deletions

File tree

docs/api/kuberay.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ These resources initialize Ray client connection with a remote cluster.
1515
- "lifecycle"
1616
- "ray_job"
1717
- "client"
18+
- "failure_tolerance_timeout"
1819
- "log_cluster_conditions"
1920

2021

@@ -25,6 +26,7 @@ These resources initialize Ray client connection with a remote cluster.
2526
- "lifecycle"
2627
- "ray_cluster"
2728
- "client"
29+
- "failure_tolerance_timeout"
2830
- "log_cluster_conditions"
2931

3032
---

src/dagster_ray/kuberay/client/raycluster/client.py

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,10 @@ class RayClusterEndpoints(TypedDict): # these are ports
7373

7474

7575
class RayClusterHead(TypedDict):
76-
podIP: str
77-
serviceIP: str
76+
podIP: NotRequired[str]
77+
podName: NotRequired[str]
78+
serviceIP: NotRequired[str]
79+
serviceName: NotRequired[str]
7880

7981

8082
class RayClusterStatus(TypedDict):
@@ -112,12 +114,23 @@ def wait_until_ready(
112114
name: str,
113115
namespace: str,
114116
timeout: float,
115-
image: str | None = None,
117+
failure_tolerance_timeout: float = 0.0,
116118
poll_interval: float = 5.0,
117119
log_cluster_conditions: bool = False,
118120
) -> tuple[str, RayClusterEndpoints]:
119121
"""
120122
If ready, returns service ip address and a dictionary of ports.
123+
124+
Parameters:
125+
name (str): The name of the `RayCluster` resource
126+
namespace (str): The namespace of the `RayCluster` resource
127+
timeout (float): The timeout in seconds to wait for the cluster to become ready.
128+
failure_tolerance_timeout (float): The period in seconds to wait for the cluster to transition out of `failed` state if it reaches it. This state can be transient under certain conditions. With the default value of 0, the first `failed` state appearance will raise an exception immediately.
129+
poll_interval (float): The interval in seconds to poll the cluster status.
130+
log_cluster_conditions (bool): Whether to log cluster conditions. See [KubeRay docs](https://docs.ray.io/en/latest/cluster/kubernetes/user-guides/observability.html#raycluster-status-conditions)
131+
132+
Returns:
133+
tuple[str, RayClusterEndpoints]: The service ip address and a dictionary of ports.
121134
"""
122135
start_time = time.time()
123136

@@ -150,14 +163,22 @@ def get_status_with_retry() -> RayClusterStatus:
150163
logger.info(f"RayCluster {namespace}/{name} condition: {conditions[condition_index_to_log]}")
151164
condition_index_to_log += 1
152165

153-
if state == "failed":
154-
raise Exception(
155-
f"RayCluster {namespace}/{name} failed to start. Status:\n\n{status}\n\nMore details: `kubectl -n {namespace} describe RayCluster {name}`"
166+
if state == "failed" and (time.time() - start_time > failure_tolerance_timeout):
167+
raise RuntimeError(
168+
f"RayCluster {namespace}/{name} status transitioned into `failed`. Consider increasing `failure_tolerance_timeout` ({failure_tolerance_timeout:.1f}s). Status:\n\n{status}\n\nMore details: `kubectl -n {namespace} describe RayCluster {name}`"
156169
)
157170

158-
if state == "ready" and status.get("head") and status.get("endpoints", {}).get("dashboard"):
171+
if (
172+
state == "ready"
173+
and status.get("endpoints", {}).get("dashboard")
174+
and (head := status.get("head"))
175+
and head.get("serviceIP")
176+
and head.get("serviceName")
177+
):
178+
# TODO: this should return serviceName instead
179+
# to support multi-cluster networking
159180
logger.debug(f"RayCluster {namespace}/{name} is ready!")
160-
return status["head"]["serviceIP"], status["endpoints"] # pyright: ignore[reportTypedDictNotRequiredAccess]
181+
return head["serviceIP"], status["endpoints"] # pyright: ignore[reportTypedDictNotRequiredAccess]
161182

162183
time.sleep(poll_interval)
163184
else:

src/dagster_ray/kuberay/client/rayjob/client.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,24 @@ def wait_until_ready(
7171
name: str,
7272
namespace: str,
7373
timeout: float = 600,
74+
failure_tolerance_timeout: float = 0.0,
7475
poll_interval: float = 1.0,
7576
log_cluster_conditions: bool = False,
7677
) -> tuple[str, RayClusterEndpoints]:
7778
"""Wait until the RayCluster attached to the RayJob is ready.
7879
7980
This doesn't necessarily mean that the cluster has already taken a job, just that it is ready to accept connections.
81+
82+
Parameters:
83+
name (str): The name of the `RayJob` resource
84+
namespace (str): The namespace of the `RayJob` resource
85+
timeout (float): The timeout in seconds to wait for the cluster to become ready.
86+
failure_tolerance_timeout (float): The period in seconds to wait for the cluster to transition out of `failed` state if it reaches it. This state can be transient under certain conditions. With the default value of 0, the first `failed` state appearance will raise an exception immediately.
87+
poll_interval (float): The interval in seconds to poll the cluster status.
88+
log_cluster_conditions (bool): Whether to log cluster conditions. See [KubeRay docs](https://docs.ray.io/en/latest/cluster/kubernetes/user-guides/observability.html#raycluster-status-conditions)
89+
90+
Returns:
91+
tuple[str, RayClusterEndpoints]: The service ip address and a dictionary of ports.
8092
"""
8193
ray_cluster_name = self.get_ray_cluster_name(name, namespace, timeout=timeout, poll_interval=poll_interval)
8294
ray_cluster_client = self.ray_cluster_client
@@ -87,6 +99,7 @@ def wait_until_ready(
8799
ray_cluster_name,
88100
namespace=namespace,
89101
timeout=timeout,
102+
failure_tolerance_timeout=failure_tolerance_timeout,
90103
poll_interval=poll_interval,
91104
log_cluster_conditions=log_cluster_conditions,
92105
)

src/dagster_ray/kuberay/resources/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ class BaseKubeRayResourceConfig(dg.Config):
2121
default=DEFAULT_DEPLOYMENT_NAME,
2222
description="Dagster deployment name. Is used as a prefix for the Kubernetes resource name. Dagster Cloud variables are used to determine the default value.",
2323
)
24+
failure_tolerance_timeout: float = Field(
25+
default=0.0,
26+
description="The period in seconds to wait for the cluster to transition out of `failed` state if it reaches it. This state can be transient under certain conditions. With the default value of 0, the first `failed` state appearance will raise an exception immediately.",
27+
)
2428
poll_interval: float = Field(default=1.0, description="Poll interval for various API requests")
2529

2630
_host: str = PrivateAttr()

src/dagster_ray/kuberay/resources/raycluster.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ def wait(self, context: AnyDagsterContext):
120120
self.name,
121121
namespace=self.namespace,
122122
timeout=self.timeout,
123+
failure_tolerance_timeout=self.failure_tolerance_timeout,
123124
poll_interval=self.poll_interval,
124125
log_cluster_conditions=self.log_cluster_conditions,
125126
)

src/dagster_ray/kuberay/resources/rayjob.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ def wait(self, context: AnyDagsterContext):
150150
self.client.wait_until_ready(
151151
name=self.name,
152152
namespace=self.namespace,
153+
failure_tolerance_timeout=self.failure_tolerance_timeout,
153154
log_cluster_conditions=self.log_cluster_conditions,
154155
timeout=self.timeout,
155156
poll_interval=self.poll_interval,

0 commit comments

Comments
 (0)