【概述】
在YARN中,不管是ApplicationMaster(后面均简称AM),还是一般的container(例如MR中的map任务、reduce任务;Spark中的executor或者Flink中的TaskManager),都有各自的启动上下文(ContainerLaunchContext)。
上下文中包含了任务启动所依赖的资源(包括jar,资源文件等)、环境变量、启动参数等。
那么启动上下文具体是怎样传递给yarn,yarn是否进行了处理,进行了怎样的处理,本文就来聊聊这些相关内容。
【参数传递流程】
对于AM而言,客户端向ResourceManager(RM)提交任务,提交的任务请求中就包含了上下文信息,RM收到请求后进行调度,最后通过NodeManager(NM)的心跳告知NM启动AM,在这个过程中将上下文信息传递给NM,NM拿到上下文信息构造出AM的启动脚本,并启动AM。
对于一般container,是由AM直接向NM发起请求,要求启动container,请求中同样携带了上下文信息。同样,NM根据这个上下文信息,构造出对应的启动脚本,然后启动container。
【示例】
先来看个实际的例子:
上图是客户端向RM提交MR任务时的抓包情况,结合protobuf文件与源码中上下文的数据结构,可以整理出,上下文信息包含了四个资源文件:
job.split、
job.xml、
job.splitmetainfo
job.jar
上下文中还指定了环境变量HADOOP_CLASSPATH,以及AM的启动参数。
该任务最终对应的启动脚本是这样的:
#!/bin/bash
export HADOOP_CONF_DIR="/opt/service/hadoop/etc/hadoop"
export MAX_APP_ATTEMPTS="2"
export JAVA_HOME="/usr/java/jdk1.8.0_231"
export APP_SUBMIT_TIME_ENV="1617090350054"
export NM_HOST="172.19.6.60"
export HADOOP_CLASSPATH="$PWD:job.jar/job.jar:job.jar/classes/:job.jar/lib/*:$PWD/*:/opt/service/hadoop/contrib/capacity-scheduler/*.jar"
export LD_LIBRARY_PATH="$PWD:$HADOOP_COMMON_HOME/lib/native"
export HADOOP_HDFS_HOME="/opt/service/hadoop-2.8.5"
export LOGNAME="root"
export JVM_PID="$$"
export PWD="/opt/data/hadoop/yarn/nodemanager/local/user/cache/root/appcache/application_1616833932566_0011/container_e21_1616833932566_0011_01_000001"
export HADOOP_COMMON_HOME="opt/service/hadoop"
export LOCAL_DIRS="/opt/data/hadoop/yarn/nodemanager/local/usercache/root/appcache/application_1616833932566_0011"
export APPLICATION_WEB_PROXY_BASE="/proxy/application_1616833932566_0011"
export SHELL="/bin/bash"
export NM_HTTP_PORT="8042"
export LOG_DIRS="/opt/data/hadoop/yarn/nodemanager/log/application_1616833932566_0011/container_e21_1616833932566_0011_01_000001"
export NM_AUX_SERVICE_mapreduce_shuffle="AAAAO+gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
export NM_PORT="9100"
export USER="root"
export HADOOP_YARN_HOME="/opt/service/hadoop-2.8.5"
export CLASSPATH="$PWD:$HADOOP_CONF_DIR:$HADOOP_COMMON_HOME/share/hadoop/common/*:$HADOOP_COMMON_HOME/share/hadoop/common/lib/*:$HADOOP_HDFS_HOME/share/hadoop/hdfs/*:$HADOOP_HDFS_HOME/share/hadoop/hdfs/lib/*:$HADOOP_YARN_HOME/share/hadoop/yarn/*:$HADOOP_YARN_HOME/share/hadoop/yarn/lib/*:$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/*:$HADOOP_MAPRED_HOME/share/hadoop/mapreduce/lib/*:job.jar/job.jar:job.jar/classes/:job.jar/lib/*:$PWD/*"
export HADOOP_TOKEN_FILE_LOCATION="/opt/data/hadoop/yarn/nodemanager/local/usercache/root/appcache/application_1616833932566_0011/container_e21_1616833932566_0011_01_000001/container_tokens"
export HOME="/home/"
export CONTAINER_ID="container_e21_1616833932566_0011_01_000001"
export MALLOC_ARENA_MAX="4"
ln -sf "/opt/data/hadoop/yarn/nodemanager/local/usercache/root/appcache/application_1616833932566_0011/filecache/11/job.jar" "job.jar"
hadoop_shell_errorcode=$?
if [ $hadoop_shell_errorcode -ne 0 ]
then
exit $hadoop_shell_errorcode
fi
mkdir -p jobSubmitDir
hadoop_shell_errorcode=$?
if [ $hadoop_shell_errorcode -ne 0 ]
then
exit $hadoop_shell_errorcode
fi
ln -sf "/opt/data/hadoop/yarn/nodemanager/local/usercache/root/appcache/application_1616833932566_0011/filecache/12/job.split" "jobSubmitDir/job.split"
hadoop_shell_errorcode=$?
if [ $hadoop_shell_errorcode -ne 0 ]
then
exit $hadoop_shell_errorcode
fi
ln -sf "/opt/data/hadoop/yarn/nodemanager/local/usercache/root/appcache/application_1616833932566_0011/filecache/13/job.xml" "job.xml"
hadoop_shell_errorcode=$?
if [ $hadoop_shell_errorcode -ne 0 ]
then
exit $hadoop_shell_errorcode
fi
mkdir -p jobSubmitDir
hadoop_shell_errorcode=$?
if [ $hadoop_shell_errorcode -ne 0 ]
then
exit $hadoop_shell_errorcode
fi
ln -sf "/opt/data/hadoop/yarn/nodemanager/local/usercache/root/appcache/application_1616833932566_0011/filecache/10/job.splitmetainfo" "jobSubmitDir/job.splitmetainfo"
hadoop_shell_errorcode=$?
if [ $hadoop_shell_errorcode -ne 0 ]
then
exit $hadoop_shell_errorcode
fi
exec /bin/bash -c "$JAVA_HOME/bin/java -Djava.io.tmpdir=$PWD/tmp -Dlog4j.configuration=container-log4j.properties -Dyarn.app.container.log.dir=/opt/data/hadoop/yarn/nodemanager/log/application_1616833932566_0011/container_e21_1616833932566_0011_01_000001 -Dyarn.app.container.log.filesize=0 -Dhadoop.root.logger=INFO,CLA -Dhadoop.root.logfile=syslog -Xmx1024m org.apache.hadoop.mapreduce.v2.app.MRAppMaster 1>/opt/data/hadoop/yarn/nodemanager/log/application_1616833932566_0011/container_e21_1616833932566_0011_01_000001/stdout 2>/opt/data/hadoop/yarn/nodemanager/log/application_1616833932566_0011/container_e21_1616833932566_0011_01_000001/stderr "
hadoop_shell_errorcode=$?
if [ $hadoop_shell_errorcode -ne 0 ]
then
exit $hadoop_shell_errorcode
fi
【上下文的处理】
从上面的实例中可以看到,客户端请求中的上下文仅描述了资源名称与位置,环境变量以及具体启动的参数,那么NM是如何处理这个上下文并最终构造出启动脚本的呢?
通过源码分析,NM在启动任务前,根据上下文信息,转换成具体的启动脚本,具体处理包括:
创建脚本构造器
根据不同的平台(windows或unix)创建不同的脚本构建器,然后写入脚本的头,例如unix中的"#!/bin/bash"。
变量替换
变量替换指的是对上下文中环境变量、启动参数中的变量进行替换,主要替换的是日志的路径。
可以仔细观察下上图中客户端传递的上下文启动参数中,标准输出和错误输出指向的路径均为<LOG_DIR>。
这其实是代码中的一个公共常量。
MR、Flink、Spark任务提交时,均使用了该值作为日志的输出路径。
在NM中则根据实际配置文件中配置的路径进行替换。
添加额外的环境变量
除了客户端上下文中所携带的环境变量外,NM还会额外添加一些环境变量,包括
CONTAINER_ID
NM_PORT
NM_HOST
NM_HTTP_PORT
LOCAL_DIRS
LOG_DIRS
USER
LOGNAME
HOME
PWD
HADOOP_CONF_DIR
JVM_PID
另外,对于AM而言,RM在处理的过程中还额外添加了三个环境变量:
APPLICATION_WEB_PROXY_BASE
MAX_APP_ATTEMPTS
APP_SUBMIT_TIME_ENV
环境变量写入脚本
所有环境变量都存放在一个map中,然后遍历该map表,逐个转换成下面的格式:
export KEY="VALUE"
资源文件的处理
环境变量写完后,对所有资源文件按需创建目录,并建立软链接。
注意:每个资源文件建立软链接后,均会进行错误检查。
写入启动参数
最后将上下文中的启动参数写入脚本中。
整个流程下来就构成了完整的启动脚本,随后真正启动对应进程。
【总结】
本文简单总结了container启动上下文包括哪些内容,如何传递的,yarn是如何进行处理的。
这里留个引子,前面示例中客户端提交任务的上下文中,其资源文件指定的是HDFS的路径,那么这些资源文件最终是如何下载的本地的?
从NM构造的启动脚本中看到对这些资源文件都做了软链接,那么软链接的路径又是如何决定的?
这些资源文件能否给其他任务使用?资源文件什么时候删除。。。
所有这些问题都涉及NM中的资源管理,下篇文章,我们就来分析下NM中的资源管理。