1. 从一次半夜的报警说起:cron明明配置了,任务就是不跑
大概凌晨两点多,手机连着震了好几下。监控群里说线上某个数据同步任务已经两个小时没出数了。我第一反应就是去看那台机器上的crontab,结果配置好好的,命令也没写错,手动执行脚本也一切正常。但任务它就是没跑。
这类"cron不执行"的问题,干运维和后台开发的基本都遇到过。大多数人第一件事是检查crontab配置有没有语法错误,然后看crond服务是不是挂了,最后再看看日志。但说实话,真正线上出现过的人都知道,大多数时候crond是活着的,配置看着也没问题,脚本单跑还正常,可定时任务就是在某个时间点毫无动静。
这篇文章我就把自己这几年在cron问题上踩过的坑、排查过的案例、总结出来的方法论完整写一遍。适合谁看呢?一类是刚接手服务器没多久的初级运维,另一类是写脚本做数据同步、报表生成、缓存清理的后端开发,还有一类是用了xxljob这类分布式调度平台但仍然需要依赖服务器cron做兜底的架构师。文章不会只讲"看日志"这种泛泛的东西,我会把整个排查链路拆成可执行的具体步骤,每一步都告诉你为什么要这么做,以及最常见的坑在哪里。
先说一个我自己的判断习惯:凡是cron定时任务不执行,先别怀疑crond挂了,先怀疑环境的差异。因为手动执行和cron执行,最大的区别不在于命令本身,而在于"谁"在执行它、在什么环境里执行它、在什么工作目录下执行它。搞清楚这三件事,一半以上的问题就已经解决了。
不过在我展开讲排查链路之前,有一个非常反直觉的事实我必须先抛出来:在绝大多数Linux发行版上,crond进程即使配置有误,也不会在系统日志里留下任何"任务执行失败"的记录。 更麻烦的是,很多发行版默认的cron实现甚至不会为普通任务保留独立日志。这意味着你如果一开始就把排查重心放在"看日志"上,大概率会白忙活半天。真正可靠的证据链,其实藏在另一个地方,后面我会专门写一节。
先搭一个整体的排查框架,后面每一节都围绕这个框架展开。整个排查思路可以分成六层:行为层、环境层、依赖层、实现层、触发层、平台层。行为层解决"任务到底跑没跑"的问题;环境层解决"为什么跑起来跟手动不一样"的问题;依赖层解决"为什么脚本里的命令、环境变量、网络不可用"的问题;实现层解决"为什么Java/C#这些语言写的任务特别容易出问题"的问题;触发层解决"为什么时间到了但没触发"的问题;平台层解决"为什么用了分布式调度平台还是会有漏执行"的问题。下面按照从浅入深、从单机到分布式的顺序,一条一条拆开讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. crond的"日志隐身术":日志和status文件修改时间才是硬证据
2.1 MAILTO/输出重定向:任务其实跑了,你看不见而已
先看一个最经典、也最容易误判的场景。你在crontab里写了这样一行:
code复制0 2 * * * /opt/scripts/sync_data.sh
凌晨两点到了,任务没产生任何输出日志,数据也没同步,于是你断言"cron没执行"。但你有没有想过——这行配置执行了,只是执行的结果在系统日志里根本看不到,脚本的输出又被cron吞掉了。
大多数Linux发行版,默认情况下cron执行任务时,如果任务产生了标准输出(stdout)或标准错误(stderr),并且没有做重定向,crond会把输出内容通过邮件发送给执行任务的那个用户(本地mail)。很多服务器上根本没装邮件服务,或者mail命令没人配置,这些输出就直接进了/var/mail或者/var/spool/mail,成了一堆没人看的垃圾文件。在某些最小化安装的系统上,甚至直接就丢了。
所以第一步的排查动作就两个:一是看系统日志里有没有那个时间点的crond相关记录(虽然大多数发行版默认没有,但看一眼成本很低,万一你的系统开启了就赚到了);二是看这个用户有没有本地邮件,邮件内容往往就是任务执行时的报错输出:
bash复制ls -la /var/mail/
cat /var/mail/root
这里有一个很多人不知道的判断技巧:直接看cron状态文件或者日志文件的修改时间。例如在CentOS/RHEL系列的cronie实现里,系统日志通常在/var/log/cron;而Debian/Ubuntu的cron默认只记录到syslog,很多时候需要自己配置。更简单的做法是打开cron的日志审计:
bash复制systemctl status crond
grep CRON /var/log/cron
但如果你发现日志文件时间戳在那个时间点变了,哪怕里面没有你那条任务的记录,也说明crond进程确实"醒来"过、处理过某个队列。再配合一个更有说服力的证据——cron的status文件,例如/var/lib/systemd/cron或类似路径,它就记录了每个任务的最近执行状态,文件内容的修改时间本身就是任务有没有被调度的铁证。
2.2 最保险的三个月习惯:每条cron任务强制重定向输出
我自己处理过的线上cron问题里,有接近四成最后都指向了同一个习惯问题:写crontab的时候不重定向输出。这个习惯必须改。
正规写法是给每条可能产生输出的任务加上标准输出和标准错误的落盘:
bash复制0 2 * * * /opt/scripts/sync_data.sh >> /var/log/sync_data.log 2>&1
追加日志而不是覆盖日志,这一点必须注意。很多人图省事用单个>符号,结果第二次执行就把第一次的日志清了,真出问题时现场早就被破坏掉了。正确做法是统一追加到日志文件,日志轮转交给logrotate处理。
加了重定向之后,你就拥有了第一个"任务到底跑没跑"的一手证据。凌晨两点任务执行完,第二天早上第一件事就是看这个日志文件的最后修改时间。有一种情况很诡异:日志文件时间戳变了,但里面是空白的,或者只有几行不知所谓的输出。这种情况往往说明脚本开头就出了问题,比如某个source路径不存在导致脚本直接退出。日志文件时间变了,本身就是"cron执行了你的任务"的强证据,这时候责任就从cron转移到了脚本本身。
再补充一个很有用的自我检测小技巧。如果你不确定crond的执行环境到底长什么样,可以故意在crontab里加一条这样的任务,让它每个小时执行一次然后看输出:
code复制15 * * * * env > /var/log/cron_env.log 2>&1
env命令输出的就是cron环境下所有环境变量和PATH的完整内容。跑几次之后打开这个文件,你会看到和SSH登录终端完全不一样的PATH。这个文件能帮你快速定位一大堆"脚本手动跑没事、cron跑就失败"的问题。等排查完记得删掉这条任务,不然每小时覆盖一次日志文件,久了也麻烦。
2.3 从"日志/status"时间戳看出的冷门坑:时区与系统时钟漂移
看到这里你应该已经形成了一个习惯:先看日志有没有变化,再看输出文件有没有内容。但还有一类更隐蔽的问题,会让你连"日志时间戳变了"这个证据都产生误判——系统时钟漂移和时区错位。
服务器跑了半年一年,又没有配置NTP时间同步的话,系统时间可能偏差几分钟甚至十几分钟。你期望凌晨2点跑任务
