这篇文章拖了很长时间,本来是打算系统整理下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明显隔离性更好,但有一些限制:
- 需要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
指定。 - container-executor还需要一个配置文件container-executor.cfg。而且这个配置文件和container-executor的二进制文件相对路径是固定的。默认情况下container-executor会去
../etc/hadoop
路径下寻找配置文件,找不到的话会报错。可以在编译hadoop时指定:mvn package -Pdist,native -DskipTests -Dtar -Dcontainer-executor.conf.dir=../../conf
。不知道为何要这样设计。 - 由于用不同的用户启动Container,所以必须有对应的Linux用户存在。否则会抛异常。这带来一些管理上的麻烦,比如新增一个用户B时,必须在所有NM节点上执行
useradd B
。 - container-executor和container-executor.cfg的所有者必须是root。而且他们所在的目录一直上溯到/,所有者也必须是root。所以我们一般把这两个文件放在/etc/yarn下。
- container-executor文件的权限必须是
6050 or --Sr-s---
,因为它的原理就是setuid/setgid。group owner必须和启动NM的用户同组。比如NM由yarn用户启动,yarn用户属于hadoop组,那container-executor必须也是hadoop组。
container-executor.cfg示例:
|
|
启动Container其实就是运行一个脚本。可以简单理解为:DCE是直接/bin/bash $*
,LCE是./container-executor $*
,额外包了一层。
如果直接执行container-executor:
|
|
内存隔离
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类负责。
分几步:
- 建相关目录。$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次),但只有一个会被真正使用。
- 将token文件写到$local.dir/usercache/$user/appcache/$appid目录。这里有bug,无论DCE还是LCE,都会将token文件写到第一个local-dir,所以可能会有竞争,导致后续container启动失败。见YARN-2566、YARN-2623。
- 对于DCE,直接new一个ContainerLocalizer对象,调用runLocalization方法。这个方法的作用是从ResourceLocalizationService处获取要分发的文件的URI,并下载到本地。对于LCE,会单独启动一个JVM进程,通过RPC协议LocalizationProtocol与ResourceLocalizationService通信。功能是一样的。
接着就没继续研究了。。。主要是这个太复杂,而且没环境去调试。一些知识点:下载的lib会放在一个随机生成的数字目录下;下载的文件默认最大10GB,每10分钟清理一次;NM会将启动container的命令写到一个脚本里,但怎么将这些下载的lib加到classpath里的,还不太清楚。
关于localize过程的更多信息可以看HortonWorks Blog。