Skip to content

Commit 96ae592

Browse files
authored
feat(kuberay): support resolve_hostname hook in PipesKubeRayJobClient (#340)
1 parent a6a20a5 commit 96ae592

4 files changed

Lines changed: 88 additions & 35 deletions

File tree

.zed/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"languages": {
3+
"Python": {
4+
"language_servers": ["basedpyright", "ruff", "!pyrefly", "!ty", "!pyright", "!pylsp"]
5+
}
6+
}
7+
}

src/dagster_ray/core/pipes.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,10 @@ class PipesRayJobClient(dg.PipesClient, TreatAsResourceParam):
227227
Args:
228228
address: Ray dashboard HTTP address.
229229
If unspecified, connects to a local Ray cluster or uses the ``RAY_ADDRESS`` environment variable.
230-
headers: HTTP headers for dashboard requests, e.g. ``{"Authorization": "Bearer token"}``.
231-
verify: TLS certificate verification. ``True`` uses system certs, ``False`` disables
232-
verification, or a path to a CA bundle.
233-
cookies: Cookies to send with dashboard requests.
234-
metadata: Arbitrary metadata stored alongside all submitted jobs.
230+
headers: HTTP headers for Ray Dashboard requests. Passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient].
231+
verify: Whether to verify TLS certificate. Passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient].
232+
cookies: HTTP cookies for Ray Dashboard requests. Passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient].
233+
metadata: Ray Job metadata. Passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient].
235234
context_injector: A context injector to use to inject
236235
context into the Ray job. Defaults to [`PipesEnvContextInjector`][dagster.PipesEnvContextInjector].
237236
message_reader: A message reader to use when reading Pipes messages.
@@ -289,9 +288,9 @@ def run( # type: ignore
289288
Execute a RayJob, enriched with the Pipes protocol.
290289
291290
Args:
292-
context (OpExecutionContext): Current Dagster op or asset context.
293-
submit_job_params (Dict[str, Any]): Parameters for [`JobSubmissionClient.submit_job`][ray.job_submission.JobSubmissionClient.submit_job].
294-
extras (Optional[Dict[str, Any]]): Additional information to pass to the Pipes session.
291+
context: Current Dagster op or asset context.
292+
submit_job_params: Parameters for [`JobSubmissionClient.submit_job`][ray.job_submission.JobSubmissionClient.submit_job].
293+
extras: Additional information to pass to the Pipes session, retrievable via [`PipesContext.get_extras`](https://docs.dagster.io/integrations/libraries/pipes/dagster-pipes#dagster_pipes.PipesContext.get_extra).
295294
"""
296295

297296
with open_pipes_session(

src/dagster_ray/kuberay/pipes.py

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
)
2727
from dagster_ray.kuberay.client import RayJobClient
2828
from dagster_ray.kuberay.client.rayjob.client import RayJobStatus
29-
from dagster_ray.kuberay.utils import normalize_k8s_label_values
29+
from dagster_ray.kuberay.utils import k8s_service_fqdn, normalize_k8s_label_values
3030
from dagster_ray.types import OpOrAssetExecutionContext
3131

3232
if TYPE_CHECKING:
@@ -36,6 +36,8 @@
3636
class PipesKubeRayJobClient(dg.PipesClient, TreatAsResourceParam):
3737
"""A pipes client for running ``RayJob`` on Kubernetes.
3838
39+
Most constructor arguments can be overridden in the [`run`][dagster_ray.kuberay.pipes.PipesKubeRayJobClient.run] method.
40+
3941
Args:
4042
context_injector: A context injector to use to inject
4143
context into the ``RayJob``. Defaults to [`PipesEnvContextInjector`][dagster.PipesEnvContextInjector].
@@ -48,18 +50,12 @@ class PipesKubeRayJobClient(dg.PipesClient, TreatAsResourceParam):
4850
poll_interval: Interval at which to poll Kubernetes for status updates.
4951
port_forward: Whether to use Kubernetes port-forwarding to connect to the KubeRay cluster.
5052
Is useful when running in a local environment.
51-
address: Ray dashboard address (e.g., "https://ray-cluster.example.com").
52-
When provided, connects directly to this address instead of using port-forwarding or in-cluster service IPs.
53-
Can be overridden per-job in the `run()` method.
54-
headers: HTTP headers for dashboard requests, e.g. ``{"Authorization": "Bearer token"}``.
55-
Can be overridden per-job in the `run()` method.
56-
verify: TLS certificate verification. ``True`` uses system certs, ``False`` disables
57-
verification, or a path to a CA bundle.
58-
Can be overridden per-job in the `run()` method.
59-
cookies: Arbitrary metadata stored alongside all submitted jobs.
60-
Can be overridden per-job in the `run()` method.
61-
metadata: Arbitrary metadata to store along with all jobs.
62-
Will be merged with per-job metadata. Can be overridden per-job in the `run()` method.
53+
address: Ray dashboard address. Passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient].
54+
Generated by [`resolve_hostname`][dagster_ray.kuberay.pipes.PipesKubeRayJobClient.resolve_hostname] if not provided.
55+
headers: HTTP headers for Ray Dashboard requests. Passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient].
56+
verify: Whether to verify TLS certificate. Passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient].
57+
cookies: HTTP cookies for Ray Dashboard requests. Passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient].
58+
metadata: Ray Job metadata. Passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient].
6359
6460
Info:
6561
Image defaults to `dagster/image` run tag.
@@ -130,15 +126,18 @@ def run( # type: ignore
130126
"""
131127
Execute a RayJob, enriched with the Pipes protocol.
132128
129+
Parameters passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient] take precedence over the constructor arguments.
130+
133131
Args:
134132
context: Current Dagster op or asset context.
135-
ray_job: RayJob specification. `API reference <https://ray-project.github.io/kuberay/reference/api/#rayjob>`_.
136-
extras: Additional information to pass to the Pipes session.
137-
address: Ray dashboard address override for this specific job.
138-
headers: HTTP headers override for this specific job.
139-
verify: TLS verification override for this specific job.
140-
cookies: Cookies override for this specific job.
141-
metadata: Metadata override for this specific job.
133+
ray_job: `RayJob` specification. See [API reference](https://ray-project.github.io/kuberay/reference/api/#rayjob).
134+
extras: Additional information to pass to the Pipes session, retrievable via [`PipesContext.get_extras`](https://docs.dagster.io/integrations/libraries/pipes/dagster-pipes#dagster_pipes.PipesContext.get_extra).
135+
address: Ray dashboard address. Passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient].
136+
Generated by [`resolve_hostname`][dagster_ray.kuberay.pipes.PipesKubeRayJobClient.resolve_hostname] if not provided.
137+
headers: HTTP headers for Ray Dashboard requests. Passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient].
138+
verify: Whether to verify TLS certificate. Passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient].
139+
cookies: HTTP cookies for Ray Dashboard requests. Passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient].
140+
metadata: Ray Job metadata. Passed to [`JobSubmissionClient`][ray.job_submission.JobSubmissionClient].
142141
"""
143142
with open_pipes_session(
144143
context=context,
@@ -154,13 +153,24 @@ def run( # type: ignore
154153
name = ray_job["metadata"]["name"]
155154
namespace = ray_job["metadata"]["namespace"]
156155

156+
cluster_name = self.client.get_ray_cluster_name(
157+
name=name, namespace=namespace, timeout=self.timeout, poll_interval=self.poll_interval
158+
)
159+
160+
resolved_address = address or self.address
161+
if resolved_address is None and not self.port_forward:
162+
cluster_status = self.client.ray_cluster_client.get_status(
163+
name=cluster_name, namespace=namespace, timeout=self.timeout, poll_interval=self.poll_interval
164+
)
165+
service_name = cluster_status["head"]["serviceName"] # type: ignore
166+
dashboard_port = int(cluster_status["endpoints"]["dashboard"]) # type: ignore
167+
resolved_address = self.resolve_hostname(service_name, namespace, dashboard_port)
168+
157169
with self.client.ray_cluster_client.job_submission_client(
158-
name=self.client.get_ray_cluster_name(
159-
name=name, namespace=namespace, timeout=self.timeout, poll_interval=self.poll_interval
160-
),
170+
name=cluster_name,
161171
namespace=namespace,
162172
port_forward=self.port_forward,
163-
address=address or self.address,
173+
address=resolved_address,
164174
headers=headers or self.headers,
165175
verify=verify if verify is not None else self.verify,
166176
cookies=cookies or self.cookies,
@@ -203,14 +213,41 @@ def run( # type: ignore
203213
self._terminate(
204214
context,
205215
start_response,
206-
address=address,
216+
address=resolved_address,
207217
headers=headers,
208218
verify=verify,
209219
cookies=cookies,
210220
metadata=metadata,
211221
)
212222
raise
213223

224+
def resolve_hostname(self, service_name: str, namespace: str, dashboard_port: int) -> str:
225+
"""Resolve the Ray dashboard address from a Kubernetes service name, namespace, and port.
226+
227+
By default, returns an HTTP URL using the in-cluster FQDN
228+
(e.g. `http://my-svc.namespace.svc.cluster.local:8265`).
229+
230+
Override this method to customize the address, for example to use a proxy
231+
or a different protocol. The `dashboard_port` can be ignored if not needed.
232+
233+
Args:
234+
service_name: The Kubernetes service name for the Ray head node.
235+
namespace: The Kubernetes namespace.
236+
dashboard_port: The Ray dashboard port reported by the KubeRay operator.
237+
238+
Returns:
239+
The full Ray dashboard URL (including protocol and optional port).
240+
241+
Example:
242+
```python
243+
class MyPipesKubeRayJobClient(PipesKubeRayJobClient):
244+
def resolve_hostname(self, service_name: str, namespace: str, dashboard_port: int) -> str:
245+
return f"https://{service_name}.{namespace}.company.com"
246+
```
247+
"""
248+
hostname = k8s_service_fqdn(service_name, namespace)
249+
return f"http://{hostname}:{dashboard_port}"
250+
214251
def get_dagster_tags(self, context: OpOrAssetExecutionContext) -> dict[str, str]:
215252
tags = get_dagster_tags(context)
216253
return tags
@@ -320,7 +357,7 @@ def _terminate(
320357
name=name,
321358
namespace=namespace,
322359
port_forward=self.port_forward,
323-
address=address or self.address,
360+
address=address,
324361
headers=headers or self.headers,
325362
verify=verify if verify is not None else self.verify,
326363
cookies=cookies or self.cookies,

src/dagster_ray/kuberay/resources/base.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,17 @@ def resolve_hostname(self, service_name: str, namespace: str) -> str:
3535
"""Resolve the host address from a Kubernetes service name and namespace.
3636
3737
By default, returns the in-cluster FQDN (e.g. `my-svc.namespace.svc.cluster.local`).
38-
Override this method to customize the host address, for example to use a custom domain:
38+
Override this method to customize the host address, for example to use a custom domain.
39+
40+
The returned hostname is used to construct both the Ray Client address (`ray://<host>:<redis_port>`)
41+
and the dashboard URL (`http://<host>:<dashboard_port>`).
42+
43+
Args:
44+
service_name: The Kubernetes service name for the Ray head node.
45+
namespace: The Kubernetes namespace.
46+
47+
Returns:
48+
The hostname to use for connecting to the Ray cluster.
3949
4050
Example:
4151
```python

0 commit comments

Comments
 (0)