mirror of
https://github.com/wassname/ray.git
synced 2026-07-07 22:06:19 +08:00
bdf647c4ec
* Add DockerSyncer * Add docs * Update python/ray/tune/integration/docker.py Co-authored-by: Richard Liaw <rliaw@berkeley.edu> * Updated docs * fix dir * Added docker integration test * added docker integration test to bazel build * Use sdk.rsync API Co-authored-by: Richard Liaw <rliaw@berkeley.edu>
158 lines
5.5 KiB
Python
158 lines
5.5 KiB
Python
import os
|
|
from typing import Any, Optional, Tuple
|
|
|
|
import kubernetes
|
|
import subprocess
|
|
|
|
from ray import services, logger
|
|
from ray.autoscaler._private.command_runner import KubernetesCommandRunner
|
|
from ray.tune.syncer import NodeSyncer
|
|
from ray.tune.sync_client import SyncClient
|
|
|
|
|
|
def NamespacedKubernetesSyncer(namespace):
|
|
"""Wrapper to return a ``KubernetesSyncer`` for a Kubernetes namespace.
|
|
|
|
Args:
|
|
namespace (str): Kubernetes namespace.
|
|
|
|
Returns: A ``KubernetesSyncer`` class to be passed to ``tune.run()``.
|
|
|
|
Example:
|
|
|
|
.. code-block:: python
|
|
|
|
from ray.tune.integration.kubernetes import NamespacedKubernetesSyncer
|
|
tune.run(train,
|
|
sync_config=tune.SyncConfig(
|
|
sync_to_driver=NamespacedKubernetesSyncer("ray")))
|
|
|
|
"""
|
|
|
|
class _NamespacedKubernetesSyncer(KubernetesSyncer):
|
|
_namespace = namespace
|
|
|
|
return _NamespacedKubernetesSyncer
|
|
|
|
|
|
class KubernetesSyncer(NodeSyncer):
|
|
"""KubernetesSyncer used for synchronization between Kubernetes pods.
|
|
|
|
This syncer extends the node syncer, but is usually instantiated
|
|
without a custom sync client. The sync client defaults to
|
|
``KubernetesSyncClient`` instead.
|
|
|
|
KubernetesSyncer uses the default namespace ``ray``. You should
|
|
probably use ``NamespacedKubernetesSyncer`` to return a class
|
|
with a custom namespace instead.
|
|
"""
|
|
|
|
_namespace = "ray"
|
|
|
|
def __init__(self,
|
|
local_dir: str,
|
|
remote_dir: str,
|
|
sync_client: Optional[SyncClient] = None):
|
|
self.local_ip = services.get_node_ip_address()
|
|
self.local_node = self._get_kubernetes_node_by_ip(self.local_ip)
|
|
self.worker_ip = None
|
|
self.worker_node = None
|
|
|
|
sync_client = sync_client or KubernetesSyncClient(
|
|
namespace=self.__class__._namespace)
|
|
|
|
super(NodeSyncer, self).__init__(local_dir, remote_dir, sync_client)
|
|
|
|
def set_worker_ip(self, worker_ip: str):
|
|
self.worker_ip = worker_ip
|
|
self.worker_node = self._get_kubernetes_node_by_ip(worker_ip)
|
|
|
|
def _get_kubernetes_node_by_ip(self, node_ip: str) -> Optional[str]:
|
|
"""Return node name by internal or external IP"""
|
|
kubernetes.config.load_incluster_config()
|
|
api = kubernetes.client.CoreV1Api()
|
|
pods = api.list_namespaced_pod(self._namespace)
|
|
for pod in pods.items:
|
|
if pod.status.host_ip == node_ip or \
|
|
pod.status.pod_ip == node_ip:
|
|
return pod.metadata.name
|
|
|
|
logger.error(
|
|
"Could not find Kubernetes pod name for IP {}".format(node_ip))
|
|
return None
|
|
|
|
@property
|
|
def _remote_path(self) -> Tuple[str, str]:
|
|
return self.worker_node, self._remote_dir
|
|
|
|
|
|
class KubernetesSyncClient(SyncClient):
|
|
"""KubernetesSyncClient to be used by KubernetesSyncer.
|
|
|
|
This client takes care of executing the synchronization
|
|
commands for Kubernetes clients. In its ``sync_down`` and
|
|
``sync_up`` commands, it expects tuples for the source
|
|
and target, respectively, for compatibility with the
|
|
KubernetesCommandRunner.
|
|
|
|
Args:
|
|
namespace (str): Namespace in which the pods live.
|
|
process_runner: How commands should be called.
|
|
Defaults to ``subprocess``.
|
|
|
|
"""
|
|
|
|
def __init__(self, namespace: str, process_runner: Any = subprocess):
|
|
self.namespace = namespace
|
|
self._process_runner = process_runner
|
|
self._command_runners = {}
|
|
|
|
def _create_command_runner(self, node_id: str) -> KubernetesCommandRunner:
|
|
"""Create a command runner for one Kubernetes node"""
|
|
return KubernetesCommandRunner(
|
|
log_prefix="KubernetesSyncClient: {}:".format(node_id),
|
|
namespace=self.namespace,
|
|
node_id=node_id,
|
|
auth_config=None,
|
|
process_runner=self._process_runner)
|
|
|
|
def _get_command_runner(self, node_id: str) -> KubernetesCommandRunner:
|
|
"""Create command runner if it doesn't exist"""
|
|
# Todo(krfricke): These cached runners are currently
|
|
# never cleaned up. They are cheap so this shouldn't
|
|
# cause much problems, but should be addressed if
|
|
# the SyncClient is used more extensively in the future.
|
|
if node_id not in self._command_runners:
|
|
command_runner = self._create_command_runner(node_id)
|
|
self._command_runners[node_id] = command_runner
|
|
return self._command_runners[node_id]
|
|
|
|
def sync_up(self, source: str, target: Tuple[str, str]) -> bool:
|
|
"""Here target is a tuple (target_node, target_dir)"""
|
|
target_node, target_dir = target
|
|
|
|
# Add trailing slashes for rsync
|
|
source = os.path.join(source, "")
|
|
target_dir = os.path.join(target_dir, "")
|
|
|
|
command_runner = self._get_command_runner(target_node)
|
|
command_runner.run_rsync_up(source, target_dir)
|
|
return True
|
|
|
|
def sync_down(self, source: Tuple[str, str], target: str) -> bool:
|
|
"""Here source is a tuple (source_node, source_dir)"""
|
|
source_node, source_dir = source
|
|
|
|
# Add trailing slashes for rsync
|
|
source_dir = os.path.join(source_dir, "")
|
|
target = os.path.join(target, "")
|
|
|
|
command_runner = self._get_command_runner(source_node)
|
|
command_runner.run_rsync_down(source_dir, target)
|
|
return True
|
|
|
|
def delete(self, target: str) -> bool:
|
|
"""No delete function because it is only used by
|
|
the KubernetesSyncer, which doesn't call delete."""
|
|
return True
|