agent是一种计算机程序,它的特点包括持久运行、无需人工干预自主执行某些活动、能够与其他组件协调共同完成任务、能够感知并响应环境变化等特点。
在实际运行中,由于计算机资源、人为误操作等原因,agent往往很难实现长时间持久化的运行。
笔者整理了几种agent维活方法:
supervisord
首先的想法是使用其他进程来监听agent进程,当agent进程停止后主动拉起。这种方式的典型工具是supervisord。
Supervisord是一个用Python语言编写的进程管理工具,它可以很方便地监听、启动、停止、重启一个或多个进程。当一个进程意外被杀死,Supervisor监听到进程死后,可以很方便地让进程自动恢复,不再需要程序员或系统管理员自己编写代码来控制。
supervisord提供了supervisorctl命令来管理它所管理的任务,例如,可以使用supervisorctl status命令来查看所有进程的状态,或者使用supervisorctl start myprogram命令来启动名为myprogram的进程,stop命令可以停止一个进程,restart命令可以重启一个进程。
详细命令可以参考官方文档:http://supervisord.org/
Supervisord本身是一个常驻进程,在大多数情况下都能够稳定运行,但是如果它意外崩溃或被杀死,就需要有其他机制来重启supervisor进程。比如我们再使用一个supervisor2进程来监听并维活supervisor进程。 比如定时任务corntab:
corntab
Crontab是Linux系统中用于定时执行任务的命令。它可以让用户在指定的时间自动执行某些命令或脚本。Crontab命令的格式为:
* * * * * command
其中,星号代表时间的各个字段,分别表示分钟、小时、日期、月份和星期几。例如*
表示任意时间,*/5
表示每5分钟,1,2,3
表示第1、2、3分钟等。
可以使用crontab -e
命令来编辑当前用户的Crontab文件,并添加定时任务。也可以使用crontab -l
命令来查看当前用户的Crontab文件。
使用Crontab添加脚本定期检查Supervisord进程是否在运行,如果不存在则启动它。下面是一个简单的示例脚本,它检查Supervisord进程是否在运行并自动拉起:
#!/bin/bash
# 检查 supervisord 是否正在运行
pid_cnts=$(ps -ef | grep supervisord | wc -l)
# 如果 supervisord 未运行,则启动它
if [ $pid_cnts -eq 0 ]; then
echo "my_program is not running, starting it"
/usr/bin/supervisord &
fi
将上面的脚本保存为一个文件,例如/usr/local/bin/check_supervisord.sh
,并使用chmod +x /usr/local/bin/check_supervisord.sh
命令添加执行权限。
然后使用crontab -e
命令编辑当前用户的Crontab文件,并添加一行,每5分钟运行一次check_supervisord.sh
脚本:
*/5 * * * * /usr/local/bin/check_supervisord.sh
在实际使用过程中,我们还需要考虑脚本执行超时、执行失败、crontab重复拉起导致脚本并发执行等等问题,实际生产可用的脚本要比上述脚本复杂得多。
另外,脚本只会在crontab表达式所描述的时间点执行,这个时间间隔就是程序自动拉起的最大间隔时间,在实际工作中crontab脚本一般设置为分钟级,分钟级的间隔对于某些应用来说可能还是太长了。
那么有没有一种机制,能够在supervisor异常退出后能够直接感知到,并自动拉起呢?有的,比如我们接下来要说的systemd机制:
systemd
你可以使用操作系统提供的进程监控工具来监控Supervisord进程并在需要时重启它。
在Linux系统上,可以使用systemd来管理Supervisord进程。可以创建一个systemd服务来启动Supervisord,并配置服务在Supervisord崩溃时自动拉起。
在/etc/systemd/system/目录下创建一个名为supervisord.service的文件,然后在其中添加如下内容:
[Unit]
Description=Supervisor process control system for UNIX
Documentation=http://supervisord.org
After=network.target
[Service]
ExecStart=/usr/bin/supervisord -n -c /etc/supervisor/supervisord.conf
ExecStop=/usr/bin/supervisorctl $OPTIONS shutdown
ExecReload=/usr/bin/supervisorctl $OPTIONS reload
KillMode=process
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
上面的配置指定了Supervisord的启动命令、停止命令和重载命令。它还指定了当Supervisord崩溃时,systemd应该在5秒后重启它。
创建完成后,你可以使用systemctl start supervisord命令来启动Supervisord服务,或者使用systemctl enable supervisord命令来配置Supervisord服务在系统启动时自动启动。
关于systemd的更多说明和用法可以参考https://systemd.io/
systemd会感知到进程的异常退出(返回值不为0),并自动拉起。但是实际环境中,某些应用程序可能会陷入某种异常状态,进程仍然存活,但是无法正常工作了。systemd提供了watchDog机制来自动重启进程
要使用 Watchdog 来监测系统运行情况,需要以下2个步骤:
1、在 service unit 文件中添加配置:在 [Service]
部分中添加 WatchdogSec
配置来指定 Watchdog 的超时时间。
2、在程序中使用 sd_notify
函数来向 systemd 发送心跳信号,以通知 systemd 程序仍在运行。
下面的例子中配置了一个名为 my_program
的程序,并启用了 Watchdog 功能:添加了 WatchdogSec=30s
配置来指定 Watchdog 的超时时间为 30 秒。这意味着如果 my_program
在 30 秒内未向 systemd 发送心跳信号,则 systemd 将认为该程序已经挂起,并根据配置的重启策略(例如 Restart=on-failure
)来重启该程序。
[Unit]
Description=My Program
[Service]
ExecStart=/path/to/my_program
Restart=on-failure
WatchdogSec=30s
[Install]
WantedBy=multi-user.target
在程序中发送心跳信号的代码如下:
import (
"github.com/mdlayher/sdnotify"
"log"
"time"
)
func StartWatchDogNotify() {
n, err := sdnotify.New()
if err != nil {
log.printf("error create systemd notify: err=%+v", err)
return
}
// 创建之后马上发一条
err = n.Notify(sdnotify.Ready)
if err != nil {
log.printf("error send sdnotify.Ready notify: err=%+v", err)
return
}
go func() {
ti := time.NewTicker(time.Second * 10)
defer ti.Stop()
for true {
select {
case <-ti.C:
log.printf("send watchdog notify")
err := n.Notify("WATCHDOG=1")
if err != nil {
log.printf("error send WATCHDOG notify: err=%+v", err)
return
}
}
}
}()
}
总结
本文中讨论了几种进程维活的机制,实际生产环境各不相同,需要因地制宜选择或者综合使用,以达到更好的维活效果。