跳至正文

cron任务不执行?排查了三个小时终于找到原因

配图

配图

配图

上周博客的自动化发文脚本突然不跑了。每天早上10点定时发布技术文章,那个活儿全靠cron。结果去看的时候,文章列表空了一天。

我第一反应是cron服务挂了,systemctl status cron一看还在跑。又怀疑是脚本权限问题,chmod +x再跑一遍,手动执行完全正常。

开始排查

先看cron日志。grep CRON /var/log/syslog | tail -20,输出是这样:

May 24 10:00:01 ai CRON[12345]: (ubuntu) CMD (/usr/bin/python3 /home/ubuntu/wp_to_wechat_server.py 456)
May 24 10:00:01 ai CRON[12346]: (ubuntu) CMD (cd /tmp && /bin/bash deploy.sh)
May 24 10:00:02 ai CRON[12347]: (ubuntu) MAIL (mailed 1 byte of output)

注意到最后一行有个MAIL (mailed 1 byte of output)。这是说cron执行了命令,但有输出被邮件了。一般来说,cron把stdout/stderr当成邮件发送,说明脚本有输出——可能报错。

我赶紧去/var/mail/ubuntu看邮件。果然有一大堆失败通知,最早的已经是昨天早上10点发的。

真正的错误

邮件内容是这样的:

Traceback (most recent call last):
  File "/home/ubuntu/wp_to_wechat_server.py", line 8, in <module>
    import requests
ModuleNotFoundError: No module named 'requests'

???我明明装了requests的。直接在终端跑python3 -c "import requests",没问题。再在cron里跑一遍试试——写一个测试脚本:

#!/bin/bash
echo "PATH=$PATH"
echo "PYTHONPATH=$PYTHONPATH"
which python3
python3 -c "import requests; print(requests.__version__)"

cron跑完,我去看输出。结果PATH只有/usr/bin:/bin,根本没包含我装requests的那个Python环境路径。

问题根源

原来我之前用pip3 install --user requests装的时候,包装到了~/.local/lib/python3.10/site-packages/。终端里PATH是对的,PYTHONPATH也带了这个路径。但cron执行的时候,环境变量是最小化的,~/.bashrc~/.profile这些根本不加载。

所以cron里的python3找的是系统Python,不是我装了requests的那个。两种修法:

  • 在cron文件开头加PATH=/usr/local/bin:/usr/bin:/bin
  • 或者脚本里用绝对路径/usr/bin/python3 -m pip install requests确保装到系统Python

我选了更稳妥的办法——在cron任务里用SHELL=/bin/bash + BASH_ENV=/home/ubuntu/.bashrc,强制加载我的环境。改完之后cron长这样:

SHELL=/bin/bash
BASH_ENV=/home/ubuntu/.bashrc

0 10 * * * /usr/bin/python3 /home/ubuntu/wp_to_wechat_server.py 456

等10点再看,这次文章正常同步了。

额外踩的坑

排查过程中还发现一个坑:我的wp_to_wechat_server.py里用了mysql.connector,但在cron环境里连不上数据库——因为~/.my.cnf文件里的socket路径用的是相对路径,cron的工作目录是/home/ubuntu,但脚本里cd /tmp了,socket找不到。

修法:把~/.my.cnf里的socket改成绝对路径/var/run/mysqld/mysqld.sock,或者脚本里用TCP连接127.0.0.1:3306而不是socket。

经验教训

这次花了三个小时,主要时间浪费在”以为是脚本问题”上,其实根因是环境变量。几点经验:

  1. 排查cron问题先看邮件——/var/mail/你的用户名,cron会把报错发邮件,往往一眼就能看到
  2. cron的PATH很有限——脚本里所有命令都用绝对路径,或者export PATH在脚本开头
  3. 环境变量别忘了——~/.bashrc不会自动加载,需要BASH_ENV或者脚本里source
  4. 测试用/bin/bash -c——跟cron一样的环境测试:/bin/bash -c "echo $PATH; your_command"

这条经验写得有点水,但是昨天凌晨3点还在查日志是真实发生的事。改完之后今天早上10点文章正常发布了,这才是真正验证过的。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注