A-A+

宝塔面板bug:过了0点,CPU使用率就会突然上去,并且一直下不来

2023年04月27日 VPS评价 等您评论

宝塔面板在最近两年,有个很奇怪的bug,具体表现为一旦过了0点,CPU使用率就会突然上去,并且一直下不来可以考证的最早记录是2021年1月份pc6a写下的。具体情况是,在初始安装完成后,CPU的负载很低,表现一切正常。一旦过了0点,CPU使用率瞬间上去了。通过htop查看,占用CPU最高的是site_task.py,这个程序本身是系统的日志上报功能模块。但是每10秒钟就会出现一次,每次出现就极大的提高了CPU使用率,显得极其诡异。要不是因为能看到源码,一定会判定它是木马,实际上这个程序并不复杂,因此大概率是个逻辑错误或者程序员水平低下导致的bug。我使用的是Debian 11系统进行测试的,在腾讯云上有一篇记录,是在CentOS 7上测试出相同现象的。这说明了和系统本身相关性不大。

宝塔面板的源码是开放的,于是在GitHub上下载了下来,搜索和site_task相关的内容。最终发现了site_task.py是通过bt-task.c这个文件调用/www/server/panel/pyenv/bin/python3 /www/server/panel/script/site_task.py而运行起来的。注释里面写的是“网站到期时间处理”,有一个while true的死循环,每个小时执行一次。

仔细查看代码内容,发现这个循环中涉及到了日期更改。程序首先读取文件中的日期,判断和目前日期是否相同,如果相同就会睡眠一个小时。此外还有个将文件日期更改的功能,由于宝塔的代码稀烂,很难捋顺它的程序,我就不去细查了。此外,“重新标记执行日期并执行到期网站脚本”这里好像是缺了一个判断,它这种写法会导致频繁读写硬盘,降低运行效率。

宝塔服务器面板,一键全能部署及管理,送你3188元礼包,点我领取

言归正传,0点CPU高占用bug大概率就是这个模块导致的了,涉及到日期更改和site_task.py程序。出于某种原因(比如程序员水平稀烂)导致了逻辑错误,最终造成一旦日期更改了,面板就不知道要做什么,然后疯狂进行10秒一次循环。这种好几年的bug居然都没有发现,只能说宝塔的水平是极其差的。

临时的解决方法是设置定时任务,每天0点重启一次面板。终端输入crontab -e,然后写入2 0 * * * /etc/init.d/bt restart,即每天0点2分的时候重启一次面板,让日期归零,从而避开这个bug。我有点怀疑这个bug可能和Let's Encrypt的SSL证书申请有关,好像是在我全部使用第三方SSL证书之后才发现这个bug的。具体的表现可能是如果面板没有Let's Encrypt的SSL证书申请任务,那么就会出现这个bug。

分析

宝塔目前主版本代码会执行/www/server/panel/BT-Task作为BT-Task service。在/www/server/panel/BT-Task引入了 /www/server/panel/task.py并执行其中的main函数。在task.py中,main函数中会开启多个线程去调用其他任务, 最后主进程交由startTask函数进行处理。startTask是个死循环函数, 它会先执行从面板数据库中拿取的任务, 之后调用siteEdate函数进行网站到期处理。siteEdate 函数正常来说应该在一天内只执行一次/www/server/panel/script/site_task.py,但是很不幸宝塔程序员逻辑写错了:

def siteEdate():    
global oldEdate    
try:    
# 程序拿到的永远是启动时候/www/server/panel/data/edate.pl的值, 第二次执行时候虽然oldEdate不为空, 但是/www/server/panel/data/edate.pl已经发生变化了, 没有去更新    
# 两种解决方法, 删掉oldEdate判断或者在函数结束后将oldEdate手动置空    
if not oldEdate:    
oldEdate = ReadFile('/www/server/panel/data/edate.pl')    
if not oldEdate:    
oldEdate = '0000-00-00'    
mEdate = time.strftime('%Y-%m-%d', time.localtime())    
if oldEdate == mEdate:    
return False    
os.system(get_python_bin() +    
" /www/server/panel/script/site_task.py > /dev/null")    
except Exception as ex:    
logging.info(ex)    
pass

 

这将会导致在第一次执行后, 每次调用siteEdate函数时都会继续执行/www/server/panel/script/site_task.py。更糟糕的是,/www/server/panel/script/site_task.py中似乎也没有对此做处理(判断时间), 也就是说site_task.py会不断执行一系列没有必要的检测。

修复

建议官方尽快解决, 目前可用临时修复补丁进行修复,但不保证完全可靠(补丁基于7.7.0并且未经过长期及生产环境测试), 建议谨慎使用。

diff -uN old/task.py new/task.py    
--- old/task.py    2022-06-10 13:13:00.107691400 +0800    
+++ new/task.py    2022-06-10 13:17:52.807987000 +0800    
@@ -158,8 +158,7 @@    
def siteEdate():    
global oldEdate    
try:    
-        if not oldEdate:    
-            oldEdate = ReadFile('/www/server/panel/data/edate.pl')    
+        oldEdate = ReadFile('/www/server/panel/data/edate.pl')    
if not oldEdate:    
oldEdate = '0000-00-00'    
mEdate = time.strftime('%Y-%m-%d', time.localtime())

 

给我留言