Future<Option<ContainerLaunchInfo>> PosixFilesystemIsolatorProcess::prepare( const ContainerID& containerId, const ContainerConfig& containerConfig) { if (infos.contains(containerId)) { return Failure("Container has already been prepared"); } const ExecutorInfo& executorInfo = containerConfig.executor_info(); if (executorInfo.has_container()) { CHECK_EQ(executorInfo.container().type(), ContainerInfo::MESOS); // Return failure if the container change the filesystem root // because the symlinks will become invalid in the new root. if (executorInfo.container().mesos().has_image()) { return Failure("Container root filesystems not supported"); } if (executorInfo.container().volumes().size() > 0) { return Failure("Volumes in ContainerInfo is not supported"); } } infos.put(containerId, Owned<Info>(new Info(containerConfig.directory()))); return update(containerId, executorInfo.resources()) .then([]() -> Future<Option<ContainerLaunchInfo>> { return None(); }); }
Future<Option<ContainerLaunchInfo>> LinuxFilesystemIsolatorProcess::prepare( const ContainerID& containerId, const ContainerConfig& containerConfig) { const string& directory = containerConfig.directory(); Option<string> user; if (containerConfig.has_user()) { user = containerConfig.user(); } if (infos.contains(containerId)) { return Failure("Container has already been prepared"); } Owned<Info> info(new Info( directory, containerConfig.executor_info())); infos.put(containerId, info); ContainerLaunchInfo launchInfo; launchInfo.set_namespaces(CLONE_NEWNS); // Prepare the commands that will be run in the container's mount // namespace right after forking the executor process. We use these // commands to mount those volumes specified in the container info // so that they don't pollute the host mount namespace. Try<string> _script = script(containerId, containerConfig); if (_script.isError()) { return Failure("Failed to generate isolation script: " + _script.error()); } CommandInfo* command = launchInfo.add_commands(); command->set_value(_script.get()); return update(containerId, containerConfig.executor_info().resources()) .then([launchInfo]() -> Future<Option<ContainerLaunchInfo>> { return launchInfo; }); }
Try<string> LinuxFilesystemIsolatorProcess::script( const ContainerID& containerId, const ContainerConfig& containerConfig) { ostringstream out; out << "#!/bin/sh\n"; out << "set -x -e\n"; // Make sure mounts in the container mount namespace do not // propagate back to the host mount namespace. // NOTE: We cannot simply run `mount --make-rslave /`, for more info // please refer to comments in mount.hpp. MesosContainerizerMount::Flags mountFlags; mountFlags.operation = MesosContainerizerMount::MAKE_RSLAVE; mountFlags.path = "/"; out << path::join(flags.launcher_dir, "mesos-containerizer") << " " << MesosContainerizerMount::NAME << " " << stringify(mountFlags) << "\n"; if (!containerConfig.executor_info().has_container()) { return out.str(); } // Bind mount the sandbox if the container specifies a rootfs. if (containerConfig.has_rootfs()) { string sandbox = path::join( containerConfig.rootfs(), flags.sandbox_directory); Try<Nothing> mkdir = os::mkdir(sandbox); if (mkdir.isError()) { return Error( "Failed to create sandbox mount point at '" + sandbox + "': " + mkdir.error()); } out << "mount -n --rbind '" << containerConfig.directory() << "' '" << sandbox << "'\n"; } foreach (const Volume& volume, containerConfig.executor_info().container().volumes()) { // NOTE: Volumes with source will be handled by the corresponding // isolators (e.g., docker/volume). if (volume.has_source()) { VLOG(1) << "Ignored a volume with source for container '" << containerId << "'"; continue; } if (!volume.has_host_path()) { return Error("A volume misses 'host_path'"); } // If both 'host_path' and 'container_path' are relative paths, // return an error because the user can just directly access the // volume in the work directory. if (!strings::startsWith(volume.host_path(), "/") && !strings::startsWith(volume.container_path(), "/")) { return Error( "Both 'host_path' and 'container_path' of a volume are relative"); } // Determine the source of the mount. string source; if (strings::startsWith(volume.host_path(), "/")) { source = volume.host_path(); // An absolute path must already exist. if (!os::exists(source)) { return Error("Absolute host path does not exist"); } } else { // Path is interpreted as relative to the work directory. source = path::join(containerConfig.directory(), volume.host_path()); // TODO(jieyu): We need to check that source resolves under the // work directory because a user can potentially use a container // path like '../../abc'. Try<Nothing> mkdir = os::mkdir(source); if (mkdir.isError()) { return Error( "Failed to create the source of the mount at '" + source + "': " + mkdir.error()); } // TODO(idownes): Consider setting ownership and mode. } // Determine the target of the mount. string target; if (strings::startsWith(volume.container_path(), "/")) { if (containerConfig.has_rootfs()) { target = path::join( containerConfig.rootfs(), volume.container_path()); Try<Nothing> mkdir = os::mkdir(target); if (mkdir.isError()) { return Error( "Failed to create the target of the mount at '" + target + "': " + mkdir.error()); } } else { target = volume.container_path(); // An absolute path must already exist. This is because we // want to avoid creating mount points outside the work // directory in the host filesystem. if (!os::exists(target)) { return Error("Absolute container path does not exist"); } } // TODO(jieyu): We need to check that target resolves under // 'rootfs' because a user can potentially use a container path // like '/../../abc'. } else { if (containerConfig.has_rootfs()) { target = path::join(containerConfig.rootfs(), flags.sandbox_directory, volume.container_path()); } else { target = path::join(containerConfig.directory(), volume.container_path()); } // TODO(jieyu): We need to check that target resolves under the // sandbox because a user can potentially use a container path // like '../../abc'. // NOTE: We cannot create the mount point at 'target' if // container has rootfs defined. The bind mount of the sandbox // will hide what's inside 'target'. So we should always create // the mount point in 'directory'. string mountPoint = path::join( containerConfig.directory(), volume.container_path()); Try<Nothing> mkdir = os::mkdir(mountPoint); if (mkdir.isError()) { return Error( "Failed to create the target of the mount at '" + mountPoint + "': " + mkdir.error()); } } // TODO(jieyu): Consider the mode in the volume. out << "mount -n --rbind '" << source << "' '" << target << "'\n"; } return out.str(); }