Revert "YARN-7677. Docker image cannot set HADOOP_CONF_DIR. Contributed by Jim Brennan"
[hadoop.git] / 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
1 /*
2 * *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * /
19 */
20
21 package org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime;
22
23 import com.google.common.annotations.VisibleForTesting;
24 import org.apache.hadoop.security.Credentials;
25 import org.apache.hadoop.yarn.server.nodemanager.Context;
26 import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerCommandExecutor;
27 import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerKillCommand;
28 import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerRmCommand;
29 import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerVolumeCommand;
30 import org.apache.hadoop.yarn.server.nodemanager.containermanager.resourceplugin.DockerCommandPlugin;
31 import org.apache.hadoop.yarn.server.nodemanager.containermanager.resourceplugin.ResourcePlugin;
32 import org.apache.hadoop.yarn.util.DockerClientConfigHandler;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35 import org.apache.hadoop.classification.InterfaceAudience;
36 import org.apache.hadoop.classification.InterfaceStability;
37 import org.apache.hadoop.conf.Configuration;
38 import org.apache.hadoop.fs.Path;
39 import org.apache.hadoop.registry.client.api.RegistryConstants;
40 import org.apache.hadoop.registry.client.binding.RegistryPathUtils;
41 import org.apache.hadoop.security.UserGroupInformation;
42 import org.apache.hadoop.security.authorize.AccessControlList;
43 import org.apache.hadoop.util.Shell;
44 import org.apache.hadoop.util.StringUtils;
45 import org.apache.hadoop.yarn.conf.YarnConfiguration;
46 import org.apache.hadoop.yarn.server.nodemanager.ContainerExecutor;
47 import org.apache.hadoop.yarn.server.nodemanager.containermanager.container.Container;
48 import org.apache.hadoop.yarn.server.nodemanager.containermanager.launcher.ContainerLaunch;
49 import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.privileged.PrivilegedOperation;
50 import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.privileged.PrivilegedOperationException;
51 import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.privileged.PrivilegedOperationExecutor;
52 import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.resources.CGroupsHandler;
53 import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.resources.ResourceHandlerModule;
54 import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerClient;
55 import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerInspectCommand;
56 import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerRunCommand;
57 import org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.docker.DockerStopCommand;
58 import org.apache.hadoop.yarn.server.nodemanager.containermanager.runtime.ContainerExecutionException;
59 import org.apache.hadoop.yarn.server.nodemanager.containermanager.runtime.ContainerRuntime;
60 import org.apache.hadoop.yarn.server.nodemanager.containermanager.runtime.ContainerRuntimeConstants;
61 import org.apache.hadoop.yarn.server.nodemanager.containermanager.runtime.ContainerRuntimeContext;
62
63 import java.io.File;
64 import java.io.IOException;
65 import java.net.InetAddress;
66 import java.net.UnknownHostException;
67 import java.nio.ByteBuffer;
68 import java.nio.file.Files;
69 import java.nio.file.Paths;
70 import java.util.ArrayList;
71 import java.util.Arrays;
72 import java.util.Collections;
73 import java.util.HashSet;
74 import java.util.List;
75 import java.util.Map;
76 import java.util.Map.Entry;
77 import java.util.Set;
78 import java.util.regex.Matcher;
79 import java.util.regex.Pattern;
80
81 import static org.apache.hadoop.yarn.server.nodemanager.containermanager.linux.runtime.LinuxContainerRuntimeConstants.*;
82
83 /**
84 * <p>This class is a {@link ContainerRuntime} implementation that uses the
85 * native {@code container-executor} binary via a
86 * {@link PrivilegedOperationExecutor} instance to launch processes inside
87 * Docker containers.</p>
88 *
89 * <p>The following environment variables are used to configure the Docker
90 * engine:</p>
91 *
92 * <ul>
93 * <li>
94 * {@code YARN_CONTAINER_RUNTIME_TYPE} ultimately determines whether a
95 * Docker container will be used. If the value is {@code docker}, a Docker
96 * container will be used. Otherwise a regular process tree container will
97 * be used. This environment variable is checked by the
98 * {@link #isDockerContainerRequested} method, which is called by the
99 * {@link DelegatingLinuxContainerRuntime}.
100 * </li>
101 * <li>
102 * {@code YARN_CONTAINER_RUNTIME_DOCKER_IMAGE} names which image
103 * will be used to launch the Docker container.
104 * </li>
105 * <li>
106 * {@code YARN_CONTAINER_RUNTIME_DOCKER_IMAGE_FILE} is currently ignored.
107 * </li>
108 * <li>
109 * {@code YARN_CONTAINER_RUNTIME_DOCKER_RUN_OVERRIDE_DISABLE} controls
110 * whether the Docker container's default command is overridden. When set
111 * to {@code true}, the Docker container's command will be
112 * {@code bash <path_to_launch_script>}. When unset or set to {@code false}
113 * the Docker container's default command is used.
114 * </li>
115 * <li>
116 * {@code YARN_CONTAINER_RUNTIME_DOCKER_CONTAINER_NETWORK} sets the
117 * network type to be used by the Docker container. It must be a valid
118 * value as determined by the
119 * {@code yarn.nodemanager.runtime.linux.docker.allowed-container-networks}
120 * property.
121 * </li>
122 * <li>
123 * {@code YARN_CONTAINER_RUNTIME_DOCKER_CONTAINER_PID_NAMESPACE}
124 * controls which PID namespace will be used by the Docker container. By
125 * default, each Docker container has its own PID namespace. To share the
126 * namespace of the host, the
127 * {@code yarn.nodemanager.runtime.linux.docker.host-pid-namespace.allowed}
128 * property must be set to {@code true}. If the host PID namespace is
129 * allowed and this environment variable is set to {@code host}, the
130 * Docker container will share the host's PID namespace. No other value is
131 * allowed.
132 * </li>
133 * <li>
134 * {@code YARN_CONTAINER_RUNTIME_DOCKER_CONTAINER_HOSTNAME} sets the
135 * hostname to be used by the Docker container. If not specified, a
136 * hostname will be derived from the container ID.
137 * </li>
138 * <li>
139 * {@code YARN_CONTAINER_RUNTIME_DOCKER_RUN_PRIVILEGED_CONTAINER}
140 * controls whether the Docker container is a privileged container. In order
141 * to use privileged containers, the
142 * {@code yarn.nodemanager.runtime.linux.docker.privileged-containers.allowed}
143 * property must be set to {@code true}, and the application owner must
144 * appear in the value of the
145 * {@code yarn.nodemanager.runtime.linux.docker.privileged-containers.acl}
146 * property. If this environment variable is set to {@code true}, a
147 * privileged Docker container will be used if allowed. No other value is
148 * allowed, so the environment variable should be left unset rather than
149 * setting it to false.
150 * </li>
151 * <li>
152 * {@code YARN_CONTAINER_RUNTIME_DOCKER_LOCAL_RESOURCE_MOUNTS} adds
153 * additional volume mounts to the Docker container. The value of the
154 * environment variable should be a comma-separated list of mounts.
155 * All such mounts must be given as {@code source:dest}, where the
156 * source is an absolute path that is not a symlink and that points to a
157 * localized resource.
158 * </li>
159 * <li>
160 * {@code YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS} allows users to specify
161 + additional volume mounts for the Docker container. The value of the
162 * environment variable should be a comma-separated list of mounts.
163 * All such mounts must be given as {@code source:dest:mode}, and the mode
164 * must be "ro" (read-only) or "rw" (read-write) to specify the type of
165 * access being requested. The requested mounts will be validated by
166 * container-executor based on the values set in container-executor.cfg for
167 * {@code docker.allowed.ro-mounts} and {@code docker.allowed.rw-mounts}.
168 * </li>
169 * <li>
170 * {@code YARN_CONTAINER_RUNTIME_DOCKER_DELAYED_REMOVAL} allows a user
171 * to request delayed deletion of the Docker containers on a per
172 * container basis. If true, Docker containers will not be removed until
173 * the duration defined by {@code yarn.nodemanager.delete.debug-delay-sec}
174 * has elapsed. Administrators can disable this feature through the
175 * yarn-site property
176 * {@code yarn.nodemanager.runtime.linux.docker.delayed-removal.allowed}.
177 * This feature is disabled by default. When this feature is disabled or set
178 * to false, the container will be removed as soon as it exits.
179 * </li>
180 * </ul>
181 */
182 @InterfaceAudience.Private
183 @InterfaceStability.Unstable
184 public class DockerLinuxContainerRuntime implements LinuxContainerRuntime {
185 private static final Logger LOG =
186 LoggerFactory.getLogger(DockerLinuxContainerRuntime.class);
187
188 // This validates that the image is a proper docker image
189 public static final String DOCKER_IMAGE_PATTERN =
190 "^(([a-zA-Z0-9.-]+)(:\\d+)?/)?([a-z0-9_./-]+)(:[\\w.-]+)?$";
191 private static final Pattern dockerImagePattern =
192 Pattern.compile(DOCKER_IMAGE_PATTERN);
193 public static final String HOSTNAME_PATTERN =
194 "^[a-zA-Z0-9][a-zA-Z0-9_.-]+$";
195 private static final Pattern hostnamePattern = Pattern.compile(
196 HOSTNAME_PATTERN);
197 private static final Pattern USER_MOUNT_PATTERN = Pattern.compile(
198 "(?<=^|,)([^:\\x00]+):([^:\\x00]+):([a-z]+)");
199
200 @InterfaceAudience.Private
201 public static final String ENV_DOCKER_CONTAINER_IMAGE =
202 "YARN_CONTAINER_RUNTIME_DOCKER_IMAGE";
203 @InterfaceAudience.Private
204 public static final String ENV_DOCKER_CONTAINER_IMAGE_FILE =
205 "YARN_CONTAINER_RUNTIME_DOCKER_IMAGE_FILE";
206 @InterfaceAudience.Private
207 public static final String ENV_DOCKER_CONTAINER_RUN_OVERRIDE_DISABLE =
208 "YARN_CONTAINER_RUNTIME_DOCKER_RUN_OVERRIDE_DISABLE";
209 @InterfaceAudience.Private
210 public static final String ENV_DOCKER_CONTAINER_NETWORK =
211 "YARN_CONTAINER_RUNTIME_DOCKER_CONTAINER_NETWORK";
212 @InterfaceAudience.Private
213 public static final String ENV_DOCKER_CONTAINER_PID_NAMESPACE =
214 "YARN_CONTAINER_RUNTIME_DOCKER_CONTAINER_PID_NAMESPACE";
215 @InterfaceAudience.Private
216 public static final String ENV_DOCKER_CONTAINER_HOSTNAME =
217 "YARN_CONTAINER_RUNTIME_DOCKER_CONTAINER_HOSTNAME";
218 @InterfaceAudience.Private
219 public static final String ENV_DOCKER_CONTAINER_RUN_PRIVILEGED_CONTAINER =
220 "YARN_CONTAINER_RUNTIME_DOCKER_RUN_PRIVILEGED_CONTAINER";
221 @InterfaceAudience.Private
222 public static final String ENV_DOCKER_CONTAINER_RUN_ENABLE_USER_REMAPPING =
223 "YARN_CONTAINER_RUNTIME_DOCKER_RUN_ENABLE_USER_REMAPPING";
224 @InterfaceAudience.Private
225 public static final String ENV_DOCKER_CONTAINER_LOCAL_RESOURCE_MOUNTS =
226 "YARN_CONTAINER_RUNTIME_DOCKER_LOCAL_RESOURCE_MOUNTS";
227 @InterfaceAudience.Private
228 public static final String ENV_DOCKER_CONTAINER_MOUNTS =
229 "YARN_CONTAINER_RUNTIME_DOCKER_MOUNTS";
230 @InterfaceAudience.Private
231 public static final String ENV_DOCKER_CONTAINER_DELAYED_REMOVAL =
232 "YARN_CONTAINER_RUNTIME_DOCKER_DELAYED_REMOVAL";
233
234 private Configuration conf;
235 private Context nmContext;
236 private DockerClient dockerClient;
237 private PrivilegedOperationExecutor privilegedOperationExecutor;
238 private Set<String> allowedNetworks = new HashSet<>();
239 private String defaultNetwork;
240 private CGroupsHandler cGroupsHandler;
241 private AccessControlList privilegedContainersAcl;
242 private boolean enableUserReMapping;
243 private int userRemappingUidThreshold;
244 private int userRemappingGidThreshold;
245 private Set<String> capabilities;
246 private boolean delayedRemovalAllowed;
247
248 /**
249 * Return whether the given environment variables indicate that the operation
250 * is requesting a Docker container. If the environment contains a key
251 * called {@code YARN_CONTAINER_RUNTIME_TYPE} whose value is {@code docker},
252 * this method will return true. Otherwise it will return false.
253 *
254 * @param env the environment variable settings for the operation
255 * @return whether a Docker container is requested
256 */
257 public static boolean isDockerContainerRequested(
258 Map<String, String> env) {
259 if (env == null) {
260 return false;
261 }
262
263 String type = env.get(ContainerRuntimeConstants.ENV_CONTAINER_TYPE);
264
265 return type != null && type.equals("docker");
266 }
267
268 /**
269 * Create an instance using the given {@link PrivilegedOperationExecutor}
270 * instance for performing operations.
271 *
272 * @param privilegedOperationExecutor the {@link PrivilegedOperationExecutor}
273 * instance
274 */
275 public DockerLinuxContainerRuntime(PrivilegedOperationExecutor
276 privilegedOperationExecutor) {
277 this(privilegedOperationExecutor,
278 ResourceHandlerModule.getCGroupsHandler());
279 }
280
281 /**
282 * Create an instance using the given {@link PrivilegedOperationExecutor}
283 * instance for performing operations and the given {@link CGroupsHandler}
284 * instance. This constructor is intended for use in testing.
285 * @param privilegedOperationExecutor the {@link PrivilegedOperationExecutor}
286 * instance
287 * @param cGroupsHandler the {@link CGroupsHandler} instance
288 */
289 @VisibleForTesting
290 public DockerLinuxContainerRuntime(
291 PrivilegedOperationExecutor privilegedOperationExecutor,
292 CGroupsHandler cGroupsHandler) {
293 this.privilegedOperationExecutor = privilegedOperationExecutor;
294
295 if (cGroupsHandler == null) {
296 LOG.info("cGroupsHandler is null - cgroups not in use.");
297 } else {
298 this.cGroupsHandler = cGroupsHandler;
299 }
300 }
301
302 @Override
303 public void initialize(Configuration conf, Context nmContext)
304 throws ContainerExecutionException {
305 this.nmContext = nmContext;
306 this.conf = conf;
307 dockerClient = new DockerClient(conf);
308 allowedNetworks.clear();
309 allowedNetworks.addAll(Arrays.asList(
310 conf.getTrimmedStrings(
311 YarnConfiguration.NM_DOCKER_ALLOWED_CONTAINER_NETWORKS,
312 YarnConfiguration.DEFAULT_NM_DOCKER_ALLOWED_CONTAINER_NETWORKS)));
313 defaultNetwork = conf.getTrimmed(
314 YarnConfiguration.NM_DOCKER_DEFAULT_CONTAINER_NETWORK,
315 YarnConfiguration.DEFAULT_NM_DOCKER_DEFAULT_CONTAINER_NETWORK);
316
317 if(!allowedNetworks.contains(defaultNetwork)) {
318 String message = "Default network: " + defaultNetwork
319 + " is not in the set of allowed networks: " + allowedNetworks;
320
321 if (LOG.isWarnEnabled()) {
322 LOG.warn(message + ". Please check "
323 + "configuration");
324 }
325
326 throw new ContainerExecutionException(message);
327 }
328
329 privilegedContainersAcl = new AccessControlList(conf.getTrimmed(
330 YarnConfiguration.NM_DOCKER_PRIVILEGED_CONTAINERS_ACL,
331 YarnConfiguration.DEFAULT_NM_DOCKER_PRIVILEGED_CONTAINERS_ACL));
332
333 enableUserReMapping = conf.getBoolean(
334 YarnConfiguration.NM_DOCKER_ENABLE_USER_REMAPPING,
335 YarnConfiguration.DEFAULT_NM_DOCKER_ENABLE_USER_REMAPPING);
336
337 userRemappingUidThreshold = conf.getInt(
338 YarnConfiguration.NM_DOCKER_USER_REMAPPING_UID_THRESHOLD,
339 YarnConfiguration.DEFAULT_NM_DOCKER_USER_REMAPPING_UID_THRESHOLD);
340
341 userRemappingGidThreshold = conf.getInt(
342 YarnConfiguration.NM_DOCKER_USER_REMAPPING_GID_THRESHOLD,
343 YarnConfiguration.DEFAULT_NM_DOCKER_USER_REMAPPING_GID_THRESHOLD);
344
345 capabilities = getDockerCapabilitiesFromConf();
346
347 delayedRemovalAllowed = conf.getBoolean(
348 YarnConfiguration.NM_DOCKER_ALLOW_DELAYED_REMOVAL,
349 YarnConfiguration.DEFAULT_NM_DOCKER_ALLOW_DELAYED_REMOVAL);
350 }
351
352 private Set<String> getDockerCapabilitiesFromConf() throws
353 ContainerExecutionException {
354 Set<String> caps = new HashSet<>(Arrays.asList(
355 conf.getTrimmedStrings(
356 YarnConfiguration.NM_DOCKER_CONTAINER_CAPABILITIES,
357 YarnConfiguration.DEFAULT_NM_DOCKER_CONTAINER_CAPABILITIES)));
358 if(caps.contains("none") || caps.contains("NONE")) {
359 if(caps.size() > 1) {
360 String msg = "Mixing capabilities with the none keyword is" +
361 " not supported";
362 throw new ContainerExecutionException(msg);
363 }
364 caps = Collections.emptySet();
365 }
366
367 return caps;
368 }
369
370 public Set<String> getCapabilities() {
371 return capabilities;
372 }
373
374 @Override
375 public boolean useWhitelistEnv(Map<String, String> env) {
376 // Avoid propagating nodemanager environment variables into the container
377 // so those variables can be picked up from the Docker image instead.
378 return false;
379 }
380
381 private String runDockerVolumeCommand(DockerVolumeCommand dockerVolumeCommand,
382 Container container) throws ContainerExecutionException {
383 try {
384 String commandFile = dockerClient.writeCommandToTempFile(
385 dockerVolumeCommand, container.getContainerId().toString());
386 PrivilegedOperation privOp = new PrivilegedOperation(
387 PrivilegedOperation.OperationType.RUN_DOCKER_CMD);
388 privOp.appendArgs(commandFile);
389 String output = privilegedOperationExecutor
390 .executePrivilegedOperation(null, privOp, null,
391 null, true, false);
392 LOG.info("ContainerId=" + container.getContainerId()
393 + ", docker volume output for " + dockerVolumeCommand + ": "
394 + output);
395 return output;
396 } catch (ContainerExecutionException e) {
397 LOG.error("Error when writing command to temp file, command="
398 + dockerVolumeCommand,
399 e);
400 throw e;
401 } catch (PrivilegedOperationException e) {
402 LOG.error("Error when executing command, command="
403 + dockerVolumeCommand, e);
404 throw new ContainerExecutionException(e);
405 }
406
407 }
408
409 @Override
410 public void prepareContainer(ContainerRuntimeContext ctx)
411 throws ContainerExecutionException {
412 Container container = ctx.getContainer();
413
414 // Create volumes when needed.
415 if (nmContext != null
416 && nmContext.getResourcePluginManager().getNameToPlugins() != null) {
417 for (ResourcePlugin plugin : nmContext.getResourcePluginManager()
418 .getNameToPlugins().values()) {
419 DockerCommandPlugin dockerCommandPlugin =
420 plugin.getDockerCommandPluginInstance();
421 if (dockerCommandPlugin != null) {
422 DockerVolumeCommand dockerVolumeCommand =
423 dockerCommandPlugin.getCreateDockerVolumeCommand(
424 ctx.getContainer());
425 if (dockerVolumeCommand != null) {
426 runDockerVolumeCommand(dockerVolumeCommand, container);
427
428 // After volume created, run inspect to make sure volume properly
429 // created.
430 if (dockerVolumeCommand.getSubCommand().equals(
431 DockerVolumeCommand.VOLUME_CREATE_SUB_COMMAND)) {
432 checkDockerVolumeCreated(dockerVolumeCommand, container);
433 }
434 }
435 }
436 }
437 }
438 }
439
440 private void checkDockerVolumeCreated(
441 DockerVolumeCommand dockerVolumeCreationCommand, Container container)
442 throws ContainerExecutionException {
443 DockerVolumeCommand dockerVolumeInspectCommand = new DockerVolumeCommand(
444 DockerVolumeCommand.VOLUME_LS_SUB_COMMAND);
445 String output = runDockerVolumeCommand(dockerVolumeInspectCommand,
446 container);
447
448 // Parse output line by line and check if it matches
449 String volumeName = dockerVolumeCreationCommand.getVolumeName();
450 String driverName = dockerVolumeCreationCommand.getDriverName();
451 if (driverName == null) {
452 driverName = "local";
453 }
454
455 for (String line : output.split("\n")) {
456 line = line.trim();
457 if (line.contains(volumeName) && line.contains(driverName)) {
458 // Good we found it.
459 LOG.info(
460 "Docker volume-name=" + volumeName + " driver-name=" + driverName
461 + " already exists for container=" + container
462 .getContainerId() + ", continue...");
463 return;
464 }
465 }
466
467 // Couldn't find the volume
468 String message =
469 " Couldn't find volume=" + volumeName + " driver=" + driverName
470 + " for container=" + container.getContainerId()
471 + ", please check error message in log to understand "
472 + "why this happens.";
473 LOG.error(message);
474
475 if (LOG.isDebugEnabled()) {
476 LOG.debug("All docker volumes in the system, command="
477 + dockerVolumeInspectCommand.toString());
478 }
479
480 throw new ContainerExecutionException(message);
481 }
482
483 private void validateContainerNetworkType(String network)
484 throws ContainerExecutionException {
485 if (allowedNetworks.contains(network)) {
486 return;
487 }
488
489 String msg = "Disallowed network: '" + network
490 + "' specified. Allowed networks: are " + allowedNetworks
491 .toString();
492 throw new ContainerExecutionException(msg);
493 }
494
495 /**
496 * Return whether the YARN container is allowed to run using the host's PID
497 * namespace for the Docker container. For this to be allowed, the submitting
498 * user must request the feature and the feature must be enabled on the
499 * cluster.
500 *
501 * @param container the target YARN container
502 * @return whether host pid namespace is requested and allowed
503 * @throws ContainerExecutionException if host pid namespace is requested
504 * but is not allowed
505 */
506 private boolean allowHostPidNamespace(Container container)
507 throws ContainerExecutionException {
508 Map<String, String> environment = container.getLaunchContext()
509 .getEnvironment();
510 String pidNamespace = environment.get(ENV_DOCKER_CONTAINER_PID_NAMESPACE);
511
512 if (pidNamespace == null) {
513 return false;
514 }
515
516 if (!pidNamespace.equalsIgnoreCase("host")) {
517 LOG.warn("NOT requesting PID namespace. Value of " +
518 ENV_DOCKER_CONTAINER_PID_NAMESPACE + "is invalid: " + pidNamespace);
519 return false;
520 }
521
522 boolean hostPidNamespaceEnabled = conf.getBoolean(
523 YarnConfiguration.NM_DOCKER_ALLOW_HOST_PID_NAMESPACE,
524 YarnConfiguration.DEFAULT_NM_DOCKER_ALLOW_HOST_PID_NAMESPACE);
525
526 if (!hostPidNamespaceEnabled) {
527 String message = "Host pid namespace being requested but this is not "
528 + "enabled on this cluster";
529 LOG.warn(message);
530 throw new ContainerExecutionException(message);
531 }
532
533 return true;
534 }
535
536 public static void validateHostname(String hostname) throws
537 ContainerExecutionException {
538 if (hostname != null && !hostname.isEmpty()) {
539 if (!hostnamePattern.matcher(hostname).matches()) {
540 throw new ContainerExecutionException("Hostname '" + hostname
541 + "' doesn't match docker hostname pattern");
542 }
543 }
544 }
545
546 /** Set a DNS friendly hostname. */
547 private void setHostname(DockerRunCommand runCommand, String
548 containerIdStr, String name)
549 throws ContainerExecutionException {
550 if (name == null || name.isEmpty()) {
551 name = RegistryPathUtils.encodeYarnID(containerIdStr);
552
553 String domain = conf.get(RegistryConstants.KEY_DNS_DOMAIN);
554 if (domain != null) {
555 name += ("." + domain);
556 }
557 validateHostname(name);
558 }
559
560 LOG.info("setting hostname in container to: " + name);
561 runCommand.setHostname(name);
562 }
563
564 /**
565 * If CGROUPS in enabled and not set to none, then set the CGROUP parent for
566 * the command instance.
567 *
568 * @param resourcesOptions the resource options to check for "cgroups=none"
569 * @param containerIdStr the container ID
570 * @param runCommand the command to set with the CGROUP parent
571 */
572 @VisibleForTesting
573 protected void addCGroupParentIfRequired(String resourcesOptions,
574 String containerIdStr, DockerRunCommand runCommand) {
575 if (cGroupsHandler == null) {
576 if (LOG.isDebugEnabled()) {
577 LOG.debug("cGroupsHandler is null. cgroups are not in use. nothing to"
578 + " do.");
579 }
580 return;
581 }
582
583 if (resourcesOptions.equals(PrivilegedOperation.CGROUP_ARG_PREFIX
584 + PrivilegedOperation.CGROUP_ARG_NO_TASKS)) {
585 if (LOG.isDebugEnabled()) {
586 LOG.debug("no resource restrictions specified. not using docker's "
587 + "cgroup options");
588 }
589 } else {
590 if (LOG.isDebugEnabled()) {
591 LOG.debug("using docker's cgroups options");
592 }
593
594 String cGroupPath = "/"
595 + cGroupsHandler.getRelativePathForCGroup(containerIdStr);
596
597 if (LOG.isDebugEnabled()) {
598 LOG.debug("using cgroup parent: " + cGroupPath);
599 }
600
601 runCommand.setCGroupParent(cGroupPath);
602 }
603 }
604
605 /**
606 * Return whether the YARN container is allowed to run in a privileged
607 * Docker container. For a privileged container to be allowed all of the
608 * following three conditions must be satisfied:
609 *
610 * <ol>
611 * <li>Submitting user must request for a privileged container</li>
612 * <li>Privileged containers must be enabled on the cluster</li>
613 * <li>Submitting user must be white-listed to run a privileged
614 * container</li>
615 * </ol>
616 *
617 * @param container the target YARN container
618 * @return whether privileged container execution is allowed
619 * @throws ContainerExecutionException if privileged container execution
620 * is requested but is not allowed
621 */
622 private boolean allowPrivilegedContainerExecution(Container container)
623 throws ContainerExecutionException {
624 Map<String, String> environment = container.getLaunchContext()
625 .getEnvironment();
626 String runPrivilegedContainerEnvVar = environment
627 .get(ENV_DOCKER_CONTAINER_RUN_PRIVILEGED_CONTAINER);
628
629 if (runPrivilegedContainerEnvVar == null) {
630 return false;
631 }
632
633 if (!runPrivilegedContainerEnvVar.equalsIgnoreCase("true")) {
634 LOG.warn("NOT running a privileged container. Value of " +
635 ENV_DOCKER_CONTAINER_RUN_PRIVILEGED_CONTAINER
636 + "is invalid: " + runPrivilegedContainerEnvVar);
637 return false;
638 }
639
640 LOG.info("Privileged container requested for : " + container
641 .getContainerId().toString());
642
643 //Ok, so we have been asked to run a privileged container. Security
644 // checks need to be run. Each violation is an error.
645
646 //check if privileged containers are enabled.
647 boolean privilegedContainersEnabledOnCluster = conf.getBoolean(
648 YarnConfiguration.NM_DOCKER_ALLOW_PRIVILEGED_CONTAINERS,
649 YarnConfiguration.DEFAULT_NM_DOCKER_ALLOW_PRIVILEGED_CONTAINERS);
650
651 if (!privilegedContainersEnabledOnCluster) {
652 String message = "Privileged container being requested but privileged "
653 + "containers are not enabled on this cluster";
654 LOG.warn(message);
655 throw new ContainerExecutionException(message);
656 }
657
658 //check if submitting user is in the whitelist.
659 String submittingUser = container.getUser();
660 UserGroupInformation submitterUgi = UserGroupInformation
661 .createRemoteUser(submittingUser);
662
663 if (!privilegedContainersAcl.isUserAllowed(submitterUgi)) {
664 String message = "Cannot launch privileged container. Submitting user ("
665 + submittingUser + ") fails ACL check.";
666 LOG.warn(message);
667 throw new ContainerExecutionException(message);
668 }
669
670 LOG.info("All checks pass. Launching privileged container for : "
671 + container.getContainerId().toString());
672
673 return true;
674 }
675
676 @VisibleForTesting
677 protected String validateMount(String mount,
678 Map<Path, List<String>> localizedResources)
679 throws ContainerExecutionException {
680 for (Entry<Path, List<String>> resource : localizedResources.entrySet()) {
681 if (resource.getValue().contains(mount)) {
682 java.nio.file.Path path = Paths.get(resource.getKey().toString());
683 if (!path.isAbsolute()) {
684 throw new ContainerExecutionException("Mount must be absolute: " +
685 mount);
686 }
687 if (Files.isSymbolicLink(path)) {
688 throw new ContainerExecutionException("Mount cannot be a symlink: " +
689 mount);
690 }
691 return path.toString();
692 }
693 }
694 throw new ContainerExecutionException("Mount must be a localized " +
695 "resource: " + mount);
696 }
697
698 private String getUserIdInfo(String userName)
699 throws ContainerExecutionException {
700 String id = "";
701 Shell.ShellCommandExecutor shexec = new Shell.ShellCommandExecutor(
702 new String[]{"id", "-u", userName});
703 try {
704 shexec.execute();
705 id = shexec.getOutput().replaceAll("[^0-9]", "");
706 } catch (Exception e) {
707 throw new ContainerExecutionException(e);
708 }
709 return id;
710 }
711
712 private String[] getGroupIdInfo(String userName)
713 throws ContainerExecutionException {
714 String[] id = null;
715 Shell.ShellCommandExecutor shexec = new Shell.ShellCommandExecutor(
716 new String[]{"id", "-G", userName});
717 try {
718 shexec.execute();
719 id = shexec.getOutput().replace("\n", "").split(" ");
720 } catch (Exception e) {
721 throw new ContainerExecutionException(e);
722 }
723 return id;
724 }
725
726 @Override
727 public void launchContainer(ContainerRuntimeContext ctx)
728 throws ContainerExecutionException {
729 Container container = ctx.getContainer();
730 Map<String, String> environment = container.getLaunchContext()
731 .getEnvironment();
732 String imageName = environment.get(ENV_DOCKER_CONTAINER_IMAGE);
733 String network = environment.get(ENV_DOCKER_CONTAINER_NETWORK);
734 String hostname = environment.get(ENV_DOCKER_CONTAINER_HOSTNAME);
735
736 if(network == null || network.isEmpty()) {
737 network = defaultNetwork;
738 }
739
740 validateContainerNetworkType(network);
741
742 validateHostname(hostname);
743
744 validateImageName(imageName);
745
746 String containerIdStr = container.getContainerId().toString();
747 String runAsUser = ctx.getExecutionAttribute(RUN_AS_USER);
748 String dockerRunAsUser = runAsUser;
749 Path containerWorkDir = ctx.getExecutionAttribute(CONTAINER_WORK_DIR);
750 String[] groups = null;
751
752 if (enableUserReMapping) {
753 String uid = getUserIdInfo(runAsUser);
754 groups = getGroupIdInfo(runAsUser);
755 String gid = groups[0];
756 if(Integer.parseInt(uid) < userRemappingUidThreshold) {
757 String message = "uid: " + uid + " below threshold: "
758 + userRemappingUidThreshold;
759 throw new ContainerExecutionException(message);
760 }
761 for(int i = 0; i < groups.length; i++) {
762 String group = groups[i];
763 if (Integer.parseInt(group) < userRemappingGidThreshold) {
764 String message = "gid: " + group
765 + " below threshold: " + userRemappingGidThreshold;
766 throw new ContainerExecutionException(message);
767 }
768 }
769 dockerRunAsUser = uid + ":" + gid;
770 }
771
772 //List<String> -> stored as List -> fetched/converted to List<String>
773 //we can't do better here thanks to type-erasure
774 @SuppressWarnings("unchecked")
775 List<String> filecacheDirs = ctx.getExecutionAttribute(FILECACHE_DIRS);
776 @SuppressWarnings("unchecked")
777 List<String> containerLogDirs = ctx.getExecutionAttribute(
778 CONTAINER_LOG_DIRS);
779 @SuppressWarnings("unchecked")
780 List<String> userFilecacheDirs =
781 ctx.getExecutionAttribute(USER_FILECACHE_DIRS);
782 @SuppressWarnings("unchecked")
783 List<String> applicationLocalDirs =
784 ctx.getExecutionAttribute(APPLICATION_LOCAL_DIRS);
785 @SuppressWarnings("unchecked")
786 Map<Path, List<String>> localizedResources = ctx.getExecutionAttribute(
787 LOCALIZED_RESOURCES);
788
789 @SuppressWarnings("unchecked")
790 DockerRunCommand runCommand = new DockerRunCommand(containerIdStr,
791 dockerRunAsUser, imageName)
792 .detachOnRun()
793 .setContainerWorkDir(containerWorkDir.toString())
794 .setNetworkType(network);
795 setHostname(runCommand, containerIdStr, hostname);
796 runCommand.setCapabilities(capabilities);
797
798 runCommand.addAllReadWriteMountLocations(containerLogDirs);
799 runCommand.addAllReadWriteMountLocations(applicationLocalDirs);
800 runCommand.addAllReadOnlyMountLocations(filecacheDirs);
801 runCommand.addAllReadOnlyMountLocations(userFilecacheDirs);
802
803 if (environment.containsKey(ENV_DOCKER_CONTAINER_LOCAL_RESOURCE_MOUNTS)) {
804 String mounts = environment.get(
805 ENV_DOCKER_CONTAINER_LOCAL_RESOURCE_MOUNTS);
806 if (!mounts.isEmpty()) {
807 for (String mount : StringUtils.split(mounts)) {
808 String[] dir = StringUtils.split(mount, ':');
809 if (dir.length != 2) {
810 throw new ContainerExecutionException("Invalid mount : " +
811 mount);
812 }
813 String src = validateMount(dir[0], localizedResources);
814 String dst = dir[1];
815 runCommand.addReadOnlyMountLocation(src, dst, true);
816 }
817 }
818 }
819
820 if (environment.containsKey(ENV_DOCKER_CONTAINER_MOUNTS)) {
821 Matcher parsedMounts = USER_MOUNT_PATTERN.matcher(
822 environment.get(ENV_DOCKER_CONTAINER_MOUNTS));
823 if (!parsedMounts.find()) {
824 throw new ContainerExecutionException(
825 "Unable to parse user supplied mount list: "
826 + environment.get(ENV_DOCKER_CONTAINER_MOUNTS));
827 }
828 parsedMounts.reset();
829 while (parsedMounts.find()) {
830 String src = parsedMounts.group(1);
831 String dst = parsedMounts.group(2);
832 String mode = parsedMounts.group(3);
833 if (!mode.equals("ro") && !mode.equals("rw")) {
834 throw new ContainerExecutionException(
835 "Invalid mount mode requested for mount: "
836 + parsedMounts.group());
837 }
838 if (mode.equals("ro")) {
839 runCommand.addReadOnlyMountLocation(src, dst);
840 } else {
841 runCommand.addReadWriteMountLocation(src, dst);
842 }
843 }
844 }
845
846 if (allowHostPidNamespace(container)) {
847 runCommand.setPidNamespace("host");
848 }
849
850 if (allowPrivilegedContainerExecution(container)) {
851 runCommand.setPrivileged();
852 }
853
854 addDockerClientConfigToRunCommand(ctx, runCommand);
855
856 String resourcesOpts = ctx.getExecutionAttribute(RESOURCES_OPTIONS);
857
858 addCGroupParentIfRequired(resourcesOpts, containerIdStr, runCommand);
859
860 String disableOverride = environment.get(
861 ENV_DOCKER_CONTAINER_RUN_OVERRIDE_DISABLE);
862
863 if (disableOverride != null && disableOverride.equals("true")) {
864 LOG.info("command override disabled");
865 } else {
866 List<String> overrideCommands = new ArrayList<>();
867 Path launchDst =
868 new Path(containerWorkDir, ContainerLaunch.CONTAINER_SCRIPT);
869
870 overrideCommands.add("bash");
871 overrideCommands.add(launchDst.toUri().getPath());
872 runCommand.setOverrideCommandWithArgs(overrideCommands);
873 }
874
875 if(enableUserReMapping) {
876 runCommand.groupAdd(groups);
877 }
878
879 // use plugins to update docker run command.
880 if (nmContext != null
881 && nmContext.getResourcePluginManager().getNameToPlugins() != null) {
882 for (ResourcePlugin plugin : nmContext.getResourcePluginManager()
883 .getNameToPlugins().values()) {
884 DockerCommandPlugin dockerCommandPlugin =
885 plugin.getDockerCommandPluginInstance();
886 if (dockerCommandPlugin != null) {
887 dockerCommandPlugin.updateDockerRunCommand(runCommand, container);
888 }
889 }
890 }
891
892 String commandFile = dockerClient.writeCommandToTempFile(runCommand,
893 containerIdStr);
894 PrivilegedOperation launchOp = buildLaunchOp(ctx,
895 commandFile, runCommand);
896
897 try {
898 privilegedOperationExecutor.executePrivilegedOperation(null,
899 launchOp, null, null, false, false);
900 } catch (PrivilegedOperationException e) {
901 LOG.warn("Launch container failed. Exception: ", e);
902 LOG.info("Docker command used: " + runCommand);
903
904 throw new ContainerExecutionException("Launch container failed", e
905 .getExitCode(), e.getOutput(), e.getErrorOutput());
906 }
907 }
908
909 /**
910 * Signal the docker container.
911 *
912 * Signals are used to check the liveliness of the container as well as to
913 * stop/kill the container. The following outlines the docker container
914 * signal handling.
915 *
916 * <ol>
917 * <li>If the null signal is sent, run kill -0 on the pid. This is used
918 * to check if the container is still alive, which is necessary for
919 * reacquiring containers on NM restart.</li>
920 * <li>If SIGTERM, SIGKILL is sent, attempt to stop and remove the docker
921 * container.</li>
922 * <li>If the docker container exists and is running, execute docker
923 * stop.</li>
924 * <li>If any other signal is sent, signal the container using docker
925 * kill.</li>
926 * </ol>
927 *
928 * @param ctx the {@link ContainerRuntimeContext}.
929 * @throws ContainerExecutionException if the signaling fails.
930 */
931 @Override
932 public void signalContainer(ContainerRuntimeContext ctx)
933 throws ContainerExecutionException {
934 ContainerExecutor.Signal signal = ctx.getExecutionAttribute(SIGNAL);
935 String containerId = ctx.getContainer().getContainerId().toString();
936 Map<String, String> env =
937 ctx.getContainer().getLaunchContext().getEnvironment();
938 try {
939 if (ContainerExecutor.Signal.NULL.equals(signal)) {
940 executeLivelinessCheck(ctx);
941 } else {
942 if (ContainerExecutor.Signal.KILL.equals(signal)
943 || ContainerExecutor.Signal.TERM.equals(signal)) {
944 handleContainerStop(containerId, env);
945 } else {
946 handleContainerKill(containerId, env, signal);
947 }
948 }
949 } catch (ContainerExecutionException e) {
950 LOG.warn("Signal docker container failed. Exception: ", e);
951 throw new ContainerExecutionException("Signal docker container failed",
952 e.getExitCode(), e.getOutput(), e.getErrorOutput());
953 }
954 }
955
956 /**
957 * Reap the docker container.
958 *
959 * @param ctx the {@link ContainerRuntimeContext}.
960 * @throws ContainerExecutionException if the removal fails.
961 */
962 @Override
963 public void reapContainer(ContainerRuntimeContext ctx)
964 throws ContainerExecutionException {
965 // Clean up the Docker container
966 handleContainerRemove(ctx.getContainer().getContainerId().toString(),
967 ctx.getContainer().getLaunchContext().getEnvironment());
968
969 // Cleanup volumes when needed.
970 if (nmContext != null
971 && nmContext.getResourcePluginManager().getNameToPlugins() != null) {
972 for (ResourcePlugin plugin : nmContext.getResourcePluginManager()
973 .getNameToPlugins().values()) {
974 DockerCommandPlugin dockerCommandPlugin =
975 plugin.getDockerCommandPluginInstance();
976 if (dockerCommandPlugin != null) {
977 DockerVolumeCommand dockerVolumeCommand =
978 dockerCommandPlugin.getCleanupDockerVolumesCommand(
979 ctx.getContainer());
980 if (dockerVolumeCommand != null) {
981 runDockerVolumeCommand(dockerVolumeCommand, ctx.getContainer());
982 }
983 }
984 }
985 }
986 }
987
988
989 // ipAndHost[0] contains comma separated list of IPs
990 // ipAndHost[1] contains the hostname.
991 @Override
992 public String[] getIpAndHost(Container container) {
993 String containerId = container.getContainerId().toString();
994 DockerInspectCommand inspectCommand =
995 new DockerInspectCommand(containerId).getIpAndHost();
996 try {
997 String commandFile = dockerClient.writeCommandToTempFile(inspectCommand,
998 containerId);
999 PrivilegedOperation privOp = new PrivilegedOperation(
1000 PrivilegedOperation.OperationType.RUN_DOCKER_CMD);
1001 privOp.appendArgs(commandFile);
1002 String output = privilegedOperationExecutor
1003 .executePrivilegedOperation(null, privOp, null,
1004 null, true, false);
1005 LOG.info("Docker inspect output for " + containerId + ": " + output);
1006 // strip off quotes if any
1007 output = output.replaceAll("['\"]", "");
1008 int index = output.lastIndexOf(',');
1009 if (index == -1) {
1010 LOG.error("Incorrect format for ip and host");
1011 return null;
1012 }
1013 String ips = output.substring(0, index).trim();
1014 String host = output.substring(index+1).trim();
1015 if (ips.equals("")) {
1016 String network;
1017 try {
1018 network = container.getLaunchContext().getEnvironment()
1019 .get("YARN_CONTAINER_RUNTIME_DOCKER_CONTAINER_NETWORK");
1020 if (network == null || network.isEmpty()) {
1021 network = defaultNetwork;
1022 }
1023 } catch (NullPointerException e) {
1024 network = defaultNetwork;
1025 }
1026 boolean useHostNetwork = network.equalsIgnoreCase("host");
1027 if (useHostNetwork) {
1028 // Report back node manager IP in the event where docker
1029 // inspect reports no IP address. This is for bridging a gap for
1030 // docker environment to run with host network.
1031 InetAddress address;
1032 try {
1033 address = InetAddress.getLocalHost();
1034 ips = address.getHostAddress();
1035 } catch (UnknownHostException e) {
1036 LOG.error("Can not determine IP for container:"
1037 + containerId);
1038 }
1039 }
1040 }
1041 String[] ipAndHost = new String[2];
1042 ipAndHost[0] = ips;
1043 ipAndHost[1] = host;
1044 return ipAndHost;
1045 } catch (ContainerExecutionException e) {
1046 LOG.error("Error when writing command to temp file", e);
1047 } catch (PrivilegedOperationException e) {
1048 LOG.error("Error when executing command.", e);
1049 }
1050 return null;
1051 }
1052
1053
1054
1055 private PrivilegedOperation buildLaunchOp(ContainerRuntimeContext ctx,
1056 String commandFile, DockerRunCommand runCommand) {
1057
1058 String runAsUser = ctx.getExecutionAttribute(RUN_AS_USER);
1059 String containerIdStr = ctx.getContainer().getContainerId().toString();
1060 Path nmPrivateContainerScriptPath = ctx.getExecutionAttribute(
1061 NM_PRIVATE_CONTAINER_SCRIPT_PATH);
1062 Path containerWorkDir = ctx.getExecutionAttribute(CONTAINER_WORK_DIR);
1063 //we can't do better here thanks to type-erasure
1064 @SuppressWarnings("unchecked")
1065 List<String> localDirs = ctx.getExecutionAttribute(LOCAL_DIRS);
1066 @SuppressWarnings("unchecked")
1067 List<String> logDirs = ctx.getExecutionAttribute(LOG_DIRS);
1068 String resourcesOpts = ctx.getExecutionAttribute(RESOURCES_OPTIONS);
1069
1070 PrivilegedOperation launchOp = new PrivilegedOperation(
1071 PrivilegedOperation.OperationType.LAUNCH_DOCKER_CONTAINER);
1072
1073 launchOp.appendArgs(runAsUser, ctx.getExecutionAttribute(USER),
1074 Integer.toString(PrivilegedOperation
1075 .RunAsUserCommand.LAUNCH_DOCKER_CONTAINER.getValue()),
1076 ctx.getExecutionAttribute(APPID),
1077 containerIdStr,
1078 containerWorkDir.toString(),
1079 nmPrivateContainerScriptPath.toUri().getPath(),
1080 ctx.getExecutionAttribute(NM_PRIVATE_TOKENS_PATH).toUri().getPath(),
1081 ctx.getExecutionAttribute(PID_FILE_PATH).toString(),
1082 StringUtils.join(PrivilegedOperation.LINUX_FILE_PATH_SEPARATOR,
1083 localDirs),
1084 StringUtils.join(PrivilegedOperation.LINUX_FILE_PATH_SEPARATOR,
1085 logDirs),
1086 commandFile,
1087 resourcesOpts);
1088
1089 String tcCommandFile = ctx.getExecutionAttribute(TC_COMMAND_FILE);
1090
1091 if (tcCommandFile != null) {
1092 launchOp.appendArgs(tcCommandFile);
1093 }
1094 if (LOG.isDebugEnabled()) {
1095 LOG.debug("Launching container with cmd: " + runCommand);
1096 }
1097
1098 return launchOp;
1099 }
1100
1101 public static void validateImageName(String imageName)
1102 throws ContainerExecutionException {
1103 if (imageName == null || imageName.isEmpty()) {
1104 throw new ContainerExecutionException(
1105 ENV_DOCKER_CONTAINER_IMAGE + " not set!");
1106 }
1107 if (!dockerImagePattern.matcher(imageName).matches()) {
1108 throw new ContainerExecutionException("Image name '" + imageName
1109 + "' doesn't match docker image name pattern");
1110 }
1111 }
1112
1113 private void executeLivelinessCheck(ContainerRuntimeContext ctx)
1114 throws ContainerExecutionException {
1115 PrivilegedOperation signalOp = new PrivilegedOperation(
1116 PrivilegedOperation.OperationType.SIGNAL_CONTAINER);
1117 signalOp.appendArgs(ctx.getExecutionAttribute(RUN_AS_USER),
1118 ctx.getExecutionAttribute(USER), Integer.toString(
1119 PrivilegedOperation.RunAsUserCommand.SIGNAL_CONTAINER.getValue()),
1120 ctx.getExecutionAttribute(PID),
1121 Integer.toString(ctx.getExecutionAttribute(SIGNAL).getValue()));
1122 signalOp.disableFailureLogging();
1123 try {
1124 privilegedOperationExecutor.executePrivilegedOperation(null, signalOp,
1125 null, ctx.getContainer().getLaunchContext().getEnvironment(), false,
1126 false);
1127 } catch (PrivilegedOperationException e) {
1128 String msg = "Liveliness check failed for PID: "
1129 + ctx.getExecutionAttribute(PID)
1130 + ". Container may have already completed.";
1131 throw new ContainerExecutionException(msg, e.getExitCode(), e.getOutput(),
1132 e.getErrorOutput());
1133 }
1134 }
1135
1136 private void handleContainerStop(String containerId, Map<String, String> env)
1137 throws ContainerExecutionException {
1138 DockerCommandExecutor.DockerContainerStatus containerStatus =
1139 DockerCommandExecutor.getContainerStatus(containerId, conf,
1140 privilegedOperationExecutor);
1141 if (DockerCommandExecutor.isStoppable(containerStatus)) {
1142 DockerStopCommand dockerStopCommand = new DockerStopCommand(containerId);
1143 DockerCommandExecutor.executeDockerCommand(dockerStopCommand, containerId,
1144 env, conf, privilegedOperationExecutor, false);
1145 } else {
1146 if (LOG.isDebugEnabled()) {
1147 LOG.debug(
1148 "Container status is " + containerStatus.getName()
1149 + ", skipping stop - " + containerId);
1150 }
1151 }
1152 }
1153
1154 private void handleContainerKill(String containerId, Map<String, String> env,
1155 ContainerExecutor.Signal signal) throws ContainerExecutionException {
1156 DockerCommandExecutor.DockerContainerStatus containerStatus =
1157 DockerCommandExecutor.getContainerStatus(containerId, conf,
1158 privilegedOperationExecutor);
1159 if (DockerCommandExecutor.isKillable(containerStatus)) {
1160 DockerKillCommand dockerKillCommand =
1161 new DockerKillCommand(containerId).setSignal(signal.name());
1162 DockerCommandExecutor.executeDockerCommand(dockerKillCommand, containerId,
1163 env, conf, privilegedOperationExecutor, false);
1164 } else {
1165 if (LOG.isDebugEnabled()) {
1166 LOG.debug(
1167 "Container status is " + containerStatus.getName()
1168 + ", skipping kill - " + containerId);
1169 }
1170 }
1171 }
1172
1173 private void handleContainerRemove(String containerId,
1174 Map<String, String> env) throws ContainerExecutionException {
1175 String delayedRemoval = env.get(ENV_DOCKER_CONTAINER_DELAYED_REMOVAL);
1176 if (delayedRemovalAllowed && delayedRemoval != null
1177 && delayedRemoval.equalsIgnoreCase("true")) {
1178 LOG.info("Delayed removal requested and allowed, skipping removal - "
1179 + containerId);
1180 } else {
1181 DockerCommandExecutor.DockerContainerStatus containerStatus =
1182 DockerCommandExecutor.getContainerStatus(containerId, conf,
1183 privilegedOperationExecutor);
1184 if (DockerCommandExecutor.isRemovable(containerStatus)) {
1185 DockerRmCommand dockerRmCommand = new DockerRmCommand(containerId);
1186 DockerCommandExecutor.executeDockerCommand(dockerRmCommand, containerId,
1187 env, conf, privilegedOperationExecutor, false);
1188 }
1189 }
1190 }
1191
1192 private void addDockerClientConfigToRunCommand(ContainerRuntimeContext ctx,
1193 DockerRunCommand dockerRunCommand) throws ContainerExecutionException {
1194 ByteBuffer tokens = ctx.getContainer().getLaunchContext().getTokens();
1195 Credentials credentials;
1196 if (tokens != null) {
1197 tokens.rewind();
1198 if (tokens.hasRemaining()) {
1199 try {
1200 credentials = DockerClientConfigHandler
1201 .getCredentialsFromTokensByteBuffer(tokens);
1202 } catch (IOException e) {
1203 throw new ContainerExecutionException("Unable to read tokens.");
1204 }
1205 if (credentials.numberOfTokens() > 0) {
1206 Path nmPrivateDir =
1207 ctx.getExecutionAttribute(NM_PRIVATE_CONTAINER_SCRIPT_PATH)
1208 .getParent();
1209 File dockerConfigPath = new File(nmPrivateDir + "/config.json");
1210 try {
1211 DockerClientConfigHandler
1212 .writeDockerCredentialsToPath(dockerConfigPath, credentials);
1213 } catch (IOException e) {
1214 throw new ContainerExecutionException(
1215 "Unable to write Docker client credentials to "
1216 + dockerConfigPath);
1217 }
1218 dockerRunCommand.setClientConfigDir(dockerConfigPath.getParent());
1219 }
1220 }
1221 }
1222 }
1223 }