把定时任务工程化:脚本化巡检比「智能体定时跑」更靠得住
给一批线上服务做日常巡检,最初的做法是让 AI 助手每天定时跑一轮:读日志、判断异常、写一段总结推送出来。跑了几个月之后,我们把其中大部分巡检改回了「纯脚本 + 只在异常时输出」的老办法。这不是 AI 不行,而是巡检这件事的工程属性决定了它需要确定性。
下面是我们实际落地的一套结构与几条硬规则。
一、巡检的第一需求是「不出声」
巡检任务最理想的输出是空。绝大多数日子里服务都正常,如果每天固定推一条「一切正常」,接收方很快就会把它折叠、忽略,直到真正的告警混在同一堆消息里被一起划掉。
所以脚本的约定很明确:正常就一句话都不打印,退出码 0;异常才把证据打到标准输出,退出码非 0。调度侧据此决定是否推送。这个约定带来一个副作用——脚本必须对「正常」有非常清楚的判定,不能靠模糊描述。
一个典型的巡检脚本骨架:
def main():
problems = []
for svc, url in SERVICES.items():
try:
r = requests.get(url, timeout=5)
if r.status_code != 200:
problems.append(f"{svc} HTTP {r.status_code}")
except Exception as e:
problems.append(f"{svc} 连接失败: {e}")
if problems:
print("\n".join(problems))
sys.exit(1)
二、容器巡检要看镜像摘要,不要看「运行中」
早期我们只检查容器是否在跑、端口是否监听。后来遇到两次「进程在、逻辑已经坏了」的情况:旧容器还在跑,但镜像其实早已被上游更新,本地一直停在半年前的版本上。
改法是把「当前镜像 ID」和「远端仓库最新摘要」做比对,只比较 digest,不比较 tag。tag 会被覆盖,digest 不会。真正需要关注的两种情况只有:本地镜像与远端不一致,或者容器重启次数在窗口期内异常增长。
docker inspect --format '{{.Image}}' svc
docker manifest inspect registry/svc:latest | jq -r '.config.digest'
三、把「能自动判断」和「必须人判断」分开
巡检项大致分成两类。第一类判据是硬的:进程有没有、端口通不通、证书还有几天到期、备份文件大小是否在合理区间。这类全部脚本化,零容错空间。
第二类是软的:日志里出现了新的错误模式、响应时间分布有漂移、某个接口的返回体结构发生变化。这类不适合让脚本下结论,但可以脚本负责采集和初筛——把「一定概率有问题」的片段截取出来,交给人或者其他分析环节判断。
这条边界划清楚之后,脚本的复杂度反而下降了:不需要在脚本里写一堆 if-else 猜意图,只需要做可复现的采集与阈值判断。
四、日志与状态要能自证
脚本化的另一个好处是可回溯。每次执行都留一份结构化记录:时间、检查项、实际值、判定结果。出问题时能回答「昨晚那一轮到底看到了什么」,而不是「当时好像没问题」。
日志建议写成一行一个 JSON 对象,字段固定,方便 grep 也方便后续喂给分析工具:
{"ts":"2026-11-12T08:40:03+08:00","check":"cert_expiry","target":"api.example.com","value":12,"verdict":"warn"}
五、失败也要有边界
任何自动巡检脚本都必须回答一个问题:脚本自身挂了怎么办。我们的做法是给脚本加一个外层看门狗——超过预期耗时直接杀掉并上报,而不是让它静默卡死。同时脚本内的每个外部调用都带超时,避免一个卡住的 HTTP 请求拖垮整轮检查。
另外,巡检脚本不要做有副作用的操作。发现异常就只汇报,重启、扩容、清理这类动作交给独立的、有人确认的流程。把「观测」和「干预」混在一个脚本里,是运维事故的常见起点。
小结
巡检脚本的价值不在于它多聪明,而在于它每天用完全相同的方式看同样一批指标,并且只在事情不对的时候才开口。确定性、静默、可回溯,这三点做到了,一套几十行的脚本能顶住大部分日常值守;剩下的复杂判断,再交给需要推理的环节去做,各归其位。
- 点赞
- 收藏
- 关注作者
评论(0)