前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >yarn container的进程以及kill动作的逻辑

yarn container的进程以及kill动作的逻辑

作者头像
陈猿解码
发布2023-02-28 15:10:27
7390
发布2023-02-28 15:10:27
举报
文章被收录于专栏:陈猿解码

【背景】

在一次问题排查过程中,误杀了yarn任务container的其中一个进程,导致yarn application kill不再生效,并且在rm中任务状态显示为失败,但实际进程还在运行。在分析问题的同时,抽时间对yarn任务的进程、以及kill命令的执行流程进行了整理。本文就来聊聊这些内容。

【yarn任务相关的进程】

在yarn中,任务提交时(不管是AM还是任务container),会指定任务的启动命令,对于AM而言,由客户端提交任务时指定,对于任务container,由AM来指定。

启动命令最终会被传递到NodeManager中,NodeManager会进行一些包装组成多个shell脚本,然后调用这些脚本启动任务。具体涉及的脚本包括:

1)wrapper包装脚本(default_container_executor.sh)

该脚本中直接调用会话脚本,并等待其执行结果。例如:

代码语言:javascript
复制
#!/bin/bash
/bin/bash "/opt/hadoop/yarn/nodemanager/local/usercache/root/appcache/application_1667473260420_0002/container_e02_1667473260420_0002_01_000001/default_container_executor_session.sh"
rc=$?
...

2)会话脚本(default_container_executor_session.sh)

在该脚本里通过exec的方式调用container启动脚本。例如:

代码语言:javascript
复制
#!/bin/bash

echo $$ > /opt/hadoop/yarn/nodemanager/local/nmPrivate/application_1667473260420_0002/container_e02_1667473260420_0002_01_000001/container_e02_1667473260420_0002_01_000001.pid.tmp
/bin/mv -f /opt/hadoop/yarn/nodemanager/local/nmPrivate/application_1667473260420_0002/container_e02_1667473260420_0002_01_000001/container_e02_1667473260420_0002_01_000001.pid.tmp /opt/hadoop/yarn/nodemanager/local/nmPrivate/application_1667473260420_0002/container_e02_1667473260420_0002_01_000001/container_e02_1667473260420_0002_01_000001.pid
exec setsid /bin/bash "/opt/hadoop/yarn/nodemanager/local/usercache/root/appcache/application_1667473260420_0002/container_e02_1667473260420_0002_01_000001/launch_container.sh"

3)container启动脚本(launch_container.sh)

通过exec加输出重定向的方式,调用提交任务的命令。例如:

代码语言:javascript
复制
#!/bin/bash

...

exec /bin/bash -c "$JAVA_HOME/bin/java -Djava.io.tmpdir=$PWD/tmp -Dlog4j.configuration=container-log4j.properties -Dyarn.app.container.log.dir=/opt/hadoop/yarn/nodemanager/log/application_1667473260420_0002/container_e02_1667473260420_0002_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/hadoop/yarn/nodemanager/log/application_1667473260420_0002/container_e02_1667473260420_0002_01_000001/stdout 2>/opt/hadoop/yarn/nodemanager/log/application_1667473260420_0002/container_e02_1667473260420_0002_01_000001/stderr "

因此,对于一个container,一共会启动3个进程。

一个实际的例子如下所示:

代码语言:javascript
复制
[root@nm-0 opt]# jps
35237 YarnChild
168 NodeManager
36149 YarnChild
34391 MRAppMaster

[root@nm-0 container_e02_1667473260420_0002_01_000001]# pstree -ps 34381
dumb-init(1)───java(168)───bash(34381)───bash(34383)───java(34391)


[root@nm-0 container_e02_1667473260420_0002_01_000001]# ps -efww
hadoop 34381 168 0 08:54 ? 00:00:00 bash /opt/hadoop/yarn/nodemanager/local/usercache/root/appcache/application_1667473260420_0002/container_e02_1667473260420_0002_01_000001/default_container_executor.sh
hadoop 34383 34381 0 08:54 ? 00:00:00 /bin/bash -c /usr/lib/jvm/java/bin/java -Djava.io.tmpdir=/opt/hadoop/yarn/nodemanager/local/usercache/root/appcache/application_1667473260420_0002/container_e02_1667473260420_0002_01_000001/tmp -Dlog4j.configuration=container-log4j.properties -Dyarn.app.container.log.dir=/opt/hadoop/yarn/nodemanager/log/application_1667473260420_0002/container_e02_1667473260420_0002_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/hadoop/yarn/nodemanager/log/application_1667473260420_0002/container_e02_1667473260420_0002_01_000001/stdout 2>/opt/hadoop/yarn/nodemanager/log/application_1667473260420_0002/container_e02_1667473260420_0002_01_000001/stderr
hadoop 34391 34383 45 08:54 ? 00:00:23 /usr/lib/jvm/java/bin/java -Djava.io.tmpdir=/opt/hadoop/yarn/nodemanager/local/usercache/root/appcache/application_1667473260420_0002/container_e02_1667473260420_0002_01_000001/tmp -Dlog4j.configuration=container-log4j.properties -Dyarn.app.container.log.dir=/opt/hadoop/yarn/nodemanager/log/application_1667473260420_0002/container_e02_1667473260420_0002_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

注意:整个脚本调用是比较简单,其逻辑也是很清晰的,但这里涉及到的一个知识点是:运用了不同方式来调用脚本(程序),会涉及到是否创建子进程。

假设有一个程序(假设取名main),程序内部什么也做,就死循环睡眠,然后在另外一个脚本(假设取名executor.sh)以下面几种方式调用该程序,来看看分别会创建几个进程?

方式1:直接调用该程序

代码语言:javascript
复制
#!/bin/bash
./main

方式2:通过exec的方式

代码语言:javascript
复制
#!/bin/bash
exec /bin/bash "./main"

方式3:通过exec的方式,并重定向标准输出

代码语言:javascript
复制
#!/bin/bash
exec /bin/bash "./main 1>tmp.log 2>&1"

上面三种方式的答案分别是2、1、2

首先,在shell中,执行任何一个命令(程序)都是以创建一个新进程的方式来运行的。因此在方式1中,一共有两个进程,一个是"executor.sh"脚本自身的进程,另外一个是运行main程序的进程。

代码语言:javascript
复制
[root@localhost dockerfile]# pstree -sp 65995
systemd(1)───sshd(71917)───sshd(225687)───bash(226675)───sh(65994)───main(65995)
[root@localhost dockerfile]# ps -efww | grep 65994
root      65994 226675  0 08:32 pts/1    00:00:00 sh executor.sh
root      65995  65994  0 08:32 pts/1    00:00:00 ./main

其次,exec并不启动新的进程,而是用将要被执行的命令(程序)来替换当前的shell进程,然后将原有进程的环境变量全部清除,并且在exec之后的命令均不会再执行。因此在方式2中,只有一个进程。

代码语言:javascript
复制
[root@localhost dockerfile]# pstree -sp 70995
systemd(1)───sshd(71917)───sshd(225687)───bash(226675)───main(70995)
[root@localhost dockerfile]# ps -efww | grep 70995

最后,对于标准输出重定向,总是会创建一个新的进程出来,不管是否采用exec的方式。因此,对于第三种方式而言,也是会创建两个进程。

代码语言:javascript
复制
[root@localhost dockerfile]# pstree -sp 74781
systemd(1)───sshd(71917)───sshd(225687)───bash(226675)───bash(74780)───main(74781)
[root@localhost dockerfile]# ps -efww | grep 74780
root      74780 226675  0 08:35 pts/1    00:00:00 /bin/bash -c ./main 1>tmp.log 2>&1
root      74781  74780  0 08:35 pts/1    00:00:00 ./main

【yarn application kill命令的】

1、命令的执行流程

执行yarn任务kill命令时,本质上是向resourcemanager发送了一个rpc请求。

rm收到请求后内部会进行一些判断及处理,比如判断任务是否存在、application是否已经调度等,对于正在运行的application,最终会通过心跳告知NM需要进行清除。

NM通过定时心跳从RM得到需要清理的container,内部也会进行一系列判断和处理,(对于DefaultContainerExecutor而言)最终处理方式是对需要清除的container,为对应的进程(`default_container_executor_session.sh`)执行kill动作。

2、kill过程中的处理细节

在Nodemanager执行Kill的过程中,有两个细节需要注意:

1)kill进程的时候,是从pid文件里读取进程的pid,然后执行kill动作。但是pid文件里仅记录了"default_container_executor_session.sh"进程对应的pid,而真正的java程序的进程pid则没有记录。如果仅仅只是对shell进程进行kill,那么,java程序进程依旧会继续运行,但父进程的pid变为nodemanager。这显然是不符合逻辑的。因此,走读源码发现,使用“kill -SIGNAL -- -$gpid”的方式对整个进程组中的进程进行kill。这样保证可以将相关的进程一并删除。

例如通过strace抓取NM及其子进程的系统调用,可以观察到对应的动作:

2)kill进程的时候,会分两阶段进行。一次是"kill -15",即发送"TERM"信号;一次是"kill -9",即发送"KILL"信号。两次kill之间的间隔由配置项"yarn.nodemanager.sleep-delay-before-sigkill.ms"决定。而这么做的意义在于,第一次发送TERM信号,让AM有机会捕获该信号,进行相应的清理动作,比如清除在HDFS中上传的资源文件。第二次发送KILL信号,则是确保对应的进程强制结束。

看到这里,再回顾文章开始提到的问题,想必大家也都能分析出其中的原因来了。

小结一下,本文总结了yarn中一个container涉及的linux进程,并穿插介绍了shell命令调用与进程创建之间的关系。最后对yarn任务kill动作的执行流程进行了简单梳理,尤其是nm中的一些小细节。这里主要是针对DefaultContainerExecutor的情况,对于LinuxContainerExecutor和DockerContainerExecutor会稍有不同,后续有时间再来分析。有兴趣的也可以一起来交流。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-11-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 陈猿解码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档