crontab 每30秒被静默覆盖的定位与迁移方案

举报
yd_254065487 发表于 2026/11/03 05:20:54 2026/11/03
【摘要】 定时任务无故消失的真凶是另一条模板同步脚本,本文用备份时间戳链锁定覆盖者,并给出 cron.d 与 systemd timer 两种迁移选择。

现象:昨天还能跑的备份任务,今天静默消失了

周一早上发现数据库备份的日志停在周六凌晨 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=oneshot
ExecStart=/opt/backup/db_dump.sh

再建 /etc/systemd/system/db-backup.timer:

[Unit]
Description=Run DB backup daily
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=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 观察一个调度周期,确认任务按预期触发再收工。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。