[docker] Use tmp paths for rsync and fix file_mounts on docker (#10368)

This commit is contained in:
Ian Rodney
2020-09-01 13:14:35 -07:00
committed by GitHub
parent 52a5ec99d0
commit 283f4d1060
7 changed files with 215 additions and 92 deletions
+62 -37
View File
@@ -1,19 +1,20 @@
from getpass import getuser
from shlex import quote
from typing import List, Tuple, Dict
from typing import Any, List, Tuple, Dict, Optional
import click
import hashlib
import json
import logging
import os
import subprocess
import sys
import time
import json
from ray.autoscaler.docker import check_docker_running_cmd, \
from ray.autoscaler.docker import check_bind_mounts_cmd, \
check_docker_running_cmd, \
check_docker_image, \
docker_autoscaler_setup, \
docker_start_cmds, \
DOCKER_MOUNT_PREFIX, \
with_docker_exec
from ray.autoscaler.log_timer import LogTimer
@@ -134,7 +135,10 @@ class CommandRunnerInterface:
"""
raise NotImplementedError
def run_rsync_up(self, source: str, target: str) -> None:
def run_rsync_up(self,
source: str,
target: str,
options: Optional[Dict[str, Any]] = None) -> None:
"""Rsync files up to the cluster node.
Args:
@@ -143,7 +147,10 @@ class CommandRunnerInterface:
"""
raise NotImplementedError
def run_rsync_down(self, source: str, target: str) -> None:
def run_rsync_down(self,
source: str,
target: str,
options: Optional[Dict[str, Any]] = None) -> None:
"""Rsync files down from the cluster node.
Args:
@@ -231,7 +238,7 @@ class KubernetesCommandRunner(CommandRunnerInterface):
else:
raise
def run_rsync_up(self, source, target):
def run_rsync_up(self, source, target, options=None):
if target.startswith("~"):
target = "/root" + target[1:]
@@ -257,7 +264,7 @@ class KubernetesCommandRunner(CommandRunnerInterface):
target)
])
def run_rsync_down(self, source, target):
def run_rsync_down(self, source, target, options=None):
if target.startswith("~"):
target = "/root" + target[1:]
@@ -531,7 +538,7 @@ class SSHCommandRunner(CommandRunnerInterface):
else:
return self._run_helper(final_cmd, with_output, exit_on_fail)
def run_rsync_up(self, source, target):
def run_rsync_up(self, source, target, options=None):
self._set_ssh_ip_if_required()
command = [
"rsync", "--rsh",
@@ -543,7 +550,7 @@ class SSHCommandRunner(CommandRunnerInterface):
cli_logger.verbose("Running `{}`", cf.bold(" ".join(command)))
self._run_helper(command, silent=is_rsync_silent())
def run_rsync_down(self, source, target):
def run_rsync_down(self, source, target, options=None):
self._set_ssh_ip_if_required()
command = [
@@ -609,37 +616,41 @@ class DockerCommandRunner(CommandRunnerInterface):
with_output=with_output,
ssh_options_override_ssh_key=ssh_options_override_ssh_key)
def run_rsync_up(self, source, target):
# TODO(ilr) Expose this to before NodeUpdater::sync_file_mounts
protected_path = target
if target.find("/root") == 0:
target = target.replace("/root", "/tmp/root")
def run_rsync_up(self, source, target, options=None):
options = options or {}
host_destination = os.path.join(DOCKER_MOUNT_PREFIX,
target.lstrip("/"))
self.ssh_command_runner.run(
f"mkdir -p {os.path.dirname(target.rstrip('/'))}")
self.ssh_command_runner.run_rsync_up(source, target)
if self._check_container_status():
f"mkdir -p {os.path.dirname(host_destination.rstrip('/'))}")
self.ssh_command_runner.run_rsync_up(
source, host_destination, options=None)
if self._check_container_status() and not options.get(
"file_mount", False):
if os.path.isdir(source):
# Adding a "." means that docker copies the *contents*
# Without it, docker copies the source *into* the target
target += "/."
host_destination += "/."
self.ssh_command_runner.run("docker cp {} {}:{}".format(
target, self.container_name,
self._docker_expand_user(protected_path)))
host_destination, self.container_name,
self._docker_expand_user(target)))
def run_rsync_down(self, source, target):
protected_path = source
if source.find("/root") == 0:
source = source.replace("/root", "/tmp/root")
def run_rsync_down(self, source, target, options=None):
options = options or {}
host_source = os.path.join(DOCKER_MOUNT_PREFIX, source.lstrip("/"))
self.ssh_command_runner.run(
f"mkdir -p {os.path.dirname(source.rstrip('/'))}")
if protected_path[-1] == "/":
protected_path += "."
f"mkdir -p {os.path.dirname(host_source.rstrip('/'))}")
if source[-1] == "/":
source += "."
# Adding a "." means that docker copies the *contents*
# Without it, docker copies the source *into* the target
self.ssh_command_runner.run("docker cp {}:{} {}".format(
self.container_name, self._docker_expand_user(protected_path),
source))
self.ssh_command_runner.run_rsync_down(source, target)
if not options.get("file_mount", False):
self.ssh_command_runner.run("docker cp {}:{} {}".format(
self.container_name, self._docker_expand_user(source),
host_source))
self.ssh_command_runner.run_rsync_down(
host_source, target, options=None)
def remote_shell_command_str(self):
inner_str = self.ssh_command_runner.remote_shell_command_str().replace(
@@ -722,9 +733,23 @@ class DockerCommandRunner(CommandRunnerInterface):
logger.error(f"A container with name {self.container_name} " +
f"is running image {running_image} instead " +
f"of {image} (which was provided in the YAML")
# Copy bootstrap config & key over
if as_head:
for copy_cmd in docker_autoscaler_setup(self.container_name):
self.run(copy_cmd, run_env="host")
mounts = self.run(
check_bind_mounts_cmd(self.container_name),
with_output=True,
run_env="host").decode("utf-8").strip()
try:
active_mounts = json.loads(mounts)
active_remote_mounts = [
mnt["Destination"] for mnt in active_mounts
]
for remote, local in file_mounts.items():
remote = self._docker_expand_user(remote)
if remote not in active_remote_mounts:
cli_logger.error(
"Please ray stop & restart cluster to "
f"allow mount {remote}:{local} to take hold")
except json.JSONDecodeError:
cli_logger.verbose(
"Unable to check if file_mounts specified in the YAML "
"differ from those on the running container.")
self.initialized = True
+4 -4
View File
@@ -664,13 +664,13 @@ def get_or_create_head_node(config,
cli_logger.print("Prepared bootstrap config")
if restart_only:
init_commands = []
setup_commands = []
ray_start_commands = config["head_start_ray_commands"]
elif no_restart:
init_commands = config["head_setup_commands"]
setup_commands = config["head_setup_commands"]
ray_start_commands = []
else:
init_commands = config["head_setup_commands"]
setup_commands = config["head_setup_commands"]
ray_start_commands = config["head_start_ray_commands"]
if not no_restart:
@@ -684,7 +684,7 @@ def get_or_create_head_node(config,
cluster_name=config["cluster_name"],
file_mounts=config["file_mounts"],
initialization_commands=config["initialization_commands"],
setup_commands=init_commands,
setup_commands=setup_commands,
ray_start_commands=ray_start_commands,
process_runner=_runner,
runtime_hash=runtime_hash,
+15 -21
View File
@@ -1,4 +1,3 @@
import os
import logging
try: # py3
from shlex import quote
@@ -7,6 +6,8 @@ except ImportError: # py2
logger = logging.getLogger(__name__)
DOCKER_MOUNT_PREFIX = "/tmp/ray_tmp_mount"
def validate_docker_config(config):
if "docker" not in config:
@@ -46,20 +47,27 @@ def with_docker_exec(cmds,
]
def check_docker_running_cmd(cname):
def _check_helper(cname, template):
return " ".join([
"docker", "inspect", "-f", "'{{.State.Running}}'", cname, "||", "true"
"docker", "inspect", "-f", "'{{" + template + "}}'", cname, "||",
"true"
])
def check_docker_running_cmd(cname):
return _check_helper(cname, ".State.Running")
def check_bind_mounts_cmd(cname):
return _check_helper(cname, "json .Mounts")
def check_docker_image(cname):
return " ".join([
"docker", "inspect", "-f", "'{{.Config.Image}}'", cname, "||", "true"
])
return _check_helper(cname, ".Config.Image")
def docker_start_cmds(user, image, mount_dict, cname, user_options):
mount = {dst: dst for dst in mount_dict}
mount = {f"{DOCKER_MOUNT_PREFIX}/{dst}": dst for dst in mount_dict}
# TODO(ilr) Move away from defaulting to /root/
mount_flags = " ".join([
@@ -78,17 +86,3 @@ def docker_start_cmds(user, image, mount_dict, cname, user_options):
mount_flags, env_flags, user_options_str, "--net=host", image, "bash"
]
return " ".join(docker_run)
def docker_autoscaler_setup(cname):
cmds = []
for path in ["~/ray_bootstrap_config.yaml", "~/ray_bootstrap_key.pem"]:
# needed because docker doesn't allow relative paths
base_path = os.path.basename(path)
cmds.append("docker cp {path} {cname}:{dpath}".format(
path=path, dpath=base_path, cname=cname))
cmds.extend(
with_docker_exec(
["cp {} {}".format("/" + base_path, path)],
container_name=cname))
return cmds
+14 -8
View File
@@ -181,10 +181,12 @@ class NodeUpdater:
with LogTimer(self.log_prefix +
"Synced {} to {}".format(local_path, remote_path)):
self.cmd_runner.run(
"mkdir -p {}".format(os.path.dirname(remote_path)),
run_env="host")
sync_cmd(local_path, remote_path)
if not isinstance(self.cmd_runner, DockerCommandRunner):
# The DockerCommandRunner handles this internally
self.cmd_runner.run(
"mkdir -p {}".format(os.path.dirname(remote_path)),
run_env="host")
sync_cmd(local_path, remote_path, file_mount=True)
if remote_path not in nolog_paths:
# todo: timed here?
@@ -413,19 +415,23 @@ class NodeUpdater:
raise click.ClickException("Start command failed.")
def rsync_up(self, source, target):
def rsync_up(self, source, target, file_mount=False):
cli_logger.old_info(logger, "{}Syncing {} to {}...", self.log_prefix,
source, target)
self.cmd_runner.run_rsync_up(source, target)
options = {}
options["file_mount"] = file_mount
self.cmd_runner.run_rsync_up(source, target, options=options)
cli_logger.verbose("`rsync`ed {} (local) to {} (remote)",
cf.bold(source), cf.bold(target))
def rsync_down(self, source, target):
def rsync_down(self, source, target, file_mount=False):
cli_logger.old_info(logger, "{}Syncing {} from {}...", self.log_prefix,
source, target)
self.cmd_runner.run_rsync_down(source, target)
options = {}
options["file_mount"] = file_mount
self.cmd_runner.run_rsync_down(source, target, options=options)
cli_logger.verbose("`rsync`ed {} (remote) to {} (local)",
cf.bold(source), cf.bold(target))