systemd 能管大多数服务,但总有例外:容器里跑的业务进程、第三方不给 unit 文件的程序、需要按业务逻辑判断存活的场景。这时候一个轻量守护脚本比强行套 systemd 更实际。
探活:端口 vs 进程
只看进程活着不够——进程在但服务已经僵死(死锁、线程打满)是常见故障。要看端口是否真的能建立连接:
def probe(host, port, timeout=3):
try:
with socket.create_connection((host, port), timeout=timeout):
return True
except OSError:
return False
更进一步可以发一个业务健康检查请求,但要注意别让探活本身成为压力,控制好频率和超时。
核心逻辑
#!/usr/bin/env python3
"""proc_guard.py —— 进程守护:探活失败则拉起,连续失败则告警"""
import os
import socket
import subprocess
import sys
import time
NAME = "myapp"
PORT = 8080
CMD = ["/usr/local/bin/myapp", "--config", "/etc/myapp.conf"]
MAX_FAIL = 3 # 连续失败多少次才告警
CHECK_INTERVAL = 30 # 巡检间隔(秒)
ALERT_CMD = ["/usr/local/bin/notify.sh"]
def probe():
try:
with socket.create_connection(("127.0.0.1", PORT), timeout=3):
return True
except OSError:
return False
def is_running():
"""用 pidof 判断进程是否存在,避免误判"""
r = subprocess.run(["pgrep", "-f", NAME], capture_output=True)
return r.returncode == 0
def start():
with open("/tmp/%s.out" % NAME, "ab") as out:
subprocess.Popen(CMD, stdout=out, stderr=subprocess.STDOUT,
start_new_session=True) # 脱离守护进程,防止被一起杀
def alert(msg):
subprocess.run(ALERT_CMD + [msg], capture_output=True)
print("[ALERT] %s" % msg, flush=True)
fails = 0
while True:
if probe():
fails = 0
else:
fails += 1
print("探活失败第 %d 次" % fails, flush=True)
if not is_running():
print("进程不存在,执行拉起", flush=True)
start()
time.sleep(10) # 给启动留时间,别立刻又判失败
continue
if fails >= MAX_FAIL:
alert("%s 端口 %d 连续 %d 次探活失败,进程存在但无响应"
% (NAME, PORT, fails))
fails = 0 # 重置,避免每分钟刷一条告警
time.sleep(CHECK_INTERVAL)
三个关键设计
1. start_new_session=True
让被拉起的进程脱离守护进程的进程组。否则守护脚本退出时,子进程可能收到 SIGHUP 一起死掉。
2. 拉起后等待再探活
进程启动要时间,拉起后立刻探活必然失败,会误判成”拉起无效”反复重启。等 10 秒再判断。
3. 告警后重置计数
否则每 30 秒一条告警,一小时 120 条,告警渠道直接被刷爆,真正的故障反而被淹没。
配合 systemd 更稳
守护脚本自己也可能死。用 systemd 托管守护脚本,双保险:
# /etc/systemd/system/proc-guard.service
[Unit]
Description=proc guard
[Service]
ExecStart=/usr/bin/python3 /usr/local/bin/proc_guard.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
这样:进程挂了 → 守护脚本拉起;守护脚本挂了 → systemd 拉起守护脚本。
日志要轮转
守护脚本持续输出,日志会无限增长。加一条 logrotate:
/var/log/proc_guard.log {
daily
rotate 7
compress
missingok
notifempty
}
什么时候别用这套
如果服务本身能用 systemd 管,就用 systemd。Restart=always 加 WatchdogSec 已经覆盖大部分场景,自己写守护反而多一个要维护的故障点。脚本只用于 systemd 搞不定的情况。