Supervisor监控多进程任务—手撸python监控

学习笔记(杂项) 同时被 2 个专栏收录
6 篇文章 0 订阅
6 篇文章 0 订阅

Supervisor监控多进程任务

上回书说到,Supervisor进程监控之役,在下先取sendmail,再克Superlance,兵峰所指,所向披靡。却不知Supervisor大本营暗藏杀招,不输出非直接子进程的异常信息,一举扭转战局,我军兵败如山倒,最终劳民伤财一无所获。这一回,我军卷土重来,高举python大旗,采用斩首行动,直指问题根源。

问题分析

既然决定要自己写监控,那么首先要做的,就是找到异常事件的表现。只有知道了怎样的事件属于异常,才能对其进行监控。

再次召唤上回的图

启动
启动
启动
启动
启动
启动
启动
启动
启动
Supervisor
Task 1
Task 2
Task n
Script 1
Script n
Script 1
Script n
Script 1
Script n

script进程挂掉之后,Supervisor会尝试重启,并启动一个奇怪的无意义进程,这是上一回已经说清楚的。正常情况下,task进程启动后,会启动若干个script子进程,这些子进程应该在启动之后就不生不灭、不增不减了。如此,就很好监控了,每分钟获取当前全部script进程的pid,和上一分钟保存的作对比,发生变动,则进行邮件报警,然后重启对应的task即可。

具体实现

python做这类运维工作还是很方便的,先介绍一下psutil,psutil相比os这些封装的更高级一点,如下代码所示:

import psutil

for proc in psutil.process_iter():
    try:
        pinfo = proc.as_dict(attrs=['pid', 'name'])
    except psutil.NoSuchProcess:
        pass
    else:
        print(pinfo)

psutil的优点就是使用更简单方便,代码也很简洁,缺点么。。。丫的name全叫python,根本分不清谁是谁。

···
{'pid': 20049, 'name': 'python3'}
{'pid': 20052, 'name': 'python3'}
{'pid': 20053, 'name': 'python3'}
{'pid': 20056, 'name': 'python3'}
{'pid': 20063, 'name': 'python3'}
{'pid': 20076, 'name': 'python3'}
{'pid': 20079, 'name': 'python3'}
{'pid': 20081, 'name': 'python3'}
{'pid': 20082, 'name': 'python3'}
{'pid': 20086, 'name': 'python3'}
{'pid': 20087, 'name': 'python3'}
{'pid': 20090, 'name': 'python3'}
{'pid': 20092, 'name': 'python3'}
{'pid': 20103, 'name': 'python3'}
{'pid': 20106, 'name': 'python3'}
{'pid': 20116, 'name': 'python3'}
···

虽然没用psutil,但还是介绍一下,库多不压身。

我最后采用的还是原始的os,代码如下

lines = os.popen("ps -ef|grep python3").readlines()

这样可以获得在服务器上ps -ef的全部输出,也就是获得一行完整的进程名。但是这行得到的一行line是没有像psutil那样处理好的,只是一串空格隔开的纯文本而已,要从中获取pid、ppid、进程全名,还得对字符串简单处理一下:

for line in lines:
    # 根据空格分隔各元素,并去除空字符
    items = line.split(" ")
    items = [i for i in items if i]
    # 使用切片操作获取进程全名
    try:
        f_idx = items.index(feature_str)
        process_name = " ".join(items[f_idx:]).strip()
    except Exception as _e:
        logging.except(_e)
        continue
    # 构建元素
    element = {
        "name": process_name,
        "pid": items[1],
        "ppid": items[2]
    }

然后把这些存储着pid、name等信息的元素保存起来。其中,ppid等于supervisorpid的,就是task进程,ppid等于某个task进程的,就是属于这个task的script进程。一旦发现某个script进程的pid发生变动,就发送报警邮件。反正也贴了这么多代码了,再贡献一下发邮件的那个函数吧。

诶我又反悔了,不想贴了。

定时任务监控

这里的定时任务,是指类似于crontab管理的任务,到时间运行一下,运行完了就退出,等下次预订的时间再次运行。

定时任务进程也和script进程一样,不直属于supervisor管理,所以挂了也没法正常重启,甚至会被错误的重启后,永远的挂在线上,而本次运行没有结束,就再也没有下一次定时运行了。

这类任务就和刚才那种不生不灭、不增不减的script进程很不同的,它们的pid变了或没了都是很正常的事情,并不能用上面的方法监控,那就需要动一点灵活的脑筋了。

我灵光一闪,想到了用lsof -p这个指令。

首先,强制每个任务进程必须写日志,也就是说,正常运行的时候能通过lsof -p命令找到它正在写的日志文件。一个正常运行的进程,无论什么时候lsof -p,都能看到它正在交互的log文件,而错误重启的进程,是一个僵尸状态,不会读写任何log文件。那么,得到要监控的pid后,用lsof -p看它有没有在写日志,我们已经强制所有进程写日志了,谁不在写谁就有问题,谁有问题就重启谁,问题解决。

代码大概这个样子

def get_related_log(_pid: str) -> None:
    """
    获取进程正在读写的日志文件
    :param _pid: 进程pid
    :return: None
    """
    # 获取进程的list openfiles
    _lines = os.popen(f"lsof -p '{_pid}'").readlines()
    # 由于lsof执行比较慢,可能进程已经终止,识别这类情况
    if not _lines:
        logging.info(f"进程{_pid}或已终止")
        return
    has_log = False
    # 在openfiles中寻找log文件
    for _l in _lines:
        if "log" in _l:
            logging.info(_l)
            has_log = True
    # 未找到log文件,报警
    if not has_log:
        send_email()

在实际运行中,有过误报警的情况,也不知为何,就误报警了那么一次。

个人感觉这种监控方法不是特别好,如果这篇文章有幸被大牛看到,还希望指点一二。

  • 0
    点赞
  • 1
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值