Docker On Yarn

本文讨论的是结合Docker和YARN的优势,利用大数据计算集群的调度能力和计算资源,靠着Docker这一轻量级的虚拟化方案,提供运行更丰富的分布式应用的可能性。

Docker

在云计算的领域,Docker是太火了,不需要再多做介绍。这样一个轻量级的无比易用的虚拟化技术谁不爱?

YARN

YARN是Hadoop生态中的分布式资源调度系统,它更倾向于用作大数据计算框架如MapReduce,Spark,Storm等系统的资源管理和调度。 从HadoopV2开始,YARN俨然成为了系统的核心,开源大数据的操作系统。

Yarnker!!

把Docker和YARN结合起来,我们可以得到软件环境隔离性和资源使用一致性的统一。

  • YARN来统一管理和控制集群资源,Docker提供运行环境的适配,解决各种依赖
  • YARN不再只是大数据计算引擎的一个调度框架,有了更多可能。有点Hybrid with PaaS的赶脚。

Yarn vs Mesos

谈到资源调度系统,提到YARN的同时往往绕不开Mesos。 相对于Mesos,我更偏爱YARN。

DCE

Apache官网的这篇文档DockerContainerExecutor讲了Docker作为YARN的容器运行的话题。

yarn-site.xml中增加配置,重点在于指定运行docker的命令,以及选择DCE(DockerContainerExecutor)类作为NodeManager的容器执行类。

<property>
 <name>yarn.nodemanager.docker-container-executor.exec-name</name>
  <value>/usr/bin/docker</value>
  <description>
     Name or path to the Docker client. This is a required parameter. If this is empty,
     user must pass an image name as part of the job invocation(see below).
  </description>
</property>

<property>
  <name>yarn.nodemanager.container-executor.class</name>
  <value>org.apache.hadoop.yarn.server.nodemanager.DockerContainerExecutor</value>
  <description>
     This is the container executor setting that ensures that all
jobs are started with the DockerContainerExecutor.
  </description>
</property>

这里可能唯一需要注意的是docker的运行问题。我们都知道docker会要求以root运行,而作为YARN的用户不一定是root用户。我的做法是提供一个脚本/usr/bin/udocker,内容为:

    sudo docker $*

然后yarn-site.xml中指定:

    <name>yarn.nodemanager.docker-container-executor.exec-name</name>
    <value>/usr/bin/udocker</value>

一个简单的小实验

官网的一个运行例子

hadoop jar ~/hadoop-2.7.1/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.1.jar teragen -Dmapreduce.map.env="yarn.nodemanager.docker-container-executor.image-name=sequenceiq/hadoop-docker:2.4.1" -Dyarn.app.mapreduce.am.env="yarn.nodemanager.docker-container-executor.image-name=sequenceiq/hadoop-docker:2.4.1" 1000 teragen_out_dir

这个例子展示了一个MapReduce的程序(只有map),ApplicationMaster和Map都是在docker镜像sequenceiq/hadoop-docker:2.4.1启动的容器里、而非YARN的原生容器里运行。这个容器里有java环境和Hadoop的JAR包。我们期望可以看到像在宿主机上直接执行Map的效果。

很遗憾,你如果严格按照官网的说明做完,会发现执行报错:

  Diagnostics: Exception from container-launch: 
  ExitCodeException exitCode=1: Error: No such image or container: container_e14_1458272021487_0008_02_000001

别怕,仔细看一下对应container的运行日志(日志具体目录参见你yarn-site.xml 中yarn.nodemanager.log-dirs配置项的值)。

  ubuntu@hulk:/data0/log/hadoop/application_1458272021487_0007/container_e14_1458272021487_0007_01_000001$ more syslog 
  2016-03-18 00:09:47,225 FATAL [main] org.apache.hadoop.mapreduce.v2.app.MRAppMaster: Error starting MRAppMaster
  java.lang.IllegalArgumentException: Invalid ContainerId: container_e14_1458272021487_0007_01_000001
      at org.apache.hadoop.yarn.util.ConverterUtils.toContainerId(ConverterUtils.java:182)
      at org.apache.hadoop.mapreduce.v2.app.MRAppMaster.main(MRAppMaster.java:1361)
  Caused by: java.lang.NumberFormatException: For input string: "e14"
      at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
      at java.lang.Long.parseLong(Long.java:441)
      at java.lang.Long.parseLong(Long.java:483)
      at org.apache.hadoop.yarn.util.ConverterUtils.toApplicationAttemptId(ConverterUtils.java:137)
      at org.apache.hadoop.yarn.util.ConverterUtils.toContainerId(ConverterUtils.java:177)
      ... 1 more
  2016-03-18 00:09:47,241 INFO [main] org.apache.hadoop.util.ExitUtil: Exiting with status 1

这个信息显然是因为选择的docker镜像的版本是2.4.1,而宿主机环境的版本是2.7.1,关于容器Id的约定不一致。

有多种办法可以解决此问题,不过我们今天只是为了试用一下DCE,简单的办法就是试用同版本的镜像(对,这个例子看起来有点没意义):

  hadoop jar ~/hadoop-2.7.1/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.7.1.jar teragen -Dmapreduce.map.env="yarn.nodemanager.docker-container-executor.image-name=sequenceiq/hadoop-docker:2.7.1" -Dyarn.app.mapreduce.am.env="yarn.nodemanager.docker-container-executor.image-name=sequenceiq/hadoop-docker:2.7.1" 1000 teragen_out_dir

我遇到了点别的问题,相信你也有很大概率遇到: 宿主机和容器的HDFS不一致。

  java.net.ConnectException: Call From hulk.dtdream/192.168.103.47 to localhost:9000 failed on connection exception: java.net.ConnectException: Connection refused;

看容器里的hadoop环境,hdfs在localhost:9000

    bash-4.1# more core-site.xml
  <configuration>
      <property>
          <name>fs.defaultFS</name>
          <value>hdfs://localhost:9000</value>
      </property>
  </configuration>

而宿主机上我的配置可能是这样的(稍作了简化,其实有ns定义和双nn的HA)

    <!-- 指定hdfs的nameservice为ns1 -->
    <property>
        <name>fs.defaultFS</name>
        <value>hdfs://192.168.103.45:9000</value>
    </property>

我修改了下我的镜像的core-site.xml配置,重新执行,总算成功了。

从这个例子可以看到DCE的文档还是比较粗糙的。

另一个简单的小实验

YARN官方源码自带一个APP用例DistributedShell,功能是请求多个容器执行相同的shell命令。

  hadoop jar hadoop-2.7.1/share/hadoop/yarn/hadoop-yarn-applications-distributedshell-2.7.1.jar org.apache.hadoop.yarn.applications.distributedshell.Client --jar hadoop-2.7.1/share/hadoop/yarn/hadoop-yarn-applications-distributedshell-2.7.1.jar --shell_command ls --num_containers 10 --container_memory 350 --master_memory 350 --priority 10

由于前面我们打开了DCE,你现在会看到它执行失败。

  For more detailed output, check application tracking page:http://batman.dtdream:8188/cluster/app/application_1458272021487_0011Then, click on links to logs of each attempt.
  Diagnostics: Container image must not be null
  Failing this attempt. Failing the application., appMasterHost=N/A, appQueue=default, appMasterRpcPort=-1, appStartTime=1458402099045, yarnAppState=FAILED, distributedFinalState=FAILED, appTrackingUrl=http://batman.dtdream:8188/cluster/app/application_1458272021487_0011, appUser=ubuntu
  16/03/19 23:41:46 INFO distributedshell.Client: Application did not finish. YarnState=FAILED, DSFinalStatus=FAILED. Breaking monitoring loop
  16/03/19 23:41:46 ERROR distributedshell.Client: Application failed to complete successfully

对,聪明的你已经想到了,DCE和缺省的ContainerExecutor互斥。DCE启动的容器需要给定Docker镜像的参数。

这也是我要再举一个例子的原因。前面的例子里MapReducev2框架做了太多封装,你看不到一些细节。

我们看下YarnConfiguration中和DCE相关的配置项。

  /** The Docker image name(For DockerContainerExecutor).*/
  public static final String NM_DOCKER_CONTAINER_EXECUTOR_IMAGE_NAME =
    NM_PREFIX + "docker-container-executor.image-name";

  /** The name of the docker executor (For DockerContainerExecutor).*/
  public static final String NM_DOCKER_CONTAINER_EXECUTOR_EXEC_NAME =
    NM_PREFIX + "docker-container-executor.exec-name";

  /** The default docker executor (For DockerContainerExecutor).*/
  public static final String NM_DEFAULT_DOCKER_CONTAINER_EXECUTOR_EXEC_NAME =
          "/usr/bin/docker";

本例中我们只需要关注NM_DOCKER_CONTAINER_EXECUTOR_IMAGE_NAME的传递。

看DCE的容器执行代码:

  @Override
  public int launchContainer(Container container,
                             Path nmPrivateContainerScriptPath, Path nmPrivateTokensPath,
                             String userName, String appId, Path containerWorkDir,
                             List<String> localDirs, List<String> logDirs) throws IOException {
    String containerImageName = container.getLaunchContext().getEnvironment()
        .get(YarnConfiguration.NM_DOCKER_CONTAINER_EXECUTOR_IMAGE_NAME);

我们要做的,只是启动AM的环境变量中设置NM_DOCKER_CONTAINER_EXECUTOR_IMAGE_NAME:xxx, 同时AM中启动其他容器的环境变量中设置NM_DOCKER_CONTAINER_EXECUTOR_IMAGE_NAME:xxx。可以使用配置文件,也可以增加命令行参数。

代码不再提供了,请你自己修改下代码编译运行试试看吧。:)

不过,机智如你应该已经想到了,DCE和Default的这种非此即彼其实很不舒服。更合理的做法,是提供一个新的Executor,可以兼容YARN原生容器和Docker容器。

业界

关于YARN和Docker结合,其实业界已有不少实践,推荐以下阅读。

  • 腾讯实践 可以看看这篇访谈
  • Hulu实践 Hulu的voidbox相关blog在这里
  • apache slider 项目 可以看看Docker Based Application Packaging Support
  • Hortonworks: K8 + YARN Hortonworks的这篇文章提到了把Kubernetes和YARN结合起来。这是非常棒的想法。Kubernetes定义业务(Service和Pod),管理Pod的生命周期,而YARN负责资源管理。

TODO 更多的需求

本文只是抛砖,是最基本的示例,其实Docker On YARN还大有文章可做,比如我们可以做以下练习:

  • 编写一个ContainerExecutor,可以同时适配Default和Docker
  • 可以编写一个AM,作为常驻服务,接受各种Docker容器的启动请求
  • client 的实现可以是先获取AM,获取不到则启动AM。若获取到AM,client可以向AM请求启动Dockedr容器
  • 对外提供更易用的封装接口

做完这些,我们就有了一个在Hadoop集群中自由发布Docker容器应用的原型。代码量并不多,有时间我整理一下吧……



最后更新: 2016-03-12 16:21