cron是Linux上的定时任务服务,可提供类似Windows上计划任务的功能,嗯,似乎cron的功能会更强大一些。为了能尽可能压榨我那可怜小云的算力,我需要定时运行一些高负载的计算任务,比如XX爬虫(参考《二手房热力图》一文),那么自然就趟了cron的坑。
坑•查看执行结果
首先按照网上的各种cron教程,安装cron之后,创建了一个测试计划任务。
*/1 * * * * echo HELLO
然后用命令cron
启动任务,嗯?启动之后似乎没啥反应啊,“哦,对,网上大佬说过cron的结果得通过邮箱查看”,赶紧输入命令mail
查看。嗯?然而什么都没显示。不应该啊,难道没运行起来,可是进程当中确实有cron这个进程呢,用crontab -l
也能看见刚刚配置的任务。“嗯,不要慌,反正慌也没啥用”,既然网上大佬说过是通过邮件通知的,那就手动在log目录(泛指Linux中产生日志,邮件的文件目录)中找找呗,最终在/var/spool/mail/
中找到日志了。可看见每隔一段时间就有一个HELLO出现。
ps:可通过命令tail -f /var/spool/mail/mail
持续跟踪cron任务执行结果的输出。
坑•环境变量
既然HELLO都可以执行了,那就来一个复杂一点的任务吧。我将需要执行的脚本统一放在了一个Shell脚本当中,脚本大概长下面这个样子。
#!/bin/bash
python 我要图片.py
python 我要应用.py
python 我要自行车.py
然后将这个脚本添加进cron任务。按预想,到时间后会依次执行“我要xx”脚本。
*/1 * * * * iwant.sh
然而和上一个坑遇到的情况一毛一样,啥都没发生,唉,就说气不气人吧。
行嘛,既然出了问题那就定位搞呗。之前直接执行echo一切正常,换成Shell脚本就出现问题,猜测是脚本执行失败,那么需要获取具体的错误原因再做进一步分析,通过重定向将错误信息保存至文本当中。发现脚本使用的是系统默认的Python2.x版本,而脚本中需要的库是安装在Python3当中的。嗯,懂了,那我就显式指定Python路径呗。将脚本修改成这个样子。
#!/bin/bash
/usr/local/bin/python 我要图片.py
/usr/local/bin/python 我要应用.py
/usr/local/bin/python 我要不知道要什么.py
再次运行,Python倒是用对了,但是脚本依然报错,提示我某些环境变量找不到(为了便于配置,我将部分参数以环境变量的形式配置在docker-compose.yml当中,程序运行时会直接读取这部分参数),我的第一反应是脚本写错了,可是单独运行又是一切正常。这又是遇到了什么鬼!
查看cron系统配置(/etc/crontab),简单看了下也没啥毛病呀。
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit t is file
# and files in /etc/cron.d. These files also have usrname fields,
# that none of the other crontabs do.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/
bin:/usr/bin
这下懵逼了,最怕的就是这种问题,连怎么调试都不知道。直到在网上找到这样的神文《The Cron Environment and Cron Job Failures》,看这标题怕不就是专门为我而写的吧。
What is the cron environment?
Since the cron daemon is a system program that is started by one of the system startup scripts, its environment will not be the same as the environment inherited from the shell when a job is run from the command line. Cron inherits its environment from its parent, which is the init process. But during cron initialization, cron can modify or extend the environment before it runs cron jobs. The default environment for a cron job consists of /etc/environment, and the default shell environment variables such as $PATH, $HOME and $PWD. Information in login files (for example, /etc/profile, ~/.profile and ~/.kshrc, so it is unlikely $PATH contains login shell directories. If the cron job needs any information in these shell initialization files, these scripts must be sourced at the top of the cron job script.
System limits, also known as ulimits are essential to the cron environment. The ulimits for a cron job are inherited from the root account, and will likely not be the same as the ulimits for a regular user account.
Note: The at command is often used to test cron jobs. Because the at command is executed from the command line, unlike cron, it will automatically inherit the current environment of the shell.
简单来说就是cron是由init进程创建的,因此它的环境变量和init保持一致,而我们在Shell中执行脚本时用到的环境变量相较于系统启动时已有很大的变化,因此cron无法主动获取到新增(或修改)的变量,而在文中介绍的解决办法则是(简单直接)
Any additional information needed by a cron job can also be added directly at the top of the cron job script.
既然蓝色大佬都这么说了,那就搞呗。可是问题又来了
我需要的环境变量是由docker传递进来的,并非写在profile或bashrc当中,因此不能通过source或自行rc的方式让环境变量生效。
为了便于管理以及安全考虑,所以才将参数修改为docker的环境变量,难道为了cron,我还得硬编码进脚本吗?似乎和初衷相违呢。
这可咋整?回头再看看文档,终于发现关键的一句话The default environment for a cron job consists of /etc/environment。如果我能把这个文件同步成最新的变量不就OK了么。有了这个思路,那解决起来就是一句话的事儿了。
printenv > /etc/enironment
是的,没错就这么一句话就搞定cron变量的问题。
坑•格式
这个坑和前面的比起来只能算小泥坑,对,就是下面这种愉快的小坑。
对于首次使用cron的新手来说,cron确实不够友好,我第一次在菜鸟教程上学习cron的时候,差点把我整自闭,大家可以感受下。
我第一反应是这一堆的 * * *
是啥啊?它怎么就表示每天,每分钟,每小时了啊?这不是在逗我吗?后来才了解到不同位置的 *
有不同的含义(站位的重要性),可每一位具体表示什么呢?网上又出现有歧义的解读,有些说是按秒分时排序,有些说是按分时排序,感觉上在不同的平台,不同的版本的cron配置是不统一的(没确认过)。
其实完全不用去网上找cron的配置说明,在/etc/crontab中就有详细的描述。
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,ap
...
# | | | | .---- day of week (0 - 6) (Sunday=0 or
7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
17 * * * * root cd / && run-parts --report /
tc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron ||
( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron ||
( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron ||
( cd / && run-parts --report /etc/cron.monthly )
简单清晰,一看就懂。
结束
至此,我的cron趟坑之旅也就快结束了,目前在cron的帮助下,我将周一至周六每天凌晨的时段都安排上了。因为最近搞了一个爬虫,数据量比较大,而我的小云负载又不是那么强,只好拆分了一下,安排在每天凌晨了呗。而这个新爬虫到底干了啥后面再单独写吧。
)