守护进程之云共享服务器GPU抢占

0x00 起因

昨天,大力找到我,想搞点事情…看图
在这里插入图片描述大力一顿描述之后,我心想:大概就是好几个人共享一台服务器,假如客户A正在使用GPU,这时候客户B来了,执行了一条调用GPU的命令,A就会被迫中止使用GPU,GPU资源则会被B占去,A只能重新执行命令,把B挤下去,才能再次获得GPU,既然是这样的话,那就写个守护进程嘛,当我对GPU的调用进程被别人挤掉之后,我再拉起来把别人挤掉不久OK了,SO EASY!
(P.S. 上面一段话是我在听完大力描述之后的心理活动。注意最后两个单词加粗大写,充分说明了我昨天的无知…唉,如果可以,我这辈子再也不要写shell脚本了)

0x01 分析

  1. 用什么语言写
    既然是在Linux下写守护进程,那就很方便了,Python,C,Shell都是很方便的选择啊。到底用啥好呢,emmmmmmm,涉及到Linux命令比较多,还是选Shell脚本吧。
  2. 实现原理
    从需求出发,从过程分析,可以得知,我想要在我进程被人挤掉的时候被拉起来:

1) 如何判断我的进程被挤掉了呢?
我调用GPU的任务是固定的,也就是说,我的进程名是固定不会变的,那我只要对系统当前所有的进程进行一个监测,就可以知道我的进程是否被挤掉了。核心命令如下:

#获取要守护进程的PID
PID=`ps -ef |grep PROCESS_NAME |grep -v grep |awk '{print $2}'`

将命令中的PROCESS_NAME 替换成你自己要守护的进程,该命令执行之后会获取要守护进程的pid值并传给变量PID
我们只要对变量PID进行判断,是否为空,就能知道我得进程是否还在运行了。如果为空,未获取到进程的PID,则说明进程被挤掉。

2) 当我的进程被挤掉的时候,我当然是要把它拉起来了!
怎么拉起来?在shell脚本里执行一下我调用GPU的命令呗

3) 我应该什么时候去监测我的进程被挤掉了呢?
谁都不知道下一个人什么时候来挤我,那我也只好全天24h跟你干了,写个死循环,不停地监听进程是否被挤掉。
但是我同时又想知道你是什么来怼我的,emmm,写个日志吧。
可是写日志的话,对磁盘读写太频繁了,会影响计算机运行速度(虽然我要写的东西并不多,我就这么一说,你就这么一听好了,嘿嘿),同时日志太多了我也看不过来啊,那咋办?恶魔妈妈买面膜…(emmmmmm…)那我设置个频率吧,每隔多久去监测一次进程好了。

  1. 好了,看一眼守护进程基本流程图吧,我用的画图画的,丑,有意见我也莫得法~~哈哈哈
    在这里插入图片描述

0x02 开搞

基本逻辑已经理清楚了,兴奋地开搞吧!(开始哭吧…)
哎,一年多没写Shell了,这该死的语法,太难写了,哭哭哭…

这该死的shell语法:
1. if [ 条件表达式 ]		#这里的条件表达式,一定要在中括号里面有两个空格把条件表达式隔开,
						#我卡在这里卡了不知道有几个小时,跑程序的时候,各种报错,说附近有语法错误,死活找不到,我擦!
2.唉,看看序号,自己想列出来的坑,突然懒得吐槽了,不写了!什么破语法规则,脑仁疼!
3.大家慢慢....
4.去踩坑吧....

在上述基本逻辑写完之后,先将程序跑起来,新开一个terminal,模拟进程被挤掉,执行:

kill -9 PID值

将GPU调用的进程结束,再去跑GPU的终端查看回显,发现进程被结束之后,又被自动拉起来了,顺利!查看一下日志输出大概就是:
① 正常运行…
②然后监测到进程down了
③进程拉起中…
④拉起成功
⑤运行正常…
此时,满心以为:嗯呐SO~~ EASY!好吧,不SO了,EASY!可恶的语法!
突然,进程又down了。
查看回显信息→回显卡在进程被kill的提示→查看日志→发现日志里写满了拉起失败…
咋回事???一个头,两个大的我赶紧检查GPU,CPU,进程列表,用户状态等等信息,发现其他共享人登上来,占用了GPU…尴尬,没拉起来。此时我的内心:不EASY啊!怎么办啊?
死马当活马医,我试了试kill掉对方的进程,再看看我的terminal,开始回显,再查看日志,哈哈拉起成功。
恶魔妈妈买面膜…看来需要先kill掉别人的进程。于是我在原来的逻辑框架上,加了一个kill别人进程的逻辑。
流程图如下:
在这里插入图片描述那实现获取别人进程以及遍历一个kill一个,是怎么实现的呢?
我一拍脑门,啊哈,SO~~EASY 递归!(注意大写加粗T~T)
于是就有了下面的递归函数

#!/bin/bash
# 使用   |grep -v PROCESS_NAME 来过滤掉自己的进程
PID_ARRAY=(`ps -a |grep -v bash |grep -v watch |grep -v sleep |grep -v grep |grep -v nvidia-smi |grep -v st |grep -v awk |grep -v ps |awk '{print $1}'`)

#递归杀进程
kill_ps()
{
        if [ $1 ]
        then
                kill -9 ${PID_ARRAY[0]}	#杀进程
                unset PID_ARRAY[0]	#删除数组中记录的已被杀掉的进程PID
                echo "$1 is killed" >> log_protect.log	#输出日志
                kill_ps PID_ARRARY[0]	#递归
        else
                return 0
        fi
}

#调用递归函数kill进程
kill_ps ${PID_ARRAY[0]}

#shell编程坑啊!unset掉下标为0的元素后,数组下标为1的元素并不会自动更新为0,还是1啊!!!!多么完美的递归函数,哎,我擦!

看到上面的递归,我只能叹气,我知道是我对shell的理解不到位,我只能换个办法,写个for循环:

#!/bin/bash
PID_ARRAY=(`ps -a |grep -v bash |grep -v watch |grep -v sleep |grep -v grep |grep -v nvidia-smi |grep -v st |grep -v awk |grep -v ps |awk '{print $1}'`)       #进程过滤

#循环杀进程
for i in ${PID_ARRAY[*]};do
        kill -9 $i
        echo "$i is killed" >> log_protect.log
done

完美!
for循环真简单啊,两下子就写好了,用什么递归??用什么递归嘛!!我真是个傻子!
这时候,回首 ~ 掏 ~ 看一下日志,额,输出的都是进程PID,杀的是谁啊,不知道啊,咋办呐?于是最后使用了while循环。也是最终使用的方式,因为只有用while循环,才可以一次性接收awk的多列输出(使用awk输出的PID和进程名)。
到这里,基本功能也就全部都实现了…放出代码如下:

#!/bin/bash
FLAG=1
frequency=$1   #延时频率,单位秒

echo "" >> log_protect.log	#当守护程序运行时,开始写日志,写到log_protect.log
echo "*START st.sh at $current  -------------------------" >> log_protect.log
while [ $FLAG ]
do
        cur_date="`date +%Y-%m-%d`"     #获取机器系统日期
        cur_time=$(date "+%H:%M:%S" --date="8 hours") #获取机器系统时刻,与用户有8小时时差,经计算后时差已消除
        #获取进程的PID
        PID=`ps -ef |grep PROCESS_NAME|grep -v grep |awk '{print $2}'`
        #检查要守护的进程是否活着
        if [ "$PID" ]
        then    #进程活着
                echo "[$cur_date:$cur_time] $PID PROCESS_NAME is running! " >>log_protect.log
        else    #进程down了
                echo "[$cur_date:$cur_time][Warning] PROCESS_NAME is down!" >> log_protect.log
                echo "          [$cur_time] Restar ..." >> log_st.log
                echo "          [$cur_time] killing other processes ..." >> log_protect.log

                #过滤掉己方进程,找出敌方进程
                ps -a |grep -v bash |grep -v watch |grep -v sleep |grep -v grep |grep -v nvidia-smi |grep -v st |grep -v awk |grep -v ps |awk '{print $1,$4}' |while read KILL_PID KILL_NAME
                do
                        kill -9 $KILL_PID      #kill敌方进程
                        echo "                          process [$KILL_PID:$KILL_NAME] is killed" >> log_protect.log     #输出到日志
                done
                echo command &  	#这里执行拉起要守护进程的命令,最后加上&让其后台执行,不然守护进程会卡在这里等待被守护进程执行结束,才会往下继续执行
                sleep 5 #延时5秒再检测进程是否重新拉起
                PID=`ps -ef |grep PROCESS_NAME |grep -v grep |awk '{print $2}'`	#这里对被守护进程进行监测,检查是否被拉起
                if [ $PID ]     #重新拉起进程
                then
                        echo "          [$cur_time]  Restart Success! " >> log_protect.log
                else
                        echo "          [$cur_time]  Restart Failed! " >> log_protect.log
                        echo "          [$cur_time]  Send E-mail ..." >> log_protect.log
                        #在此处添加发送E-mail的代码,与上一行的echo对齐
                fi
        fi
        sleep $frequency
done

直接在terminal下执行:

bash 守护进程名.sh [参数]

守护进程就跑起来了~
为什么不直接cd到守护进程所在目录然后

./守护进程名.sh

这样跑呢?是因为:
1)bash是大多数linux进程的父进程。
2)使用bash执行守护进程,ps命令最多只能看到守护进程是bash的子进程
3)一般都不敢乱杀bash进程,如果再把守护进程放到系统目录下,就可以给不是很熟悉Linux操作系统的人一个假象,误认为是系统进程而不敢乱杀它

当然了,代码中可以看到变量frequency赋值为$1,这是运行脚本时给它传入的参数,可以控制以每隔多长时间去监听要守护的进程,单位为秒。我本来写死成300秒的。后来为了让守护进程更灵活,于是改成传参的方式。
如果运行时没传参数怎么办?同时,我又想到一个问题:传参和写死其实都是限定了频率一个值,比如说是300秒,即5分钟。如果别人第一次跑GPU,刚好我把他进程kill了,他肯定很纳闷,以为系统故障或者BUG了?于是再跑一次他的进程,调起来了,我下一次kill他则将会是在5分钟后,于是他会发现~诶嘿,好像每隔5分钟,我的进程都会断一次啊,怎么回事?于是打电话给客服,然后大力被封号,哈哈哈(我是不是笑的太早了,万一我被捶怎么办?T…T)
于是随机数定义更新频率的方案又浮现在脑海中。如果我在运行守护进程的时候没有指定频率,就可以随机一个频率给守护进程。并且设置一个时段,例如1h。每过1h更新一下频率,这样就更难让人察觉到是我在使坏了~哈哈哈

具体怎么实现,我懒得说了,哈哈哈,睡觉了~

根据不同的频率获得方式,启动脚本时,向log写入不同信息,可以分辨出当前所使用频率以及是指定获得还是随机分配,如下图所示:
在这里插入图片描述

0x03 总结

  1. 回顾一下这两天走过的坑,恶魔妈妈买面膜…我再也不想写shell了T。T
  2. 除了已有功能外,为了脚本的可复用性,可以对写死的要守护的进程名,定义成变量,在执行脚本的时候,指定参数,传入进程名,可以实现对任意进程的守护
  3. 在结束守护进程的时候,是使用ctrl+c来结束的,可以劫持该信号,在守护进程被结束前,向log写入守护进程结束的原因。这样就可以区分守护进程是被手动结束的还是什么时候被别人kill掉的
  4. 如果守护进程能hook到kill进程对自己发出的信号,拦截下来,可以防止自己被kill
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值