crontab 每30秒被静默覆盖的定位与迁移方案
现象:昨天还能跑的备份任务,今天静默消失了
周一早上发现数据库备份的日志停在周六凌晨 2 点。登录机器 crontab -l,只剩下一行:
*/1 * * * * /opt/sync/touch.sh
自己上周加的 0 2 * * * /opt/backup/db_dump.sh 不见了。crontab -e 加回去,等一分钟再看,又没了。此时最容易误判为“crontab 服务异常”或者“有人手动改过”,实际两者都不是。
第一步:先确认不是 cron 服务问题
先排除 daemon 本身:
systemctl status crond,确认 active (running),无重启记录。journalctl -u crond --since "-1h",没有 reload 或报错。stat /var/spool/cron/root,看 mtime 是否在持续变化——如果每隔几十秒就变一次,说明有外部程序在写它。
当 stat 的 mtime 呈现 30 秒左右的规律跳动时,基本可以判定是外部程序在反复覆盖,而不是用户手动编辑。
第二步:备份时间戳链锁定覆盖者
如果留下的是 crontab -e 产生的备份,/var/spool/cron/ 下会出现 root~ 这类文件。但本例中覆盖者写的是原子替换(先写临时文件再 rename),所以 ~ 备份未必存在。更可靠的是用 inode 变化链来判断写入节奏:
watch -n 5 'stat -c "%y %i %s" /var/spool/cron/root'
如果 inode 每 30 秒变化一次,说明是 rename 替换,而非原地修改,那么覆盖者必然经过 /etc/cron.d/ 或某个常驻进程。接着查谁在近期写过目录:
lsof +D /var/spool/cron/ 2>/dev/null
如果拿不到,就把 auditd 打开,对目录加监控:
auditctl -w /var/spool/cron/ -p wa -k crontab_watch
然后 ausearch -k crontab_watch -i | grep proctitle,一般几十分钟内就能抓到调用 crontab 的进程,例如:
proctitle=2F7573722F62696E2F63726F6E746162... /opt/sync/sync_cron.sh
解码后确认是 /opt/sync/sync_cron.sh。打开它,内容就是问题根源:脚本从对象存储拉取一份模板 /opt/sync/cron.tpl,再用 crontab /opt/sync/cron.tpl 覆盖 live 配置,每 30 秒执行一次。模板里只有一行 touch 任务,你加的备份任务自然在 30 秒内消失。所以表现就是“昨天还好好的,今天不跑了”。
为什么不建议继续和它抢 crontab
这类模板同步在批量环境中很常见,用于保证所有机器的 crontab 一致,强行把自己的任务写回去,30 秒后仍会被覆盖,并会在多台机器上产生难以排查的“闪烁”任务。正确做法是把任务挪到 crontab 之外,让两条链路互不干扰。
方案一:容器内用 /etc/cron.d
如果任务运行在容器里,宿主机或镜像的 cron 往往并不存在。推荐把调度放进容器内的 /etc/cron.d/backup,格式与 crontab 相同但多一列用户:
0 2 * * * root /opt/backup/db_dump.sh >> /var/log/backup.log 2>&1
注意文件权限必须是 0644 且属主 root,否则 cron 会静默忽略。然后在容器入口启动 cron 前台进程:
exec /usr/sbin/cron -f
适合镜像已有 cron 包、任务数量少的场景。缺点是 cron 仍然依赖整点扫描,秒级调度做不到,且容器重启后 /etc/cron.d 里的文件需要由镜像或 entrypoint 保证存在。
方案二:systemd timer(推荐用于宿主机)
systemd timer 不读 /var/spool/cron/,天然免疫模板覆盖,且日志直接进 journal。以备份任务为例,先建 /etc/systemd/system/db-backup.service:
[Unit]Description=DB backup[Service]Type=oneshotExecStart=/opt/backup/db_dump.sh
再建 /etc/systemd/system/db-backup.timer:
[Unit]Description=Run DB backup daily[Timer]OnCalendar=*-*-* 02:00:00Persistent=true[Install]WantedBy=timers.target
启用并验证:
systemctl daemon-reload && systemctl enable --now db-backup.timer
systemctl list-timers | grep db-backup
journalctl -u db-backup.service -n 20
Persistent=true 保证机器关机错过时间点后开机补跑,这是 crontab 不具备的能力。
怎么选
- 任务在容器里、镜像里没有 systemd:选 cron.d,简单、无额外依赖。
- 任务在宿主机、需要补跑、需要按 unit 查日志:选 systemd timer。
- 需要秒级或跨机器统一调度:两者都不合适,应上独立调度系统,不要再和模板同步的 crontab 缠斗。
迁移完成后,把原 crontab 条目删除,并在模板同步脚本的负责人那里登记,避免后续有人再从模板把任务加回去。最后用 systemctl list-timers 或容器内 grep CRON /var/log/syslog 观察一个调度周期,确认任务按预期触发再收工。
- 点赞
- 收藏
- 关注作者
评论(0)