From eb6f403b97f6c92d688b3438e38962723ded1523 Mon Sep 17 00:00:00 2001 From: Barak Michener Date: Fri, 8 Jan 2021 15:38:36 -0800 Subject: [PATCH] [ray_client]: first draft of documentation (#13216) --- doc/source/index.rst | 1 + doc/source/ray-client.rst | 69 +++++++++++++++++++++++++ doc/source/starting-ray.rst | 2 + python/ray/util/__init__.py | 18 +++++-- python/ray/util/client/__init__.py | 2 +- python/ray/util/client/server/server.py | 14 +++-- python/ray/util/client_connect.py | 31 +++++++++++ 7 files changed, 128 insertions(+), 9 deletions(-) create mode 100644 doc/source/ray-client.rst create mode 100644 python/ray/util/client_connect.py diff --git a/doc/source/index.rst b/doc/source/index.rst index 7580c079e..9edb823b2 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -304,6 +304,7 @@ Papers xgboost-ray.rst dask-on-ray.rst mars-on-ray.rst + ray-client.rst .. toctree:: :hidden: diff --git a/doc/source/ray-client.rst b/doc/source/ray-client.rst new file mode 100644 index 000000000..acb6b11ea --- /dev/null +++ b/doc/source/ray-client.rst @@ -0,0 +1,69 @@ +********** +Ray Client +********** + +.. note:: + + This feature is still in beta and subject to changes. + +=========== +Basic usage +=========== + +While in beta, the server is available as an executable module. To start the server, run + +``python -m ray.util.client.server [--host host_ip] [--port port] [--redis-address address] [--redis-password password]`` + +This runs ``ray.init()`` with default options and exposes the client gRPC port at ``host_ip:port`` (by default, ``0.0.0.0:50051``). Providing ``redis-address`` and ``redis-password`` will be passed into ``ray.init()`` when the server starts, allowing connection to an existing Ray cluster, as per the `cluster setup `_ instructions. + +From here, another Ray script can access that server from a networked machine with ``ray.util.connect()`` + +.. code-block:: python + + import ray + import ray.util + + ray.util.connect("0.0.0.0:50051") # replace with the appropriate host and port + + # Normal Ray code follows + @ray.remote + def f(x): + return x ** x + + do_work.remote(2) + #.... + +When the client disconnects, any object or actor references held by the server on behalf of the client are dropped, as if directly disconnecting from the cluster + + +=================== +``RAY_CLIENT_MODE`` +=================== + +Because Ray client mode affects the behavior of the Ray API, larger scripts or libraries imported before ``ray.util.connect()`` may not realize they're in client mode. This feature is being tracked with `issue #13272 `_ but the workaround here is provided for beta users. + +One option is to defer the imports from a ``main`` script that calls ``ray.util.connect()`` first. However, some older scripts or libraries might not support that. + +Therefore, an environment variable is also available to force a Ray program into client mode: ``RAY_CLIENT_MODE`` An example usage: + +.. code-block:: bash + + RAY_CLIENT_MODE=1 python my_ray_program.py + + +=================================== +Programatically creating the server +=================================== + +For larger use-cases, it may be desirable to connect remote Ray clients to an existing Ray environment. The server can be started separately via + +.. code-block:: python + + from ray.util.client.server import serve + + server = serve("0.0.0.0:50051") + # Server does some work + # ... + # Time to clean up + server.stop(0) + diff --git a/doc/source/starting-ray.rst b/doc/source/starting-ray.rst index 4a198e27e..1791cc25b 100644 --- a/doc/source/starting-ray.rst +++ b/doc/source/starting-ray.rst @@ -18,6 +18,8 @@ There are three ways of starting the Ray runtime: * Explicitly via CLI (:ref:`start-ray-cli`) * Explicitly via the cluster launcher (:ref:`start-ray-up`) +You can also connect to an existing Ray runtime using the `Ray Client ` + .. _start-ray-init: Starting Ray on a single machine diff --git a/python/ray/util/__init__.py b/python/ray/util/__init__.py index 2bead8d18..252432ad8 100644 --- a/python/ray/util/__init__.py +++ b/python/ray/util/__init__.py @@ -7,8 +7,20 @@ from ray.util.placement_group import (placement_group, placement_group_table, remove_placement_group) from ray.util import rpdb as pdb +from ray.util.client_connect import connect, disconnect + __all__ = [ - "ActorPool", "disable_log_once_globally", "enable_periodic_logging", - "iter", "log_once", "pdb", "placement_group", "placement_group_table", - "remove_placement_group", "inspect_serializability", "collective" + "ActorPool", + "disable_log_once_globally", + "enable_periodic_logging", + "iter", + "log_once", + "pdb", + "placement_group", + "placement_group_table", + "remove_placement_group", + "inspect_serializability", + "collective", + "connect", + "disconnect", ] diff --git a/python/ray/util/client/__init__.py b/python/ray/util/client/__init__.py index 767ba1221..3ad5bd639 100644 --- a/python/ray/util/client/__init__.py +++ b/python/ray/util/client/__init__.py @@ -75,7 +75,7 @@ class RayAPIStub: return getattr(self.api, key) def is_connected(self) -> bool: - return self.api is not None + return self.client_worker is not None def init(self, *args, **kwargs): if self._server is not None: diff --git a/python/ray/util/client/server/server.py b/python/ray/util/client/server/server.py index 82c76a7c9..4b59f1797 100644 --- a/python/ray/util/client/server/server.py +++ b/python/ray/util/client/server/server.py @@ -427,7 +427,7 @@ def main(): "-p", "--port", type=int, default=50051, help="Port to bind to") parser.add_argument( "--redis-address", - required=True, + required=False, type=str, help="Address to use to connect to Ray") parser.add_argument( @@ -437,11 +437,15 @@ def main(): help="Password for connecting to Redis") args = parser.parse_args() logging.basicConfig(level="INFO") - if args.redis_password: - ray.init( - address=args.redis_address, _redis_password=args.redis_password) + if args.redis_address: + if args.redis_password: + ray.init( + address=args.redis_address, + _redis_password=args.redis_password) + else: + ray.init(address=args.redis_address) else: - ray.init(address=args.redis_address) + ray.init() hostport = "%s:%d" % (args.host, args.port) logger.info(f"Starting Ray Client server on {hostport}") server = serve(hostport) diff --git a/python/ray/util/client_connect.py b/python/ray/util/client_connect.py new file mode 100644 index 000000000..e8525e17d --- /dev/null +++ b/python/ray/util/client_connect.py @@ -0,0 +1,31 @@ +from ray.util.client import ray + +from ray._private.client_mode_hook import _enable_client_hook +from ray._private.client_mode_hook import _explicitly_enable_client_mode + +from typing import List +from typing import Tuple + + +def connect(conn_str: str, + secure: bool = False, + metadata: List[Tuple[str, str]] = None) -> None: + if ray.is_connected(): + raise RuntimeError("Ray Client is already connected. " + "Maybe you called ray.util.connect twice by " + "accident?") + # Enable the same hooks that RAY_CLIENT_MODE does, as + # calling ray.util.connect() is specifically for using client mode. + _enable_client_hook(True) + _explicitly_enable_client_mode() + + # TODO(barakmich): https://github.com/ray-project/ray/issues/13274 + # for supporting things like cert_path, ca_path, etc and creating + # the correct metadata + return ray.connect(conn_str, secure=secure, metadata=metadata) + + +def disconnect(): + if not ray.is_connected(): + raise RuntimeError("Ray Client is currently disconnected.") + return ray.disconnect()