YARN源码解析(7)-NodeManager中几种ContainerExecutor

Posted by AlstonWilliams on February 17, 2019

在NodeManager中,有三种运行Container的方式,它们分别是:

  • DefaultContainerExecutor
  • LinuxContainerExecutor
  • DockerContainerExecutor

从它们的名字中,我们就能看得出来,默认情况下,一定使用的是DefaultContainerExecutor

而一般情况下,DefaultContainerExecutor也确实能够满足我们的需求。

在这篇文章中,我们会首先介绍DefaultContainerExecutor,然后简单介绍LinuxContainerExecutorDockerContainerExecutor

DefaultContainerExecutor

这个ContainerExecutor的实现实际上很简单,就是通过构建一个脚本来执行而已。支持Windows脚本以及Linux脚本。

在ContainerExecutor启动一个Container的过程中,涉及到了三个脚本,它们分别是:

  • default_container_executor.sh
  • default_container_executor_session.sh
  • launch_container.sh

这三个脚本,都是跟Container相关的,所以它们都被放在一个Container所代表的目录结构下。

在NodeManager中,会为每个Application,以及每个Container建立一个对应的目录,在每个Container的目录下,就放置了一些运行这个Container必需的信息。

一般来说,这些目录是位于/tmp这个目录下,并且会在一个Application完成后,被删除。减少磁盘空间的消耗。

Container在磁盘上的表示

我们分别查看一下,上面我们所说的那三个脚本文件的内容。

default-container_executor.sh:

我们可以看到,在这个脚本文件的内部,会启动default_container_executor_session.sh这个脚本,并将执行结果写入到这个Container的一个名为Container ID+pid.exitcode的文件中。

default_container_executor_session.sh这个脚本呢?

我们可以看到,它主要是启动launch_container.sh这个脚本。

而我们可以看到,launch_container.sh中,就负责运行相应的Container,也能是MRAppMaster,也可能是Mapper或者Reducer:

launch_container.sh中,设置了很多环境变量。

这里因为我查看了一个ApplicationMaster的Container,所以启动的是MRAppMaster

那么,DefaultContainerExecutor应该就是首先执行default_container_executor.sh这个脚本,对吧?

嗯嗯,没错的。

我们来查看一下代码:

实际上,DefaultContainerExecutor中,实例化的是一个UnixLocalWrapperScriptBuilder对象,而这个对象,是LocalWrapperScriptBuilder的一个子类,并且在constructor中调用了LocalWrapperScriptBuilder的constructor。

总之,最后DefaultContainerExecutor确实就是直接调用了default_container_executor.sh这个脚本。

我们可以看到,它的实现实际上非常简单。

同时,我们也可以看到,这个实现有一些问题,即,对于资源隔离做的并不好。全部Container都是由运行NodeManager的那个用户启动的。

LinuxContainerExecutor

而LinuxContainerExecutor就解决了上面的那个问题。

从代码中,我们可以看到,现在,launch a container时,它是调用了container-executor这个程序:

那么,container-executor这个程序是啥东西呢?

在Hadoop的安装目录下,bin目录中,你应该就会发现这个程序。

我们可以看到,可以直接使用这个程序来实例化一个Container,启动一个Container,给这个Container发信号,以及删除这个Container,而且,我们还可以看到,它还支持挂载cgroup,并且我们可以指定运行Container的用户。

container-executor这个程序,是用c语言写的。它的源代码在$HADOOP_SOURCE_CODE_ROOT/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl中。

其中最重要的功能,都是在container-executor.c这个源文件中。我们还是主要来看它是如何启动一个Container的。

launch_container_as_user()这个函数中,就实现了启动一个Container的功能。

其中最重要的是这么两部分:

我们可以看到,在上面的代码块中,fork了一个子进程。fork()这个函数,我们可以看一下它的介绍。

从文档中,我们可以看到,对于子进程来说,这个函数会返回0,而对于父进程来说,则返回子进程的的pid。

所以,我们可以看到,子进程会执行execlp()函数,来运行launch_container.sh脚本,启动一个Container。

而父进程则会一直waitpid()函数来查看Container的运行状态,一旦运行结束,就将状态码写到特定文件中。

那我们上面提到的,我们可以指定运行这个Container的用户,是如何实现的?

就是这个函数中的change_user()实现的啦。

那cgroup又是怎么一回事呢?

cgroup是Linux中实现资源隔离的一种方式,Docker就是基于CGroup以及Namespace实现的。

这部分的代码我没有细看,但是从这个函数中,确实能看到cgroup的身影。

总的来说,LinuxContainerExecutor相对于DefaultContainerExecutor,它的有点在于,可以手动指定运行Container的用户,并且可以通过cgroup来增强不同的Container之间的隔离性。

当然啦,它也有缺点。毕竟我们手动指定的用户,必须在每个NodeManager上面都存在才行呀,否则运行起来就会出错误。

DockerContainerExecutor

这个ContainerExecutor,就是把Docker结合进来了。这样做的好处在于,由于Docker是能够保证一个Container使用的资源,不会大于分配给它的资源,所以,不会出现过度使用资源的问题。

这个ContainerExecutor的实现更加简单,就是运行docker run命令启动Docker Container,通过docker inspect观察Container的状态。

这里不再多做介绍。

刚开始学习Hadoop的时候,就意识到Container可以用Docker来做,只是没想到YARN中,已经做到这一点了。

总结

并不是很清楚为什么LinuxContainerExecutor中,container-executor要用C实现。可能是涉及到的底层操作比较多,比较密集,就用C来实现吧。