ContainerExecutor简介

这篇文章拖了很长时间,本来是打算系统整理下YARN资源隔离机制的。
但太TM复杂了。。。很烦躁,看的头痛。。。
把目前写好的部分先发出来吧。

最近看到cgroup的一篇文章,突然想起YARN也是支持cgroup的。于是研究下。
Container相关逻辑都可以从ContainerManagerImpl类入手。

ContainerExecutor

一些基础的概念不说了。
Container是资源调度的基础,也是资源隔离的基础。理论上来说不同Container之间不应该互相影响。
ContainerExecutor负责初始化、启动、杀掉Container。
YARN提供了两种ContainerExecutor,通过属性yarn.nodemanager.container-executor.class配置:

  • DefaultContainerExecutor,简称DCE。每个Container运行在单独的进程里,但进程都是由NM的用户启动的。比如NM进程是用yarn用户启动的,那么所有Container的进程也由yarn用户启动。
  • LinuxContainerExecutor,简称LCE。每个Container由不同的用户启动。比如A用户提交的job的container,都由A用户启动。此外支持cgroup、支持单独的配置文件、支持简单的ACL。

LCE明显隔离性更好,但有一些限制:

  1. 需要linux native程序支持。准确的说是一个container-executor程序,用C写的,代码见hadoop-yarn-project\hadoop-yarn\hadoop-yarn-server\hadoop-yarn-server-nodemanager\src\main\native\container-executor。编译hadoop时务必同时编译container-executor。container-executor的路径由属性yarn.nodemanager.linux-container-executor.path指定。
  2. container-executor还需要一个配置文件container-executor.cfg。而且这个配置文件和container-executor的二进制文件相对路径是固定的。默认情况下container-executor会去../etc/hadoop路径下寻找配置文件,找不到的话会报错。可以在编译hadoop时指定:mvn package -Pdist,native -DskipTests -Dtar -Dcontainer-executor.conf.dir=../../conf。不知道为何要这样设计。
  3. 由于用不同的用户启动Container,所以必须有对应的Linux用户存在。否则会抛异常。这带来一些管理上的麻烦,比如新增一个用户B时,必须在所有NM节点上执行useradd B
  4. container-executor和container-executor.cfg的所有者必须是root。而且他们所在的目录一直上溯到/,所有者也必须是root。所以我们一般把这两个文件放在/etc/yarn下。
  5. container-executor文件的权限必须是6050 or --Sr-s---,因为它的原理就是setuid/setgid。group owner必须和启动NM的用户同组。比如NM由yarn用户启动,yarn用户属于hadoop组,那container-executor必须也是hadoop组。

container-executor.cfg示例:

1
2
3
4
5
6
7
8
# 下面这3个属性,其实在yarn-site.xml里也有。两边必须一致。
yarn.nodemanager.local-dirs=/mnt/dfs/0/yarn/local,/mnt/dfs/1/yarn/local,/mnt/dfs/2/yarn/local,/mnt/dfs/3/yarn/local,/mnt/dfs/4/yarn/local,/mnt/dfs/5/yarn/local,/mnt/dfs/6/yarn/local,/mnt/dfs/7/yarn/local
yarn.nodemanager.linux-container-executor.group=yarn
yarn.nodemanager.log-dirs=/mnt/dfs/0/yarn/logs,/mnt/dfs/1/yarn/logs,/mnt/dfs/2/yarn/logs,/mnt/dfs/3/yarn/logs,/mnt/dfs/4/yarn/logs,/mnt/dfs/5/yarn/logs,/mnt/dfs/6/yarn/logs,/mnt/dfs/7/yarn/logs
# 禁止这些用户提交yarn任务,主要是一些系统用户
banned.users=hdfs,yarn,mapred,bin
# uid小于1000的用户禁止提交yarn任务
min.user.id=1000

启动Container其实就是运行一个脚本。可以简单理解为:DCE是直接/bin/bash $*,LCE是./container-executor $*,额外包了一层。

如果直接执行container-executor:

1
2
3
4
5
6
7
8
9
hadoop@inspur116:~/hadoop-current/bin$ ./container-executor
Usage: container-executor --checksetup
Usage: container-executor --mount-cgroups hierarchy controller=path...
Usage: container-executor user yarn-user command command-args
Commands:
initialize container: 0 appid tokens nm-local-dirs nm-log-dirs cmd app...
launch container: 1 appid containerid workdir container-script tokens pidfile nm-local-dirs nm-log-dirs resources
signal container: 2 container-pid signal
delete as user: 3 relative-path

内存隔离

YARN对内存其实没有真正隔离,而是监视Container进程的内存使用,超出限制后直接杀掉进程。相关逻辑见ContainersMonitorImpl类。
进程监控的逻辑见ProcfsBasedProcessTree类,原理就是读取/proc/$pid下面的文件,获得进程的内存占用。
具体的逻辑没详细看,还有点复杂的。

CPU隔离

YARN在默认情况下,完全没有考虑CPU的隔离,即使用了LCE。
所以如果某个任务是CPU密集型的,可能消耗掉整个NM的CPU。
(跟具体的应用有关。对MR而言,最多用满一个核吧。)

cgroup

YARN支持cgroup隔离CPU资源:YARN-3
cgroup必须要LCE,但默认情况下没有开启。可以设置属性yarn.nodemanager.linux-container-executor.resources-handler.class为org.apache.hadoop.yarn.server.nodemanager.util.CgroupsLCEResourcesHandler以开启。
关于cgroup还有很多属性可以调整,见yarn-default.xml中的配置。

localize过程

研究ContainerExecutor的过程中,发现了这个东西,研究的痛不欲生。。。
这其实就是类似于以前的distributed cache,但是YARN做的更通用了。

主要是分发container运行需要的所有文件,包括一些lib、token等等。
这个过程称为localize,由ResourceLocalizationService类负责。

分几步:

  1. 建相关目录。$local.dir/usercache/$user/filecache,用于暂存用户可见的distributed cache;$local.dir/usercache/$user/appcache/$appid/filecache,用于暂存app可见的distributed cache;$log.dir/$appid/$containerid,用于暂存日志。我这里只列出了最深一级目录,父目录不存在也会新建。对DCE而言,直接用java代码建这些目录。对于LCE,调用container-executor建目录,见上文container-executor的Usage。注意这些目录会在所有磁盘上建(我们的节点一般是12块盘,就建12次),但只有一个会被真正使用。
  2. 将token文件写到$local.dir/usercache/$user/appcache/$appid目录。这里有bug,无论DCE还是LCE,都会将token文件写到第一个local-dir,所以可能会有竞争,导致后续container启动失败。见YARN-2566YARN-2623
  3. 对于DCE,直接new一个ContainerLocalizer对象,调用runLocalization方法。这个方法的作用是从ResourceLocalizationService处获取要分发的文件的URI,并下载到本地。对于LCE,会单独启动一个JVM进程,通过RPC协议LocalizationProtocol与ResourceLocalizationService通信。功能是一样的。

接着就没继续研究了。。。主要是这个太复杂,而且没环境去调试。一些知识点:下载的lib会放在一个随机生成的数字目录下;下载的文件默认最大10GB,每10分钟清理一次;NM会将启动container的命令写到一个脚本里,但怎么将这些下载的lib加到classpath里的,还不太清楚。

关于localize过程的更多信息可以看HortonWorks Blog