周三早上,客户说他们每天凌晨两点的数据库备份没执行。我查了一下备份目录,昨天的文件在,前天的也在,就今天早上没有。
第一反应是 cron 服务挂了。systemctl status crond 一看,active (running),没事。那是不是任务被删了?crontab -l 一看,任务还在,时间也对,凌晨 02:00。
我手动跑了一遍备份脚本,正常,备份文件生成得又快又好。这就怪了:手动行,定时不行。
我又去看 /var/log/cron,凌晨 02:00:01 确实有记录,任务执行了:
Feb 26 02:00:01 db01 CROND[18421]: (root) CMD (/backup/backup.sh)
但执行结果呢?脚本的输出去哪了?cron 默认会把脚本的 stdout/stderr 发邮件给 root,但服务器没配邮件,所以输出直接丢了。我改了 crontab,把输出重定向到文件:
0 2 * /backup/backup.sh >> /var/log/backup.log 2>&1
然后手动触发一次,看日志:
/backup/backup.sh: line 6: mysqldump: command not found
破案了。mysqldump 在 /usr/local/mysql/bin 下面,但 cron 执行脚本时的 PATH 只有 /usr/bin:/bin,根本找不到 mysqldump。我在命令行手动跑的时候,PATH 是登录 shell 给的,里面有 /usr/local/mysql/bin,所以手动没问题,cron 里就报 command not found。
这就是 cron 最经典的坑:它不加载 /etc/profile,不加载 ~/.bashrc,环境变量几乎等于零。你手动跑得好好的脚本,一进 cron 就各种 command not found。
修法也简单,脚本开头加一行:
export PATH=/usr/local/mysql/bin:/usr/local/bin:/usr/bin:/bin
或者脚本里全部写绝对路径。我两个都做了——脚本里 mysqldump 改成全路径,开头再 export 一遍 PATH,双保险。
改完第二天早上,备份正常生成了。
后来我又想起一件事。同样是这个脚本,前阵子有同事说它偶尔会失败,报"can't open file",我当时没细查。这次修 PATH 的时候顺手看了下,发现脚本里用了相对路径:
mysqldump ... > backup.sql
而 cron 执行时的工作目录是执行者的 home,不是 /backup,所以 backup.sql 写到了 /root 下面,跟脚本同目录的预期对不上。改成绝对路径 /backup/backup.sql,这个问题也一起解决了。
cron 这个工具,看着简单,坑是真不少。环境变量、工作目录、还有 % 号要转义(cron 里 % 会被当成换行符),都是踩过才知道的。
哦对,后来我把所有定时任务都检查了一遍,凡是用到第三方命令的脚本,全加了 PATH 和绝对路径。这种问题不出则已,一出就是"数据没备份"这种级别的。
评论