docker.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. from pathlib import Path
  2. from typing import Any, Dict
  3. from ray.autoscaler._private.cli_logger import cli_logger
  4. try: # py3
  5. from shlex import quote
  6. except ImportError: # py2
  7. from pipes import quote
  8. def _check_docker_file_mounts(file_mounts: Dict[str, str]) -> None:
  9. """Checks if files are passed as file_mounts. This is a problem for Docker
  10. based clusters because when a file is bind-mounted in Docker, updates to
  11. the file on the host do not always propagate to the container. Using
  12. directories is recommended.
  13. """
  14. for remote, local in file_mounts.items():
  15. if Path(local).is_file():
  16. cli_logger.warning(
  17. f"File Mount: ({remote}:{local}) refers to a file.\n To ensure"
  18. " this mount updates properly, please use a directory."
  19. )
  20. def validate_docker_config(config: Dict[str, Any]) -> None:
  21. """Checks whether the Docker configuration is valid."""
  22. if "docker" not in config:
  23. return
  24. _check_docker_file_mounts(config.get("file_mounts", {}))
  25. docker_image = config["docker"].get("image")
  26. cname = config["docker"].get("container_name")
  27. head_docker_image = config["docker"].get("head_image", docker_image)
  28. worker_docker_image = config["docker"].get("worker_image", docker_image)
  29. image_present = docker_image or (head_docker_image and worker_docker_image)
  30. if (not cname) and (not image_present):
  31. return
  32. else:
  33. assert cname and image_present, "Must provide a container & image name"
  34. return None
  35. def with_docker_exec(
  36. cmds, container_name, docker_cmd, env_vars=None, with_interactive=False
  37. ):
  38. assert docker_cmd, "Must provide docker command"
  39. env_str = ""
  40. if env_vars:
  41. env_str = " ".join(["-e {env}=${env}".format(env=env) for env in env_vars])
  42. return [
  43. "{docker_cmd} exec {interactive} {env} {container} /bin/bash -c {cmd} ".format(
  44. docker_cmd=docker_cmd,
  45. interactive="-it" if with_interactive else "",
  46. env=env_str,
  47. container=container_name,
  48. cmd=quote(cmd),
  49. )
  50. for cmd in cmds
  51. ]
  52. def _check_helper(cname, template, docker_cmd):
  53. return " ".join(
  54. [docker_cmd, "inspect", "-f", "'{{" + template + "}}'", cname, "||", "true"]
  55. )
  56. def check_docker_running_cmd(cname, docker_cmd):
  57. return _check_helper(cname, ".State.Running", docker_cmd)
  58. def check_bind_mounts_cmd(cname, docker_cmd):
  59. return _check_helper(cname, "json .Mounts", docker_cmd)
  60. def check_docker_image(cname, docker_cmd):
  61. return _check_helper(cname, ".Config.Image", docker_cmd)
  62. def docker_start_cmds(
  63. user,
  64. image,
  65. mount_dict,
  66. container_name,
  67. user_options,
  68. cluster_name,
  69. home_directory,
  70. docker_cmd,
  71. ):
  72. # Imported here due to circular dependency.
  73. from ray.autoscaler.sdk import get_docker_host_mount_location
  74. docker_mount_prefix = get_docker_host_mount_location(cluster_name)
  75. mount = {f"{docker_mount_prefix}/{dst}": dst for dst in mount_dict}
  76. mount_flags = " ".join(
  77. [
  78. "-v {src}:{dest}".format(src=k, dest=v.replace("~/", home_directory + "/"))
  79. for k, v in mount.items()
  80. ]
  81. )
  82. # for click, used in ray cli
  83. env_vars = {"LC_ALL": "C.UTF-8", "LANG": "C.UTF-8"}
  84. env_flags = " ".join(
  85. ["-e {name}={val}".format(name=k, val=v) for k, v in env_vars.items()]
  86. )
  87. user_options_str = " ".join(user_options)
  88. docker_run = [
  89. docker_cmd,
  90. "run",
  91. "--rm",
  92. "--name {}".format(container_name),
  93. "-d",
  94. "-it",
  95. mount_flags,
  96. env_flags,
  97. user_options_str,
  98. "--net=host",
  99. image,
  100. "bash",
  101. ]
  102. return " ".join(docker_run)