


上周博客的自动化发文脚本突然不跑了。每天早上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。
经验教训
这次花了三个小时,主要时间浪费在”以为是脚本问题”上,其实根因是环境变量。几点经验:
- 排查cron问题先看邮件——
/var/mail/你的用户名,cron会把报错发邮件,往往一眼就能看到 - cron的PATH很有限——脚本里所有命令都用绝对路径,或者
export PATH在脚本开头 - 环境变量别忘了——
~/.bashrc不会自动加载,需要BASH_ENV或者脚本里source - 测试用
/bin/bash -c——跟cron一样的环境测试:/bin/bash -c "echo $PATH; your_command"
这条经验写得有点水,但是昨天凌晨3点还在查日志是真实发生的事。改完之后今天早上10点文章正常发布了,这才是真正验证过的。