diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java index 04d0fd14bec..4cd4cca30d7 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java @@ -1936,6 +1936,10 @@ public class YarnConfiguration extends Configuration { public static final String NM_DOCKER_IMAGE_NAME = DOCKER_CONTAINER_RUNTIME_PREFIX + "image-name"; + /** Default option to decide whether to pull the latest image or not. **/ + public static final String NM_DOCKER_IMAGE_UPDATE = + DOCKER_CONTAINER_RUNTIME_PREFIX + "image-update"; + /** Capabilities allowed (and added by default) for docker containers. **/ public static final String NM_DOCKER_CONTAINER_CAPABILITIES = DOCKER_CONTAINER_RUNTIME_PREFIX + "capabilities"; diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml index bfcbf4b6be7..db29fb994b1 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml @@ -1761,6 +1761,13 @@ + + Default option to decide whether to pull the latest image + or not. + yarn.nodemanager.runtime.linux.docker.image-update + false + + This configuration setting determines if privileged docker containers are allowed on this cluster. Privileged containers are granted diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/DockerLinuxContainerRuntime.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/DockerLinuxContainerRuntime.java index 7fc386d9120..f1da846af63 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/DockerLinuxContainerRuntime.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/DockerLinuxContainerRuntime.java @@ -33,6 +33,7 @@ import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime. import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerCommandExecutor; import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerExecCommand; import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerKillCommand; +import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerPullCommand; import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerRmCommand; import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerStartCommand; import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerVolumeCommand; @@ -272,6 +273,7 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime { private Map csiClients = new HashMap<>(); private PrivilegedOperationExecutor privilegedOperationExecutor; private String defaultImageName; + private Boolean defaultImageUpdate; private Set allowedNetworks = new HashSet<>(); private String defaultNetwork; private CGroupsHandler cGroupsHandler; @@ -352,6 +354,8 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime { defaultTmpfsMounts.clear(); defaultImageName = conf.getTrimmed( YarnConfiguration.NM_DOCKER_IMAGE_NAME, ""); + defaultImageUpdate = conf.getBoolean( + YarnConfiguration.NM_DOCKER_IMAGE_UPDATE, false); allowedNetworks.addAll(Arrays.asList( conf.getTrimmedStrings( YarnConfiguration.NM_DOCKER_ALLOWED_CONTAINER_NETWORKS, @@ -802,6 +806,7 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime { throws ContainerExecutionException { Container container = ctx.getContainer(); ContainerId containerId = container.getContainerId(); + String containerIdStr = containerId.toString(); Map environment = container.getLaunchContext() .getEnvironment(); String imageName = environment.get(ENV_DOCKER_CONTAINER_IMAGE); @@ -822,7 +827,10 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime { validateImageName(imageName); - String containerIdStr = containerId.toString(); + if (defaultImageUpdate) { + pullImageFromRemote(containerIdStr, imageName); + } + String runAsUser = ctx.getExecutionAttribute(RUN_AS_USER); String dockerRunAsUser = runAsUser; Path containerWorkDir = ctx.getExecutionAttribute(CONTAINER_WORK_DIR); @@ -1379,6 +1387,25 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime { } } + public void pullImageFromRemote(String containerIdStr, String imageName) + throws ContainerExecutionException { + long start = System.currentTimeMillis(); + DockerPullCommand dockerPullCommand = new DockerPullCommand(imageName); + LOG.debug("now pulling docker image." + " image name: " + imageName + "," + + " container: " + containerIdStr); + + DockerCommandExecutor.executeDockerCommand(dockerPullCommand, + containerIdStr, null, + privilegedOperationExecutor, false, nmContext); + + long end = System.currentTimeMillis(); + long pullImageTimeMs = end - start; + LOG.debug("pull docker image done with " + + String.valueOf(pullImageTimeMs) + "ms spent." + + " image name: " + imageName + "," + + " container: " + containerIdStr); + } + private void executeLivelinessCheck(ContainerRuntimeContext ctx) throws ContainerExecutionException { String procFs = ctx.getExecutionAttribute(PROCFS); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/TestDockerContainerRuntime.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/TestDockerContainerRuntime.java index ef0f2e00b13..6669cac6d43 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/TestDockerContainerRuntime.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/containermanager/linux/runtime/TestDockerContainerRuntime.java @@ -533,6 +533,121 @@ public class TestDockerContainerRuntime { dockerCommands.get(counter)); } + @Test + public void testDockerContainerLaunchWithoutDefaultImageUpdate() + throws ContainerExecutionException, PrivilegedOperationException, + IOException { + DockerLinuxContainerRuntime runtime = new DockerLinuxContainerRuntime( + mockExecutor, mockCGroupsHandler); + conf.setBoolean(YarnConfiguration.NM_DOCKER_IMAGE_UPDATE, false); + + runtime.initialize(conf, nmContext); + runtime.launchContainer(builder.build()); + List dockerCommands = readDockerCommands(); + Assert.assertEquals(false, + conf.getBoolean(YarnConfiguration.NM_DOCKER_IMAGE_UPDATE, false)); + + int expected = 13; + int counter = 0; + Assert.assertEquals(expected, dockerCommands.size()); + Assert.assertEquals("[docker-command-execution]", + dockerCommands.get(counter++)); + Assert.assertEquals(" cap-add=SYS_CHROOT,NET_BIND_SERVICE", + dockerCommands.get(counter++)); + Assert.assertEquals(" cap-drop=ALL", dockerCommands.get(counter++)); + Assert.assertEquals(" detach=true", dockerCommands.get(counter++)); + Assert.assertEquals(" docker-command=run", dockerCommands.get(counter++)); + Assert.assertEquals(" group-add=" + String.join(",", groups), + dockerCommands.get(counter++)); + Assert + .assertEquals(" image=busybox:latest", dockerCommands.get(counter++)); + Assert.assertEquals( + " launch-command=bash,/test_container_work_dir/launch_container.sh", + dockerCommands.get(counter++)); + Assert.assertEquals(" mounts=" + + "/test_container_log_dir:/test_container_log_dir:rw," + + "/test_application_local_dir:/test_application_local_dir:rw," + + "/test_filecache_dir:/test_filecache_dir:ro," + + "/test_user_filecache_dir:/test_user_filecache_dir:ro", + dockerCommands.get(counter++)); + Assert.assertEquals( + " name=container_e11_1518975676334_14532816_01_000001", + dockerCommands.get(counter++)); + Assert.assertEquals(" net=host", dockerCommands.get(counter++)); + Assert.assertEquals(" user=" + uidGidPair, dockerCommands.get(counter++)); + Assert.assertEquals(" workdir=/test_container_work_dir", + dockerCommands.get(counter)); + } + + @Test + public void testDockerContainerLaunchWithDefaultImageUpdate() + throws ContainerExecutionException, PrivilegedOperationException, + IOException { + DockerLinuxContainerRuntime runtime = new DockerLinuxContainerRuntime( + mockExecutor, mockCGroupsHandler); + conf.setBoolean(YarnConfiguration.NM_DOCKER_IMAGE_UPDATE, true); + + runtime.initialize(conf, nmContext); + runtime.launchContainer(builder.build()); + + ArgumentCaptor opCaptor = ArgumentCaptor.forClass( + PrivilegedOperation.class); + + //Two invocations expected. + verify(mockExecutor, times(2)) + .executePrivilegedOperation(any(), opCaptor.capture(), any(), + any(), anyBoolean(), anyBoolean()); + + List allCaptures = opCaptor.getAllValues(); + + // pull image from remote hub firstly + PrivilegedOperation op = allCaptures.get(0); + Assert.assertEquals(PrivilegedOperation.OperationType + .RUN_DOCKER_CMD, op.getOperationType()); + + File commandFile = new File(StringUtils.join(",", op.getArguments())); + FileInputStream fileInputStream = new FileInputStream(commandFile); + String fileContent = new String(IOUtils.toByteArray(fileInputStream)); + Assert.assertEquals("[docker-command-execution]\n" + + " docker-command=pull\n" + + " image=busybox:latest\n", fileContent); + fileInputStream.close(); + + // launch docker container + List dockerCommands = readDockerCommands(2); + + int expected = 13; + int counter = 0; + Assert.assertEquals(expected, dockerCommands.size()); + Assert.assertEquals("[docker-command-execution]", + dockerCommands.get(counter++)); + Assert.assertEquals(" cap-add=SYS_CHROOT,NET_BIND_SERVICE", + dockerCommands.get(counter++)); + Assert.assertEquals(" cap-drop=ALL", dockerCommands.get(counter++)); + Assert.assertEquals(" detach=true", dockerCommands.get(counter++)); + Assert.assertEquals(" docker-command=run", dockerCommands.get(counter++)); + Assert.assertEquals(" group-add=" + String.join(",", groups), + dockerCommands.get(counter++)); + Assert + .assertEquals(" image=busybox:latest", dockerCommands.get(counter++)); + Assert.assertEquals( + " launch-command=bash,/test_container_work_dir/launch_container.sh", + dockerCommands.get(counter++)); + Assert.assertEquals(" mounts=" + + "/test_container_log_dir:/test_container_log_dir:rw," + + "/test_application_local_dir:/test_application_local_dir:rw," + + "/test_filecache_dir:/test_filecache_dir:ro," + + "/test_user_filecache_dir:/test_user_filecache_dir:ro", + dockerCommands.get(counter++)); + Assert.assertEquals( + " name=container_e11_1518975676334_14532816_01_000001", + dockerCommands.get(counter++)); + Assert.assertEquals(" net=host", dockerCommands.get(counter++)); + Assert.assertEquals(" user=" + uidGidPair, dockerCommands.get(counter++)); + Assert.assertEquals(" workdir=/test_container_work_dir", + dockerCommands.get(counter)); + } + @Test public void testContainerLaunchWithUserRemapping() throws ContainerExecutionException, PrivilegedOperationException, diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/DockerContainers.md b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/DockerContainers.md index 8797cb54eef..135a0fc5ba0 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/DockerContainers.md +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/DockerContainers.md @@ -131,6 +131,15 @@ The following properties should be set in yarn-site.xml: + + yarn.nodemanager.runtime.linux.docker.image-update + false + + Optional. Default option to decide whether to pull the latest image + or not. + + + yarn.nodemanager.runtime.linux.docker.allowed-container-networks host,none,bridge