YARN-1972. Added a secure container-executor for Windows. Contributed by Remus Rusanu.
commit ba7f31c2ee
is the corresponding trunk commit, this is a slightly different patch for branch-2.
This commit is contained in:
parent
625456746c
commit
3326fba382
|
@ -94,6 +94,9 @@ Release 2.6.0 - UNRELEASED
|
||||||
YARN-2613. Support retry in NMClient for rolling-upgrades. (Jian He via
|
YARN-2613. Support retry in NMClient for rolling-upgrades. (Jian He via
|
||||||
junping_du)
|
junping_du)
|
||||||
|
|
||||||
|
YARN-1972. Added a secure container-executor for Windows. (Remus Rusanu via
|
||||||
|
vinodkv)
|
||||||
|
|
||||||
IMPROVEMENTS
|
IMPROVEMENTS
|
||||||
|
|
||||||
YARN-2242. Improve exception information on AM launch crashes. (Li Lu
|
YARN-2242. Improve exception information on AM launch crashes. (Li Lu
|
||||||
|
|
|
@ -929,6 +929,12 @@ public class YarnConfiguration extends Configuration {
|
||||||
public static final long DEFAULT_NM_LINUX_CONTAINER_CGROUPS_DELETE_TIMEOUT =
|
public static final long DEFAULT_NM_LINUX_CONTAINER_CGROUPS_DELETE_TIMEOUT =
|
||||||
1000;
|
1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
/* The Windows group that the windows-secure-container-executor should run as.
|
||||||
|
*/
|
||||||
|
public static final String NM_WINDOWS_SECURE_CONTAINER_GROUP =
|
||||||
|
NM_PREFIX + "windows-secure-container-executor.group";
|
||||||
|
|
||||||
/** T-file compression types used to compress aggregated logs.*/
|
/** T-file compression types used to compress aggregated logs.*/
|
||||||
public static final String NM_LOG_AGG_COMPRESSION_TYPE =
|
public static final String NM_LOG_AGG_COMPRESSION_TYPE =
|
||||||
NM_PREFIX + "log-aggregation.compression-type";
|
NM_PREFIX + "log-aggregation.compression-type";
|
||||||
|
|
|
@ -78,6 +78,20 @@ public abstract class ContainerExecutor implements Configurable {
|
||||||
*/
|
*/
|
||||||
public abstract void init() throws IOException;
|
public abstract void init() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On Windows the ContainerLaunch creates a temporary empty jar to workaround the CLASSPATH length
|
||||||
|
* In a secure cluster this jar must be localized so that the container has access to it
|
||||||
|
* This function localizes on-demand the jar.
|
||||||
|
*
|
||||||
|
* @param classPathJar
|
||||||
|
* @param owner
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public void localizeClasspathJar(Path classPathJar, String owner) throws IOException {
|
||||||
|
// For the default container this is a no-op
|
||||||
|
// The WindowsSecureContainerExecutor overrides this
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare the environment for containers in this application to execute.
|
* Prepare the environment for containers in this application to execute.
|
||||||
* For $x in local.dirs
|
* For $x in local.dirs
|
||||||
|
@ -264,8 +278,8 @@ public abstract class ContainerExecutor implements Configurable {
|
||||||
* and associate the given groupId in a process group. On
|
* and associate the given groupId in a process group. On
|
||||||
* non-Windows, groupId is ignored.
|
* non-Windows, groupId is ignored.
|
||||||
*/
|
*/
|
||||||
protected static String[] getRunCommand(String command, String groupId,
|
protected String[] getRunCommand(String command, String groupId,
|
||||||
Configuration conf) {
|
String userName, Path pidFile, Configuration conf) {
|
||||||
int containerSchedPriorityAdjustment =
|
int containerSchedPriorityAdjustment =
|
||||||
YarnConfiguration.DEFAULT_NM_CONTAINER_EXECUTOR_SCHED_PRIORITY;
|
YarnConfiguration.DEFAULT_NM_CONTAINER_EXECUTOR_SCHED_PRIORITY;
|
||||||
if (conf.get(YarnConfiguration.NM_CONTAINER_EXECUTOR_SCHED_PRIORITY) !=
|
if (conf.get(YarnConfiguration.NM_CONTAINER_EXECUTOR_SCHED_PRIORITY) !=
|
||||||
|
@ -389,5 +403,4 @@ public abstract class ContainerExecutor implements Configurable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
|
|
||||||
private static final int WIN_MAX_PATH = 260;
|
private static final int WIN_MAX_PATH = 260;
|
||||||
|
|
||||||
private final FileContext lfs;
|
protected final FileContext lfs;
|
||||||
|
|
||||||
public DefaultContainerExecutor() {
|
public DefaultContainerExecutor() {
|
||||||
try {
|
try {
|
||||||
|
@ -75,6 +75,14 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
this.lfs = lfs;
|
this.lfs = lfs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void copyFile(Path src, Path dst, String owner) throws IOException {
|
||||||
|
lfs.util().copy(src, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setScriptExecutable(Path script, String owner) throws IOException {
|
||||||
|
lfs.setPermission(script, ContainerExecutor.TASK_LAUNCH_SCRIPT_PERMISSION);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init() throws IOException {
|
public void init() throws IOException {
|
||||||
// nothing to do or verify here
|
// nothing to do or verify here
|
||||||
|
@ -93,14 +101,14 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
createUserLocalDirs(localDirs, user);
|
createUserLocalDirs(localDirs, user);
|
||||||
createUserCacheDirs(localDirs, user);
|
createUserCacheDirs(localDirs, user);
|
||||||
createAppDirs(localDirs, user, appId);
|
createAppDirs(localDirs, user, appId);
|
||||||
createAppLogDirs(appId, logDirs);
|
createAppLogDirs(appId, logDirs, user);
|
||||||
|
|
||||||
// TODO: Why pick first app dir. The same in LCE why not random?
|
// TODO: Why pick first app dir. The same in LCE why not random?
|
||||||
Path appStorageDir = getFirstApplicationDir(localDirs, user, appId);
|
Path appStorageDir = getFirstApplicationDir(localDirs, user, appId);
|
||||||
|
|
||||||
String tokenFn = String.format(ContainerLocalizer.TOKEN_FILE_NAME_FMT, locId);
|
String tokenFn = String.format(ContainerLocalizer.TOKEN_FILE_NAME_FMT, locId);
|
||||||
Path tokenDst = new Path(appStorageDir, tokenFn);
|
Path tokenDst = new Path(appStorageDir, tokenFn);
|
||||||
lfs.util().copy(nmPrivateContainerTokensPath, tokenDst);
|
copyFile(nmPrivateContainerTokensPath, tokenDst, user);
|
||||||
LOG.info("Copying from " + nmPrivateContainerTokensPath + " to " + tokenDst);
|
LOG.info("Copying from " + nmPrivateContainerTokensPath + " to " + tokenDst);
|
||||||
lfs.setWorkingDirectory(appStorageDir);
|
lfs.setWorkingDirectory(appStorageDir);
|
||||||
LOG.info("CWD set to " + appStorageDir + " = " + lfs.getWorkingDirectory());
|
LOG.info("CWD set to " + appStorageDir + " = " + lfs.getWorkingDirectory());
|
||||||
|
@ -129,30 +137,29 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
Path appCacheDir = new Path(userdir, ContainerLocalizer.APPCACHE);
|
Path appCacheDir = new Path(userdir, ContainerLocalizer.APPCACHE);
|
||||||
Path appDir = new Path(appCacheDir, appIdStr);
|
Path appDir = new Path(appCacheDir, appIdStr);
|
||||||
Path containerDir = new Path(appDir, containerIdStr);
|
Path containerDir = new Path(appDir, containerIdStr);
|
||||||
createDir(containerDir, dirPerm, true);
|
createDir(containerDir, dirPerm, true, userName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the container log-dirs on all disks
|
// Create the container log-dirs on all disks
|
||||||
createContainerLogDirs(appIdStr, containerIdStr, logDirs);
|
createContainerLogDirs(appIdStr, containerIdStr, logDirs, userName);
|
||||||
|
|
||||||
Path tmpDir = new Path(containerWorkDir,
|
Path tmpDir = new Path(containerWorkDir,
|
||||||
YarnConfiguration.DEFAULT_CONTAINER_TEMP_DIR);
|
YarnConfiguration.DEFAULT_CONTAINER_TEMP_DIR);
|
||||||
createDir(tmpDir, dirPerm, false);
|
createDir(tmpDir, dirPerm, false, userName);
|
||||||
|
|
||||||
// copy launch script to work dir
|
// copy launch script to work dir
|
||||||
Path launchDst =
|
Path launchDst =
|
||||||
new Path(containerWorkDir, ContainerLaunch.CONTAINER_SCRIPT);
|
new Path(containerWorkDir, ContainerLaunch.CONTAINER_SCRIPT);
|
||||||
lfs.util().copy(nmPrivateContainerScriptPath, launchDst);
|
copyFile(nmPrivateContainerScriptPath, launchDst, userName);
|
||||||
|
|
||||||
// copy container tokens to work dir
|
// copy container tokens to work dir
|
||||||
Path tokenDst =
|
Path tokenDst =
|
||||||
new Path(containerWorkDir, ContainerLaunch.FINAL_CONTAINER_TOKENS_FILE);
|
new Path(containerWorkDir, ContainerLaunch.FINAL_CONTAINER_TOKENS_FILE);
|
||||||
lfs.util().copy(nmPrivateTokensPath, tokenDst);
|
copyFile(nmPrivateTokensPath, tokenDst, userName);
|
||||||
|
|
||||||
// Create new local launch wrapper script
|
// Create new local launch wrapper script
|
||||||
LocalWrapperScriptBuilder sb = Shell.WINDOWS ?
|
LocalWrapperScriptBuilder sb = getLocalWrapperScriptBuilder(
|
||||||
new WindowsLocalWrapperScriptBuilder(containerIdStr, containerWorkDir) :
|
containerIdStr, containerWorkDir);
|
||||||
new UnixLocalWrapperScriptBuilder(containerWorkDir);
|
|
||||||
|
|
||||||
// Fail fast if attempting to launch the wrapper script would fail due to
|
// Fail fast if attempting to launch the wrapper script would fail due to
|
||||||
// Windows path length limitation.
|
// Windows path length limitation.
|
||||||
|
@ -178,14 +185,12 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
// fork script
|
// fork script
|
||||||
ShellCommandExecutor shExec = null;
|
ShellCommandExecutor shExec = null;
|
||||||
try {
|
try {
|
||||||
lfs.setPermission(launchDst,
|
setScriptExecutable(launchDst, userName);
|
||||||
ContainerExecutor.TASK_LAUNCH_SCRIPT_PERMISSION);
|
setScriptExecutable(sb.getWrapperScriptPath(), userName);
|
||||||
lfs.setPermission(sb.getWrapperScriptPath(),
|
|
||||||
ContainerExecutor.TASK_LAUNCH_SCRIPT_PERMISSION);
|
|
||||||
|
|
||||||
// Setup command to run
|
// Setup command to run
|
||||||
String[] command = getRunCommand(sb.getWrapperScriptPath().toString(),
|
String[] command = getRunCommand(sb.getWrapperScriptPath().toString(),
|
||||||
containerIdStr, this.getConf());
|
containerIdStr, userName, pidFile, this.getConf());
|
||||||
|
|
||||||
LOG.info("launchContainer: " + Arrays.toString(command));
|
LOG.info("launchContainer: " + Arrays.toString(command));
|
||||||
shExec = new ShellCommandExecutor(
|
shExec = new ShellCommandExecutor(
|
||||||
|
@ -241,7 +246,14 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class LocalWrapperScriptBuilder {
|
protected LocalWrapperScriptBuilder getLocalWrapperScriptBuilder(
|
||||||
|
String containerIdStr, Path containerWorkDir) {
|
||||||
|
return Shell.WINDOWS ?
|
||||||
|
new WindowsLocalWrapperScriptBuilder(containerIdStr, containerWorkDir) :
|
||||||
|
new UnixLocalWrapperScriptBuilder(containerWorkDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract class LocalWrapperScriptBuilder {
|
||||||
|
|
||||||
private final Path wrapperScriptPath;
|
private final Path wrapperScriptPath;
|
||||||
|
|
||||||
|
@ -449,7 +461,7 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
* $logdir/$user/$appId */
|
* $logdir/$user/$appId */
|
||||||
static final short LOGDIR_PERM = (short)0710;
|
static final short LOGDIR_PERM = (short)0710;
|
||||||
|
|
||||||
private Path getFirstApplicationDir(List<String> localDirs, String user,
|
protected Path getFirstApplicationDir(List<String> localDirs, String user,
|
||||||
String appId) {
|
String appId) {
|
||||||
return getApplicationDir(new Path(localDirs.get(0)), user, appId);
|
return getApplicationDir(new Path(localDirs.get(0)), user, appId);
|
||||||
}
|
}
|
||||||
|
@ -472,8 +484,8 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
ContainerLocalizer.FILECACHE);
|
ContainerLocalizer.FILECACHE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createDir(Path dirPath, FsPermission perms,
|
protected void createDir(Path dirPath, FsPermission perms,
|
||||||
boolean createParent) throws IOException {
|
boolean createParent, String user) throws IOException {
|
||||||
lfs.mkdir(dirPath, perms, createParent);
|
lfs.mkdir(dirPath, perms, createParent);
|
||||||
if (!perms.equals(perms.applyUMask(lfs.getUMask()))) {
|
if (!perms.equals(perms.applyUMask(lfs.getUMask()))) {
|
||||||
lfs.setPermission(dirPath, perms);
|
lfs.setPermission(dirPath, perms);
|
||||||
|
@ -493,7 +505,7 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
for (String localDir : localDirs) {
|
for (String localDir : localDirs) {
|
||||||
// create $local.dir/usercache/$user and its immediate parent
|
// create $local.dir/usercache/$user and its immediate parent
|
||||||
try {
|
try {
|
||||||
createDir(getUserCacheDir(new Path(localDir), user), userperms, true);
|
createDir(getUserCacheDir(new Path(localDir), user), userperms, true, user);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Unable to create the user directory : " + localDir, e);
|
LOG.warn("Unable to create the user directory : " + localDir, e);
|
||||||
continue;
|
continue;
|
||||||
|
@ -529,7 +541,7 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
Path localDirPath = new Path(localDir);
|
Path localDirPath = new Path(localDir);
|
||||||
final Path appDir = getAppcacheDir(localDirPath, user);
|
final Path appDir = getAppcacheDir(localDirPath, user);
|
||||||
try {
|
try {
|
||||||
createDir(appDir, appCachePerms, true);
|
createDir(appDir, appCachePerms, true, user);
|
||||||
appcacheDirStatus = true;
|
appcacheDirStatus = true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Unable to create app cache directory : " + appDir, e);
|
LOG.warn("Unable to create app cache directory : " + appDir, e);
|
||||||
|
@ -537,7 +549,7 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
// create $local.dir/usercache/$user/filecache
|
// create $local.dir/usercache/$user/filecache
|
||||||
final Path distDir = getFileCacheDir(localDirPath, user);
|
final Path distDir = getFileCacheDir(localDirPath, user);
|
||||||
try {
|
try {
|
||||||
createDir(distDir, fileperms, true);
|
createDir(distDir, fileperms, true, user);
|
||||||
distributedCacheDirStatus = true;
|
distributedCacheDirStatus = true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Unable to create file cache directory : " + distDir, e);
|
LOG.warn("Unable to create file cache directory : " + distDir, e);
|
||||||
|
@ -570,7 +582,7 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
Path fullAppDir = getApplicationDir(new Path(localDir), user, appId);
|
Path fullAppDir = getApplicationDir(new Path(localDir), user, appId);
|
||||||
// create $local.dir/usercache/$user/appcache/$appId
|
// create $local.dir/usercache/$user/appcache/$appId
|
||||||
try {
|
try {
|
||||||
createDir(fullAppDir, appperms, true);
|
createDir(fullAppDir, appperms, true, user);
|
||||||
initAppDirStatus = true;
|
initAppDirStatus = true;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Unable to create app directory " + fullAppDir.toString(), e);
|
LOG.warn("Unable to create app directory " + fullAppDir.toString(), e);
|
||||||
|
@ -586,7 +598,7 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
/**
|
/**
|
||||||
* Create application log directories on all disks.
|
* Create application log directories on all disks.
|
||||||
*/
|
*/
|
||||||
void createAppLogDirs(String appId, List<String> logDirs)
|
void createAppLogDirs(String appId, List<String> logDirs, String user)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
boolean appLogDirStatus = false;
|
boolean appLogDirStatus = false;
|
||||||
|
@ -595,7 +607,7 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
// create $log.dir/$appid
|
// create $log.dir/$appid
|
||||||
Path appLogDir = new Path(rootLogDir, appId);
|
Path appLogDir = new Path(rootLogDir, appId);
|
||||||
try {
|
try {
|
||||||
createDir(appLogDir, appLogDirPerms, true);
|
createDir(appLogDir, appLogDirPerms, true, user);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Unable to create the app-log directory : " + appLogDir, e);
|
LOG.warn("Unable to create the app-log directory : " + appLogDir, e);
|
||||||
continue;
|
continue;
|
||||||
|
@ -612,7 +624,7 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
* Create application log directories on all disks.
|
* Create application log directories on all disks.
|
||||||
*/
|
*/
|
||||||
void createContainerLogDirs(String appId, String containerId,
|
void createContainerLogDirs(String appId, String containerId,
|
||||||
List<String> logDirs) throws IOException {
|
List<String> logDirs, String user) throws IOException {
|
||||||
|
|
||||||
boolean containerLogDirStatus = false;
|
boolean containerLogDirStatus = false;
|
||||||
FsPermission containerLogDirPerms = new FsPermission(LOGDIR_PERM);
|
FsPermission containerLogDirPerms = new FsPermission(LOGDIR_PERM);
|
||||||
|
@ -621,7 +633,7 @@ public class DefaultContainerExecutor extends ContainerExecutor {
|
||||||
Path appLogDir = new Path(rootLogDir, appId);
|
Path appLogDir = new Path(rootLogDir, appId);
|
||||||
Path containerLogDir = new Path(appLogDir, containerId);
|
Path containerLogDir = new Path(appLogDir, containerId);
|
||||||
try {
|
try {
|
||||||
createDir(containerLogDir, containerLogDirPerms, true);
|
createDir(containerLogDir, containerLogDirPerms, true, user);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOG.warn("Unable to create the container-log directory : "
|
LOG.warn("Unable to create the container-log directory : "
|
||||||
+ appLogDir, e);
|
+ appLogDir, e);
|
||||||
|
|
|
@ -218,15 +218,7 @@ public class LinuxContainerExecutor extends ContainerExecutor {
|
||||||
if (javaLibPath != null) {
|
if (javaLibPath != null) {
|
||||||
command.add("-Djava.library.path=" + javaLibPath);
|
command.add("-Djava.library.path=" + javaLibPath);
|
||||||
}
|
}
|
||||||
command.add(ContainerLocalizer.class.getName());
|
ContainerLocalizer.buildMainArgs(command, user, appId, locId, nmAddr, localDirs);
|
||||||
command.add(user);
|
|
||||||
command.add(appId);
|
|
||||||
command.add(locId);
|
|
||||||
command.add(nmAddr.getHostName());
|
|
||||||
command.add(Integer.toString(nmAddr.getPort()));
|
|
||||||
for (String dir : localDirs) {
|
|
||||||
command.add(dir);
|
|
||||||
}
|
|
||||||
String[] commandArray = command.toArray(new String[command.size()]);
|
String[] commandArray = command.toArray(new String[command.size()]);
|
||||||
ShellCommandExecutor shExec = new ShellCommandExecutor(commandArray);
|
ShellCommandExecutor shExec = new ShellCommandExecutor(commandArray);
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.hadoop.yarn.server.nodemanager;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.fs.FileUtil;
|
||||||
|
import org.apache.hadoop.fs.Path;
|
||||||
|
import org.apache.hadoop.fs.permission.FsPermission;
|
||||||
|
import org.apache.hadoop.util.Shell;
|
||||||
|
import org.apache.hadoop.util.Shell.ShellCommandExecutor;
|
||||||
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.DefaultContainerExecutor.LocalWrapperScriptBuilder;
|
||||||
|
import org.apache.hadoop.yarn.server.nodemanager.containermanager.localizer.ContainerLocalizer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows secure container executor. Uses winutils task createAsUser.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class WindowsSecureContainerExecutor extends DefaultContainerExecutor {
|
||||||
|
|
||||||
|
private static final Log LOG = LogFactory
|
||||||
|
.getLog(WindowsSecureContainerExecutor.class);
|
||||||
|
|
||||||
|
private class WindowsSecureWrapperScriptBuilder
|
||||||
|
extends LocalWrapperScriptBuilder {
|
||||||
|
|
||||||
|
public WindowsSecureWrapperScriptBuilder(Path containerWorkDir) {
|
||||||
|
super(containerWorkDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void writeLocalWrapperScript(Path launchDst, Path pidFile, PrintStream pout) {
|
||||||
|
pout.format("@call \"%s\"", launchDst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String nodeManagerGroup;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setConf(Configuration conf) {
|
||||||
|
super.setConf(conf);
|
||||||
|
nodeManagerGroup = conf.get(YarnConfiguration.NM_WINDOWS_SECURE_CONTAINER_GROUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String[] getRunCommand(String command, String groupId,
|
||||||
|
String userName, Path pidFile, Configuration conf) {
|
||||||
|
return new String[] { Shell.WINUTILS, "task", "createAsUser", groupId, userName,
|
||||||
|
pidFile.toString(), "cmd /c " + command };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected LocalWrapperScriptBuilder getLocalWrapperScriptBuilder(
|
||||||
|
String containerIdStr, Path containerWorkDir) {
|
||||||
|
return new WindowsSecureWrapperScriptBuilder(containerWorkDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void copyFile(Path src, Path dst, String owner) throws IOException {
|
||||||
|
super.copyFile(src, dst, owner);
|
||||||
|
lfs.setOwner(dst, owner, nodeManagerGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void createDir(Path dirPath, FsPermission perms,
|
||||||
|
boolean createParent, String owner) throws IOException {
|
||||||
|
super.createDir(dirPath, perms, createParent, owner);
|
||||||
|
lfs.setOwner(dirPath, owner, nodeManagerGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setScriptExecutable(Path script, String owner) throws IOException {
|
||||||
|
super.setScriptExecutable(script, null);
|
||||||
|
lfs.setOwner(script, owner, nodeManagerGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void localizeClasspathJar(Path classpathJar, String owner) throws IOException {
|
||||||
|
lfs.setOwner(classpathJar, owner, nodeManagerGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startLocalizer(Path nmPrivateContainerTokens,
|
||||||
|
InetSocketAddress nmAddr, String user, String appId, String locId,
|
||||||
|
List<String> localDirs, List<String> logDirs) throws IOException,
|
||||||
|
InterruptedException {
|
||||||
|
|
||||||
|
createUserLocalDirs(localDirs, user);
|
||||||
|
createUserCacheDirs(localDirs, user);
|
||||||
|
createAppDirs(localDirs, user, appId);
|
||||||
|
createAppLogDirs(appId, logDirs, user);
|
||||||
|
|
||||||
|
// TODO: Why pick first app dir. The same in LCE why not random?
|
||||||
|
Path appStorageDir = getFirstApplicationDir(localDirs, user, appId);
|
||||||
|
|
||||||
|
String tokenFn = String.format(ContainerLocalizer.TOKEN_FILE_NAME_FMT, locId);
|
||||||
|
Path tokenDst = new Path(appStorageDir, tokenFn);
|
||||||
|
LOG.info("Copying from " + nmPrivateContainerTokens + " to " + tokenDst);
|
||||||
|
copyFile(nmPrivateContainerTokens, tokenDst, user);
|
||||||
|
|
||||||
|
List<String> command ;
|
||||||
|
String[] commandArray;
|
||||||
|
ShellCommandExecutor shExec;
|
||||||
|
|
||||||
|
File cwdApp = new File(appStorageDir.toString());
|
||||||
|
LOG.info(String.format("cwdApp: %s", cwdApp));
|
||||||
|
|
||||||
|
command = new ArrayList<String>();
|
||||||
|
|
||||||
|
command.add(Shell.WINUTILS);
|
||||||
|
command.add("task");
|
||||||
|
command.add("createAsUser");
|
||||||
|
command.add("START_LOCALIZER_" + locId);
|
||||||
|
command.add(user);
|
||||||
|
command.add("nul:"); // PID file
|
||||||
|
|
||||||
|
//use same jvm as parent
|
||||||
|
File jvm = new File(new File(System.getProperty("java.home"), "bin"), "java.exe");
|
||||||
|
command.add(jvm.toString());
|
||||||
|
|
||||||
|
|
||||||
|
// Build a temp classpath jar. See ContainerLaunch.sanitizeEnv().
|
||||||
|
// Passing CLASSPATH explicitly is *way* too long for command line.
|
||||||
|
String classPath = System.getProperty("java.class.path");
|
||||||
|
Map<String, String> env = new HashMap<String, String>(System.getenv());
|
||||||
|
String classPathJar = FileUtil.createJarWithClassPath(classPath, appStorageDir, env);
|
||||||
|
localizeClasspathJar(new Path(classPathJar), user);
|
||||||
|
command.add("-classpath");
|
||||||
|
command.add(classPathJar);
|
||||||
|
|
||||||
|
String javaLibPath = System.getProperty("java.library.path");
|
||||||
|
if (javaLibPath != null) {
|
||||||
|
command.add("-Djava.library.path=" + javaLibPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
ContainerLocalizer.buildMainArgs(command, user, appId, locId, nmAddr, localDirs);
|
||||||
|
commandArray = command.toArray(new String[command.size()]);
|
||||||
|
|
||||||
|
shExec = new ShellCommandExecutor(
|
||||||
|
commandArray, cwdApp);
|
||||||
|
|
||||||
|
shExec.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -766,6 +766,8 @@ public class ContainerLaunch implements Callable<Integer> {
|
||||||
|
|
||||||
String classPathJar = FileUtil.createJarWithClassPath(
|
String classPathJar = FileUtil.createJarWithClassPath(
|
||||||
newClassPath.toString(), pwd, mergedEnv);
|
newClassPath.toString(), pwd, mergedEnv);
|
||||||
|
// In a secure cluster the classpath jar must be localized to grant access
|
||||||
|
this.exec.localizeClasspathJar(new Path(classPathJar), container.getUser());
|
||||||
environment.put(Environment.CLASSPATH.name(), classPathJar);
|
environment.put(Environment.CLASSPATH.name(), classPathJar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -313,6 +313,31 @@ public class ContainerLocalizer {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the ContainerLocalizer arguments for a @{link ShellCommandExecutor},
|
||||||
|
* as expected by ContainerLocalizer.main
|
||||||
|
* @param command the current ShellCommandExecutor command line
|
||||||
|
* @param user localization user
|
||||||
|
* @param appId localized app id
|
||||||
|
* @param locId localizer id
|
||||||
|
* @param nmAddr nodemanager address
|
||||||
|
* @param localDirs list of local dirs
|
||||||
|
*/
|
||||||
|
public static void buildMainArgs(List<String> command,
|
||||||
|
String user, String appId, String locId,
|
||||||
|
InetSocketAddress nmAddr, List<String> localDirs) {
|
||||||
|
|
||||||
|
command.add(ContainerLocalizer.class.getName());
|
||||||
|
command.add(user);
|
||||||
|
command.add(appId);
|
||||||
|
command.add(locId);
|
||||||
|
command.add(nmAddr.getHostName());
|
||||||
|
command.add(Integer.toString(nmAddr.getPort()));
|
||||||
|
for(String dir : localDirs) {
|
||||||
|
command.add(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] argv) throws Throwable {
|
public static void main(String[] argv) throws Throwable {
|
||||||
Thread.setDefaultUncaughtExceptionHandler(new YarnUncaughtExceptionHandler());
|
Thread.setDefaultUncaughtExceptionHandler(new YarnUncaughtExceptionHandler());
|
||||||
// usage: $0 user appId locId host port app_log_dir user_dir [user_dir]*
|
// usage: $0 user appId locId host port app_log_dir user_dir [user_dir]*
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hadoop.yarn.server.nodemanager;
|
||||||
|
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.util.Shell;
|
||||||
|
import org.apache.hadoop.yarn.conf.YarnConfiguration;
|
||||||
|
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class TestContainerExecutor {
|
||||||
|
|
||||||
|
private ContainerExecutor containerExecutor = new DefaultContainerExecutor();
|
||||||
|
|
||||||
|
@Test (timeout = 5000)
|
||||||
|
public void testRunCommandNoPriority() throws Exception {
|
||||||
|
Configuration conf = new Configuration();
|
||||||
|
String[] command = containerExecutor.getRunCommand("echo", "group1", "user", null, conf);
|
||||||
|
assertTrue("first command should be the run command for the platform " + command[0],
|
||||||
|
command[0].equals(Shell.WINUTILS) || command[0].equals("bash"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test (timeout = 5000)
|
||||||
|
public void testRunCommandwithPriority() throws Exception {
|
||||||
|
Configuration conf = new Configuration();
|
||||||
|
conf.setInt(YarnConfiguration.NM_CONTAINER_EXECUTOR_SCHED_PRIORITY, 2);
|
||||||
|
String[] command = containerExecutor.getRunCommand("echo", "group1", "user", null, conf);
|
||||||
|
if (Shell.WINDOWS) {
|
||||||
|
// windows doesn't currently support
|
||||||
|
assertEquals("first command should be the run command for the platform",
|
||||||
|
Shell.WINUTILS, command[0]);
|
||||||
|
} else {
|
||||||
|
assertEquals("first command should be nice", "nice", command[0]);
|
||||||
|
assertEquals("second command should be -n", "-n", command[1]);
|
||||||
|
assertEquals("third command should be the priority", Integer.toString(2),
|
||||||
|
command[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// test with negative number
|
||||||
|
conf.setInt(YarnConfiguration.NM_CONTAINER_EXECUTOR_SCHED_PRIORITY, -5);
|
||||||
|
command = containerExecutor.getRunCommand("echo", "group1", "user", null, conf);
|
||||||
|
if (Shell.WINDOWS) {
|
||||||
|
// windows doesn't currently support
|
||||||
|
assertEquals("first command should be the run command for the platform",
|
||||||
|
Shell.WINUTILS, command[0]);
|
||||||
|
} else {
|
||||||
|
assertEquals("first command should be nice", "nice", command[0]);
|
||||||
|
assertEquals("second command should be -n", "-n", command[1]);
|
||||||
|
assertEquals("third command should be the priority", Integer.toString(-5),
|
||||||
|
command[2]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -199,7 +199,7 @@ public class TestDefaultContainerExecutor {
|
||||||
Assert.assertEquals(appDirPerm, stats.getPermission());
|
Assert.assertEquals(appDirPerm, stats.getPermission());
|
||||||
}
|
}
|
||||||
|
|
||||||
executor.createAppLogDirs(appId, logDirs);
|
executor.createAppLogDirs(appId, logDirs, user);
|
||||||
|
|
||||||
for (String dir : logDirs) {
|
for (String dir : logDirs) {
|
||||||
FileStatus stats = lfs.getFileStatus(new Path(dir, appId));
|
FileStatus stats = lfs.getFileStatus(new Path(dir, appId));
|
||||||
|
@ -277,7 +277,7 @@ public class TestDefaultContainerExecutor {
|
||||||
mockExec.createUserLocalDirs(localDirs, appSubmitter);
|
mockExec.createUserLocalDirs(localDirs, appSubmitter);
|
||||||
mockExec.createUserCacheDirs(localDirs, appSubmitter);
|
mockExec.createUserCacheDirs(localDirs, appSubmitter);
|
||||||
mockExec.createAppDirs(localDirs, appSubmitter, appId);
|
mockExec.createAppDirs(localDirs, appSubmitter, appId);
|
||||||
mockExec.createAppLogDirs(appId, logDirs);
|
mockExec.createAppLogDirs(appId, logDirs, appSubmitter);
|
||||||
|
|
||||||
Path scriptPath = new Path("file:///bin/echo");
|
Path scriptPath = new Path("file:///bin/echo");
|
||||||
Path tokensPath = new Path("file:///dev/null");
|
Path tokensPath = new Path("file:///dev/null");
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
~~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
~~ you may not use this file except in compliance with the License.
|
||||||
|
~~ You may obtain a copy of the License at
|
||||||
|
~~
|
||||||
|
~~ http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
~~
|
||||||
|
~~ Unless required by applicable law or agreed to in writing, software
|
||||||
|
~~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
~~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
~~ See the License for the specific language governing permissions and
|
||||||
|
~~ limitations under the License. See accompanying LICENSE file.
|
||||||
|
|
||||||
|
---
|
||||||
|
YARN Secure Containers
|
||||||
|
---
|
||||||
|
---
|
||||||
|
${maven.build.timestamp}
|
||||||
|
|
||||||
|
YARN Secure Containers
|
||||||
|
|
||||||
|
%{toc|section=1|fromDepth=0|toDepth=3}
|
||||||
|
|
||||||
|
* {Overview}
|
||||||
|
|
||||||
|
YARN containers in a secure cluster use the operating system facilities to offer
|
||||||
|
execution isolation for containers. Secure containers execute under the credentials
|
||||||
|
of the job user. The operating system enforces access restriction for the container.
|
||||||
|
The container must run as the use that submitted the application.
|
||||||
|
|
||||||
|
Secure Containers work only in the context of secured YARN clusters.
|
||||||
|
|
||||||
|
** Container isolation requirements
|
||||||
|
|
||||||
|
The container executor must access the local files and directories needed by the
|
||||||
|
container such as jars, configuration files, log files, shared objects etc. Although
|
||||||
|
it is launched by the NodeManager, the container should not have access to the
|
||||||
|
NodeManager private files and configuration. Container running applications
|
||||||
|
submitted by different users should be isolated and unable to access each other
|
||||||
|
files and directories. Similar requirements apply to other system non-file securable
|
||||||
|
objects like named pipes, critical sections, LPC queues, shared memory etc.
|
||||||
|
|
||||||
|
|
||||||
|
** Linux Secure Container Executor
|
||||||
|
|
||||||
|
On Linux environment the secure container executor is the <<<LinuxContainerExecutor>>>.
|
||||||
|
It uses an external program called the <<container-executor>>> to launch the container.
|
||||||
|
This program has the <<<setuid>>> access right flag set which allows it to launch
|
||||||
|
the container with the permissions of the YARN application user.
|
||||||
|
|
||||||
|
*** Configuration
|
||||||
|
|
||||||
|
The configured directories for <<<yarn.nodemanager.local-dirs>>> and
|
||||||
|
<<<yarn.nodemanager.log-dirs>>> must be owned by the configured NodeManager user
|
||||||
|
(<<<yarn>>>) and group (<<<hadoop>>>). The permission set on these directories must
|
||||||
|
be <<<drwxr-xr-x>>>.
|
||||||
|
|
||||||
|
The <<<container-executor>>> program must be owned by <<<root>>> and have the
|
||||||
|
permission set <<<---sr-s--->>>.
|
||||||
|
|
||||||
|
To configure the <<<NodeManager>>> to use the <<<LinuxContainerExecutor>>> set the following
|
||||||
|
in the <<conf/yarn-site.xml>>:
|
||||||
|
|
||||||
|
+---+
|
||||||
|
<property>
|
||||||
|
<name>yarn.nodemanager.container-executor.class</name>
|
||||||
|
<value>org.apache.hadoop.yarn.server.nodemanager.LinuxContainerExecutor</value>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<property>
|
||||||
|
<name>yarn.nodemanager.linux-container-executor.group</name>
|
||||||
|
<value>hadoop</value>
|
||||||
|
</property>
|
||||||
|
+---+
|
||||||
|
|
||||||
|
Additionally the LCE requires the <<<container-executor.cfg>>> file, which is read by the
|
||||||
|
<<<container-executor>>> program.
|
||||||
|
|
||||||
|
+---+
|
||||||
|
yarn.nodemanager.linux-container-executor.group=#configured value of yarn.nodemanager.linux-container-executor.group
|
||||||
|
banned.users=#comma separated list of users who can not run applications
|
||||||
|
allowed.system.users=#comma separated list of allowed system users
|
||||||
|
min.user.id=1000#Prevent other super-users
|
||||||
|
+---+
|
||||||
|
|
||||||
|
|
||||||
|
** Windows Secure Container Executor
|
||||||
|
|
||||||
|
The Windows environment secure container executor is the <<<WindowsSecureContainerExecutor>>>.
|
||||||
|
It uses the Windows S4U infrastructure to launch the container as the
|
||||||
|
YARN application user.
|
||||||
|
|
||||||
|
*** Configuration
|
||||||
|
|
||||||
|
To configure the <<<NodeManager>>> to use the <<<WindowsSecureContainerExecutor>>>
|
||||||
|
set the following in the <<conf/yarn-site.xml>>:
|
||||||
|
|
||||||
|
+---+
|
||||||
|
<property>
|
||||||
|
<name>yarn.nodemanager.container-executor.class</name>
|
||||||
|
<value>org.apache.hadoop.yarn.server.nodemanager.WindowsSecureContainerExecutor</value>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<property>
|
||||||
|
<name>yarn.nodemanager.windows-secure-container-executor.group</name>
|
||||||
|
<value>hadoop</value>
|
||||||
|
</property>
|
||||||
|
+---+
|
||||||
|
|
||||||
|
The NodeManager must run as a member of the local <<<Administrators>>> group or as
|
||||||
|
<<<LocalSystem>>>. It is not enough for the NodeManager to simply impersonate such an user.
|
||||||
|
|
||||||
|
*** Useful Links
|
||||||
|
|
||||||
|
* {{{http://msdn.microsoft.com/en-us/magazine/cc188757.aspx}Exploring S4U Kerberos Extensions in Windows Server 2003}}
|
||||||
|
|
||||||
|
* {{{https://issues.apache.org/jira/browse/YARN-1063}Winutils needs ability to create task as domain user}}
|
||||||
|
|
||||||
|
* {{{https://issues.apache.org/jira/browse/YARN-1972}Implement secure Windows Container Executor}}
|
|
@ -53,6 +53,8 @@ MapReduce NextGen aka YARN aka MRv2
|
||||||
|
|
||||||
* {{{./TimelineServer.html}YARN Timeline Server}}
|
* {{{./TimelineServer.html}YARN Timeline Server}}
|
||||||
|
|
||||||
|
* {{{./SecureContainer.html}YARN Secure Containers}}
|
||||||
|
|
||||||
* {{{../../hadoop-project-dist/hadoop-common/CLIMiniCluster.html}CLI MiniCluster}}
|
* {{{../../hadoop-project-dist/hadoop-common/CLIMiniCluster.html}CLI MiniCluster}}
|
||||||
|
|
||||||
* {{{../../hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduce_Compatibility_Hadoop1_Hadoop2.html}Backward Compatibility between Apache Hadoop 1.x and 2.x for MapReduce}}
|
* {{{../../hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduce_Compatibility_Hadoop1_Hadoop2.html}Backward Compatibility between Apache Hadoop 1.x and 2.x for MapReduce}}
|
||||||
|
|
Loading…
Reference in New Issue