crontab 被同步脚本每 30 秒静默覆盖的排查与迁移

举报
yd_254065487 发表于 2026/10/25 08:55:21 2026/10/25
【摘要】 定时任务凭空消失、crontab -l 里干干净净?本文用一条 RELOAD 日志和备份时间戳链定位覆盖者,并给出迁到独立调度的两套方案。

早上八点,群里一句“昨天的对账任务今天没跑”。登上机器执行 crontab -l,输出干干净净——不是报错,是任务本身不在了。脚本还在 /opt/scripts/recon.sh,权限没动过,手动跑一次完全正常。这类“昨天还好好的、今天不跑了”的问题,九成不是 cron 挂了,而是有人在你不知道的地方重写了 crontab。

一、先看文件修改时间,再决定查谁

crontab 的落盘位置分两派:RHEL/CentOS 系在 /var/spool/cron/<user>,Debian/Ubuntu 系在 /var/spool/cron/crontabs/<user>。先看 mtime,用完整时间戳格式,不要用默认的“10月25日”这种模糊输出:

ls -l --time-style=full-iso /var/spool/cron/

如果 mtime 就在几分钟前,而你今天根本没执行过 crontab -e,基本可以定性为被程序覆盖。更直接的证据是盯着它看:隔十几秒再看一次 mtime,如果每次都变,说明有个高频周期进程在写它。mtime 的更新频率就是覆盖者的调度周期,这一步能直接把嫌疑范围从“某天某次操作”压缩到“每 30 秒一次的后台流程”。

二、cron 自己会记账:RELOAD 日志

覆盖者调用的多半是 /usr/bin/crontab 命令(或直接写文件),而 crond 会把每次重载记进日志。RHEL 系查 /var/log/cron,Debian/Ubuntu 查 journalctl -u cron:

grep RELOAD /var/log/cron | tail -20

正常机器上这条日志一天出现几次;如果它是每 30 秒一条、整整齐齐排下去,凶手就在系统里:

CROND[2317]: (root) RELOAD (/var/spool/cron/root)

同时 /var/log/secure(或 /var/log/auth.log)里会有成对的 crontab 调用记录,能拿到执行用户。但日志只告诉你“有人在写”,不告诉你“是谁在写”,下一步要抓进程。

三、抓现行:审计规则和周期进程

加一条审计规则盯住写入和属性变更:

auditctl -w /var/spool/cron/root -p wa -k cron_tamper

等它再写一次,然后回查。注意 ausearch 必须带 -i 才会把数字解析成名字:

ausearch -k cron_tamper -i | tail -40

输出里会看到 comm="crontab"、exe="/usr/bin/crontab",以及关键的 ppid=。拿着 ppid 去 ps -p <ppid> -o pid,ppid,cmd 反查父进程;如果进程已退出,就用 grep ppid /var/log/audit/audit.log | grep crontab 把同一时刻的 execve 记录串起来。没有 auditd 的机器,用 inotify-tools 也能抓:

inotifywait -m -e close_write,attrib /var/spool/cron/

它每次触发都会打印时间戳,和任务消失的时间点比对即可。再并行开一个窗口,每 15 秒 dump 一次进程树,抓那种“跑 0.1 秒就退出”的脚本:

while true; do ps -eo pid,ppid,lstart,cmd | grep -E 'crontab|sync' | grep -v grep; sleep 15; done

四、备份时间戳链:顺着目录找源头

会覆盖 crontab 的工具几乎都会先备份再写,这是它唯一的破绽。按时间戳找那种“每 30 秒一个新文件”的目录:

find / -xdev -type f \( -name '*cron*bak*' -o -name '*crontab*20*' \) -mmin -60 2>/dev/null | head -40

找到候选目录后 ls -lt --time-style=full-iso,文件的秒级间隔会暴露周期。把这些备份文件的 mtime 与任务最后一次正常执行的时间对齐,就能确定第一份被覆盖的备份是哪一次、源头从什么时候开始跑的。最后全局搜一次调用点,确认是自研脚本还是配置管理:

grep -rn --include='*.sh' --include='*.py' -e 'crontab -' -e '/var/spool/cron' /opt /usr/local /etc/cron.d 2>/dev/null

常见源头有三类:自研的“配置同步”脚本把模板文件当权威源,单向覆盖;配置管理工具(docker entrypoint 包装脚本、Ansible 的 cron 模块等)周期性下发全量 crontab;以及用模板渲染后 crontab template.txt 一把刷的部署流水线。它们的共同特点是:模板里没有的任务,等于不存在。

五、方案一:迁到容器内的 /etc/cron.d 独立文件

如果调度跑在容器里、且镜像里已经有 cron 守护进程,最省事的做法是把任务从用户 crontab 挪到 /etc/cron.d 下自己的文件,不碰 crontab 命令,自然也不会被那个模板覆盖。两个坑必须注意:

  • /etc/cron.d 下的每个文件是 6 段格式,比用户 crontab 多一个用户名列,写成 5 段会静默不执行;
  • 文件名不能带点(sync.job 会被跳过),用 10-sync-job 这种带序号的名字,权限 chmod 644 且属主 root。

内容是:

*/1 * * * * appuser /opt/scripts/sync.sh >> /var/log/sync.log 2>&1

把这份文件当作镜像构建产物固化进 Dockerfile,而不是运行时由外部脚本写入——固化之后,即使有人重刷 crontab,这个文件仍在。代价是粒度只能到分钟,并且要自己处理日志轮转和漏跑。

六、方案二:systemd timer,秒级且自带日志

宿主机上的任务,我更推荐直接离开 cron 生态,改用 systemd timer。它不读 crontab,覆盖者动不到它,还能表达“每 30 秒”。先写服务单元 /etc/systemd/system/sync-job.service:

  • [Unit] 段写 Description=Data sync job
  • [Service] 段写 Type=oneshot
  • [Service] 段写 ExecStart=/opt/scripts/sync.sh
  • [Service] 段写 User=appuser

再写 /etc/systemd/system/sync-job.timer:

  • [Timer] 段写 OnBootSec=30s
  • [Timer] 段写 OnUnitActiveSec=30s
  • [Timer] 段写 AccuracySec=1s
  • [Timer] 段写 Persistent=true
  • [Timer] 段写 Unit=sync-job.service
  • [Install] 段写 WantedBy=timers.target

启用:

systemctl daemon-reload && systemctl enable --now sync-job.timer

验证用 systemctl list-timers --all 看下一次触发时间,查历史用 journalctl -u sync-job.service -n 50 --no-pager。相比 cron,这里有两个实打实的好处:每次执行的 stdout/stderr 自动进 journal,不需要自己重定向;Persistent=true 配合 OnCalendar= 能在关机期间错过时补跑,而 cron 不会。同一个任务重复触发时,把 Type=oneshot 保持住,脚本内部再加文件锁,避免上一轮没跑完就被拉起。

七、怎么选,给个明确结论

  • 容器内、镜像里已有 cron、周期只需到分钟:选 /etc/cron.d 独立文件,改造成本最低,构建期固化即可免疫覆盖。
  • 宿主机、需要 30 秒级、需要补跑或需要看执行日志:选 systemd timer,日志和补跑机制是白送的。
  • 无论哪种,都不要继续把业务任务放在用户 crontab 里,只要那台机器上存在“模板覆盖 live crontab”的流程,用户 crontab 就不是你的。真正要修的是那个同步脚本:让它改成增量合并,或者在覆盖前校验非模板任务的保留白名单——否则下一个被删的可能不是定时任务。

最后补一句收尾动作:排查完把 crontab -l > /backup/crontab.$(date +%F) 做成每日模板里的第一条,审计规则 cron_tamper 保留不删。下次再出同样的问题,五分钟就能定位。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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