def _check_tmp_free_space() -> CheckLevels: """ Warn if there is not enough free space in the default temporary directory. """ free_space = shutil.disk_usage(gettempdir()).free free_space_gb = free_space / 1024 / 1024 / 1024 low_space_message = ( 'The default temporary directory ("{tmp_prefix}") has ' '{free_space:.1f} GB of free space available. ' 'Creating a cluster typically takes approximately 2 GB of temporary ' 'storage. ' 'If you encounter problems with disk space usage, set the ``TMPDIR`` ' 'environment variable to a suitable temporary directory or use the ' '``--workspace-dir`` option on the ``dcos-docker create`` command.' ).format( tmp_prefix=Path('/') / gettempprefix(), free_space=free_space_gb, ) if free_space_gb < 5: warn(message=low_space_message) return CheckLevels.WARNING return CheckLevels.NONE
def _check_storage_driver() -> CheckLevels: """ Warn if the Docker storage driver is not a recommended driver. """ client = docker_client() host_driver = client.info()['Driver'] storage_driver_url = ( 'https://docs.docker.com/storage/storagedriver/select-storage-driver/') # Any image will do, we use this for another test so using it here saves # pulling another image. tiny_image = 'luca3m/sleep' container = client.containers.run( image=tiny_image, tty=True, detach=True, privileged=True, volumes={'/proc': { 'bind': '/host/proc', 'mode': 'rw', }}, ) cmd = ['cat', '/host/proc/filesystems'] _, output = container.exec_run(cmd=cmd) container.stop() container.remove(v=True) aufs_supported = bool(b'aufs' in output.split()) supported_host_driver = bool(host_driver in DOCKER_STORAGE_DRIVERS) can_work = bool(aufs_supported or supported_host_driver) if not can_work: message = ( "The host's Docker storage driver is \"{host_driver}\". " 'aufs is not available. ' 'Change your storage driver to one of: {supported_drivers}. ' 'Alternatively try using the `--docker-storage-driver` option ' 'with `overlay` or `overlay2`. ' 'See {help_url}.').format( host_driver=host_driver, supported_drivers=', '.join( sorted(DOCKER_STORAGE_DRIVERS.keys())), help_url=storage_driver_url, ) error(message=message) return CheckLevels.ERROR if not supported_host_driver: message = ("The host's Docker storage driver is \"{host_driver}\". " 'We recommend that you use one of: {supported_drivers}. ' 'See {help_url}.').format( host_driver=host_driver, supported_drivers=', '.join( sorted(DOCKER_STORAGE_DRIVERS.keys())), help_url=storage_driver_url, ) warn(message=message) return CheckLevels.WARNING return CheckLevels.NONE
def _check_can_mount_in_docker() -> CheckLevels: """ Check for an incompatibility between some systemd versions and some versions of Docker. """ docker_client() cluster_backend = Docker(docker_version=DockerVersion.v1_13_1) args = ['docker', 'run', '-v', '/foo', 'alpine'] error_message_substring = 'no subsystem for mount' with Cluster(cluster_backend=cluster_backend) as cluster: (public_agent, ) = cluster.public_agents try: public_agent.run(args=args) except subprocess.CalledProcessError as exc: if error_message_substring not in exc.stderr.decode(): raise message = ( 'An issue has been detected which means that, for some ' 'versions of Docker inside DC/OS nodes, it will not be ' 'possible to create containers with mounts. ' 'Some functionality may be affected by this, for example ' 'extracting the DC/OS installer on a node.' '\n' 'This issue is likely because the host\'s version of systemd ' 'is greater than version 232, which causes the following ' 'known issue: ' 'https://github.com/opencontainers/runc/issues/1175.' '\n' 'Newer versions of Docker, work well with new versions of ' 'systemd. ' 'To avoid issues caused by this incompatibility, do one of ' 'the following:' '\n* Set ``systemd.legacy_systemd_cgroup_controller=yes`` as ' 'a kernel parameter on your host.' '\n* Use versions of Docker newer than 1.13.1 inside the ' 'DC/OS nodes.' ' To do this in the ``dcos-docker`` CLI, use the ' '``--docker-version`` option on ``dcos-docker create``.' ' To do this in the Python library, pass a ' '``docker_version`` parameter to the ``Docker`` backend class.' ) warn(message=message) return CheckLevels.WARNING return CheckLevels.NONE
def check_docker() -> CheckLevels: """ Error if Docker is not running. """ try: docker.from_env(version='auto') except docker.errors.DockerException: message = ( 'Docker is not running. ' 'Docker is required for the "create" command to determine the ' 'DC/OS variant of the given DC/OS artifact. ' 'Use the "--variant" option when using the "create" command or ' 'install and run Docker.' ) warn(message=message) return CheckLevels.WARNING return CheckLevels.NONE
def _check_networking() -> CheckLevels: """ Error if the Docker network is not set up correctly. """ highest_level = CheckLevels.NONE # Image for a container which sleeps for a long time. tiny_image = 'luca3m/sleep' client = docker_client() docker_for_mac = bool(client.info()['OperatingSystem'] == 'Docker for Mac') ping_container = client.containers.run( image=tiny_image, tty=True, detach=True, ) ping_container.reload() ip_address = ping_container.attrs['NetworkSettings']['IPAddress'] try: subprocess.check_call( args=['ping', ip_address, '-c', '1', '-t', '1'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) except subprocess.CalledProcessError: message = ( 'Cannot connect to a Docker container by its IP address. ' 'This is needed for features such as connecting to the web UI and ' 'using the DC/OS CLI. ' 'To use the "wait" command without resolving this issue, use the ' '"--skip-http-checks" flag on the "wait" command.' ) if docker_for_mac: message += ( ' ' 'We recommend using "dcos-docker setup-mac-network" to ' 'resolve this issue.' ) warn(message=message) highest_level = CheckLevels.WARNING ping_container.stop() ping_container.remove(v=True) return highest_level
def _check_docker_root_free_space() -> CheckLevels: """ Warn if there is not enough free space in the Docker root directory. """ # Any image will do, we use this for another test so using it here saves # pulling another image. tiny_image = 'luca3m/sleep' client = docker_client() container = client.containers.run( image=tiny_image, tty=True, detach=True, privileged=True, ) cmd = ['df', '/'] _, output = container.exec_run(cmd=cmd) container.stop() container.remove(v=True) output_lines = output.decode().strip().split('\n') # We skip the first line which is headers. # Sometimes the information is split across multiple lines. information = ' '.join(line for line in output_lines[1:]) _, _, _, avail, _, _ = information.split() available_bytes = int(avail) available_gigabytes = available_bytes / 1024 / 1024 low_space_message = ( 'The Docker root directory is at "{docker_root_dir}". ' 'On macOS this location is on a hidden virtual machine. ' 'This directory has {free_space:.1f} GB of free space available. ' 'If you encounter problems try running ``docker volume prune``.' ).format( docker_root_dir=client.info()['DockerRootDir'], free_space=available_gigabytes, ) # The choice of 5 GB is arbitrary. Let's see how it goes in practice and # potentially adjust later. if available_gigabytes < 5: warn(message=low_space_message) return CheckLevels.WARNING return CheckLevels.NONE