YARN-9184. Add a system flag to allow update to latest docker images.

Contributed by Zhaohui Xin
This commit is contained in:
Eric Yang 2019-02-12 16:16:35 -05:00
parent 7806403842
commit 3dc2523266
5 changed files with 163 additions and 1 deletions

View File

@ -1936,6 +1936,10 @@ public static boolean isAclEnabled(Configuration conf) {
public static final String NM_DOCKER_IMAGE_NAME = public static final String NM_DOCKER_IMAGE_NAME =
DOCKER_CONTAINER_RUNTIME_PREFIX + "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. **/ /** Capabilities allowed (and added by default) for docker containers. **/
public static final String NM_DOCKER_CONTAINER_CAPABILITIES = public static final String NM_DOCKER_CONTAINER_CAPABILITIES =
DOCKER_CONTAINER_RUNTIME_PREFIX + "capabilities"; DOCKER_CONTAINER_RUNTIME_PREFIX + "capabilities";

View File

@ -1761,6 +1761,13 @@
<value></value> <value></value>
</property> </property>
<property>
<description>Default option to decide whether to pull the latest image
or not.</description>
<name>yarn.nodemanager.runtime.linux.docker.image-update</name>
<value>false</value>
</property>
<property> <property>
<description>This configuration setting determines if privileged docker <description>This configuration setting determines if privileged docker
containers are allowed on this cluster. Privileged containers are granted containers are allowed on this cluster. Privileged containers are granted

View File

@ -33,6 +33,7 @@
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerCommandExecutor; 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.DockerExecCommand;
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerKillCommand; 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.DockerRmCommand;
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerStartCommand; import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerStartCommand;
import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerVolumeCommand; import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerVolumeCommand;
@ -272,6 +273,7 @@ public class DockerLinuxContainerRuntime implements LinuxContainerRuntime {
private Map<String, CsiAdaptorProtocol> csiClients = new HashMap<>(); private Map<String, CsiAdaptorProtocol> csiClients = new HashMap<>();
private PrivilegedOperationExecutor privilegedOperationExecutor; private PrivilegedOperationExecutor privilegedOperationExecutor;
private String defaultImageName; private String defaultImageName;
private Boolean defaultImageUpdate;
private Set<String> allowedNetworks = new HashSet<>(); private Set<String> allowedNetworks = new HashSet<>();
private String defaultNetwork; private String defaultNetwork;
private CGroupsHandler cGroupsHandler; private CGroupsHandler cGroupsHandler;
@ -352,6 +354,8 @@ public void initialize(Configuration conf, Context nmContext)
defaultTmpfsMounts.clear(); defaultTmpfsMounts.clear();
defaultImageName = conf.getTrimmed( defaultImageName = conf.getTrimmed(
YarnConfiguration.NM_DOCKER_IMAGE_NAME, ""); YarnConfiguration.NM_DOCKER_IMAGE_NAME, "");
defaultImageUpdate = conf.getBoolean(
YarnConfiguration.NM_DOCKER_IMAGE_UPDATE, false);
allowedNetworks.addAll(Arrays.asList( allowedNetworks.addAll(Arrays.asList(
conf.getTrimmedStrings( conf.getTrimmedStrings(
YarnConfiguration.NM_DOCKER_ALLOWED_CONTAINER_NETWORKS, YarnConfiguration.NM_DOCKER_ALLOWED_CONTAINER_NETWORKS,
@ -802,6 +806,7 @@ public void launchContainer(ContainerRuntimeContext ctx)
throws ContainerExecutionException { throws ContainerExecutionException {
Container container = ctx.getContainer(); Container container = ctx.getContainer();
ContainerId containerId = container.getContainerId(); ContainerId containerId = container.getContainerId();
String containerIdStr = containerId.toString();
Map<String, String> environment = container.getLaunchContext() Map<String, String> environment = container.getLaunchContext()
.getEnvironment(); .getEnvironment();
String imageName = environment.get(ENV_DOCKER_CONTAINER_IMAGE); String imageName = environment.get(ENV_DOCKER_CONTAINER_IMAGE);
@ -822,7 +827,10 @@ public void launchContainer(ContainerRuntimeContext ctx)
validateImageName(imageName); validateImageName(imageName);
String containerIdStr = containerId.toString(); if (defaultImageUpdate) {
pullImageFromRemote(containerIdStr, imageName);
}
String runAsUser = ctx.getExecutionAttribute(RUN_AS_USER); String runAsUser = ctx.getExecutionAttribute(RUN_AS_USER);
String dockerRunAsUser = runAsUser; String dockerRunAsUser = runAsUser;
Path containerWorkDir = ctx.getExecutionAttribute(CONTAINER_WORK_DIR); Path containerWorkDir = ctx.getExecutionAttribute(CONTAINER_WORK_DIR);
@ -1379,6 +1387,25 @@ public static void validateImageName(String imageName)
} }
} }
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) private void executeLivelinessCheck(ContainerRuntimeContext ctx)
throws ContainerExecutionException { throws ContainerExecutionException {
String procFs = ctx.getExecutionAttribute(PROCFS); String procFs = ctx.getExecutionAttribute(PROCFS);

View File

@ -533,6 +533,121 @@ public void testDockerContainerLaunchWithDefaultImage()
dockerCommands.get(counter)); 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<String> 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<PrivilegedOperation> opCaptor = ArgumentCaptor.forClass(
PrivilegedOperation.class);
//Two invocations expected.
verify(mockExecutor, times(2))
.executePrivilegedOperation(any(), opCaptor.capture(), any(),
any(), anyBoolean(), anyBoolean());
List<PrivilegedOperation> 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<String> 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 @Test
public void testContainerLaunchWithUserRemapping() public void testContainerLaunchWithUserRemapping()
throws ContainerExecutionException, PrivilegedOperationException, throws ContainerExecutionException, PrivilegedOperationException,

View File

@ -131,6 +131,15 @@ The following properties should be set in yarn-site.xml:
</description> </description>
</property> </property>
<property>
<name>yarn.nodemanager.runtime.linux.docker.image-update</name>
<value>false</value>
<description>
Optional. Default option to decide whether to pull the latest image
or not.
</description>
</property>
<property> <property>
<name>yarn.nodemanager.runtime.linux.docker.allowed-container-networks</name> <name>yarn.nodemanager.runtime.linux.docker.allowed-container-networks</name>
<value>host,none,bridge</value> <value>host,none,bridge</value>