Cron
是Linux系统中以后台进程模式周期性执行命令或指定程序任务的服务软件。默认情况下,安装完Linux系统之后,Cron
系统软件便会启动,服务对应的进程名字为Crond
,Crond
服务会定期(默认每分钟一次)检查系统中是否有需要执行的定时任务工作计划。如果有,便会根据其预先设定的定时任务规则自动执行该定时任务工作。
注意:
Cron
定时任务执行的最快频率是每分钟一次,因此如果需要以秒为单位执行的计划任务,可以写shell脚本,然后作为守护程序执行。以下是一个每秒钟执行一次任务的脚本:[root@localhost ~]# cat cron.sh while true do echo "I am people." sleep 1 done
Cron的主要任务就是文件数据备份。Cron工具可以很好地帮我们完成需要重复的、周期性地、自动备份等运维工作。
在Linux系统中,Cron
是定时任务的软件名,Crond
是服务进程名,而Crontab
命令是用来设置定时任务规则的配置命令。
Cron
定时任务可以人工划分为用户定时任务计划和系统定时任务计划两类。
Crond
服务在工作时会以分钟为单位查看/var/spool/cron
路径下以系统用户命名的定时任务文件,确定是否有需要执行的任务计划。如果有,就会把定时任务调度到内存中执行。这部分文件就被称为用户定时任务文件,使用crontab
命令编辑的文件最终会以当前用户名作为文件名存在于/var/spool/cron
路径下。
如果是系统管理员或某个普通用户定期要做的任务工作,例如每隔5分钟与互联网上的时间服务器进行一次时间同步,每天晚上0点备份网站站点数据以及数据库数据,就可以使用crontab
命令配置在/var/spool/cron
路径下。
Crond
服务在工作时除了查看/var/spool/cron
路径下的定时任务文件外,还会查看/etc/cron.d
目录以及/etc/anacrontab
下的文件内容,里面通常是每天、每周或每月需要执行的任务,如果有需要执行的任务就会执行,系统定时任务的路径通常如下:
[root@localhost ~]# ls -l /etc|grep cron
-rw-------. 1 root root 541 Aug 24 2016 anacrontab
drwxr-xr-x. 2 root root 4096 Mar 8 09:55 cron.d #<==系统定时任务的目录。
drwxr-xr-x. 2 root root 4096 Mar 8 09:55 cron.daily #<==按天轮询配置的目录。
drwxr-xr-x. 2 root root 4096 Mar 8 09:54 cron.hourly #<==按小时轮询配置的目录。
drwxr-xr-x. 2 root root 4096 Mar 8 09:55 cron.monthly #<==按月轮询配置的目录。
drwxr-xr-x. 2 root root 4096 Sep 27 2011 cron.weekly #<==按周轮询配置的目录。
特别注意:系统路径下的定时任务配置格式与前文讲解的用户定时任务的配置格式是不同的。
Crond
服务除了执行用户定时任务计划(/var/spool/cron
目录)以外,还会周期性地自动执行与操作系统相关的定时任务工作,例如轮询系统日志、备份系统数据、清理系统缓存等,这些任务无需我们人为干预。示例代码如下:
[test@localhost ~]$ ls -l /var/log/messages* #<==Linux系统的日志被自动轮询,以时间结尾。
-rw-------. 1 root root 470941 9月 7 09:06 /var/log/messages
-rw-------. 1 root root 77868 8月 24 19:35 /var/log/messages-20230825
-rw-------. 1 root root 153778 8月 31 17:27 /var/log/messages-20230901
[test@localhost ~]$ ls -l /var/log/secure*
-rw-------. 1 root root 6969 9月 7 09:07 /var/log/secure
-rw-------. 1 root root 17640 8月 25 08:25 /var/log/secure-20230825
-rw-------. 1 root root 6494 9月 1 08:52 /var/log/secure-20230901
类似于上述的日志轮询工作就是由系统自身来完成的,不需要系统管理员来设置。
systemctl start crond.service # 开启
systemctl stop crond.service # 停止
systemctl status crond.service # 查看状态
systemctl disable crond.service # 关闭自启动
systemctl enable crond.service # 开启自启动
在Linux运维工作中,通常使用crontab
命令编辑定时任务。
参数 | 含义 | 示例 |
---|---|---|
-l (小写L) |
查看定时任务内容。提示:l为list缩写 | crontab -l |
-e |
编辑定时任务内容,提示:e 为edit缩写 | crontab -e |
-i |
删除定时任务内容,删除前会提示确认 | crontab -ri |
-r |
删除定时任务内容 | crontab -r |
-u |
指定使用的用户执行任务 | crontab -u test -l |
【特别强调】
使用
crontab
命令编辑的文件实际上就是在操作“/var/spool/cron/当前用户名
”这样的文件。-i
、-r
参数在生产中很少使用。
crontab
命令参数的等效命令:crontab -l <==> cat /var/spool/cron/root
crontab -e <==> vim /var/spool/cron/root
crontab -u root -l <==> cat /var/spool/cron/root
文件 | 说明 |
---|---|
/etc/cron.deny | 该文件中所列的用户不允许使用crontab命令,不常用 |
/etc/cron.allow | 该文件中所列的用户允许使用crontab命令,优先于/etc/cron.deny |
/var/spool/cron/ | 所有用户的Cron配置文件默认都存放在此目录中,文件名以用户名命名。以root用户为例,执行crontab -e就等同于vim /var/spool/cron/root,而执行crontab -l就等同于执行cat/var/spool/cron/root |
默认情况下,待用户建立定时任务规则之后,该规则所记录的对应配置文件将会存在于/var/spool/cron
中,其crontab配置文件对应的文件名与登录的用户名应一致,例如,root用户的定时任务配置文件为/var/spool/cron/root
。
利用crontab命令编写定时任务的书写格式很简单,规则一般可分为6个段(每个段之间均通过空格来分隔),前5段为时间设定段,第6段为所要执行的命令或脚本任务段。
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * (command to be executed)
共六列:
第一列:分 minute (0 - 59)
第二列:时 hour (0 - 23)
第三列:日 day of month (1 - 31)
第四列:月 month (1 - 12) OR jan,feb,mar,apr ...
第五列:周 day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
第六列:要执行的任务命令或程序
段 | 含义 | 取值范围 |
---|---|---|
1 | 分钟 | 00~59 |
2 | 小时 | 00~23 |
3 | 天 | 01~31 |
4 | 月 | 01~12 |
5 | 星期 | 0~7(0和7都表示星期日) |
在使用crontab命令编辑的用户定时任务的语法中,除了时间段以外,还会包含很多特殊字符应用,用户定时任务编写语法中的特殊字符及含义:
特殊符号 | 含义 |
---|---|
* | * 号,表示任意时间都,实际就是“每”的意思。例如, 00 23 * * * cmd 表示每月每周每日的23:00都执行cmd任务。需要注意的是,各个时间位上的“ * ”表示每,如果位上是“* ”就是该位上时间的取值范围,例如:小时上的“* ”等价于00~23。经验技巧:定时任务规则如果间隔单位到小时,那么口述时就最多提到每天。分位上的“ * ”就等价于00~59,表示每分 |
- | 减号,表示分隔符,表示一个时间范围、区间段,如17~19点,每天的17、18、19的00分执行任务。00 17-19 * * * cmd 。就是17、18、19点整点分别执行的意思 |
, | 逗号,表示分隔时段的意思。30 17,18,19 * * * /bin/sh /scripts/test.sh 表示每天17、18和19点的半点时刻执行/scripts/test.sh脚本。也可以与“-”结合使用 |
/n | n代表数字,即“每隔n单位时间”,例如:每10分钟执行一次任务可以写成“*/10 * * * * cmd ”,其中,“/10,*”的范围是0~59,因此,也可以写成0-59/10。 |
crontab
命令编辑的定时任务依赖于crond
服务,下面来看一下它的运行情况,示例代码如下:
[root@localhost ~]# systemctl status crond.service #<==查看Cron定时任务服务启动状态。
● crond.service - Command Scheduler
#下面的enabled说明开机Crond为自启动状态,running为当前启动状态。
Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled; vendor preset: enabled)
Active: active (running)since Thu 2020-10-08 01:56:12 CST; 41min ago
Main PID: 13189 (crond)
CGroup: /system.slice/crond.service
└─13189 /usr/sbin/crond -n
......
特别强调:
1)在编辑定时任务文件之后,无须重启定时任务,
Crond
会自动检查更改的变化。2)当定时任务不能正确执行时,请查看定时任务日志文件(
/var/log/cron
)以获取故障信息。
# 命令实例1:
*/1 * * * * /bin/sh /scripts/data.sh # 表示每隔一分钟执行一次/bin/sh/scripts/data.sh程序。
# 命令实例2:
30 3,12 * * * /bin/sh /scripts/test.sh # 每天凌晨3:30和中午12:30执行一次/scripts/test.sh脚本任务。
# 命令实例3:
30 */6 * * * /bin/sh /scripts/test.sh # 每隔6个小时的 半点时刻 执行一次/scripts/test.sh脚本任务。
# 命令实例4:
30 8-18/2 * * * /bin/sh /scripts/test.sh # 早晨8点到下午18点之间,每隔2小时的 半点时刻 执行一次脚本任务。
# 命令实例5:
30 21 * * * /application/apache/bin/apachectl graceful # 每晚的21:30重启Apache
# 命令实例6:
45 4 1,10,22 * * /application/apache/bin/apachectl graceful # 表示每月1、10、22日的凌晨4:45分重启一次Apache
# 命令实例7:
10 1 * * 6,0 /application/apache/bin/apachectl graceful # 表示每周六、周日的凌晨1:10分重启一次Apache
# 命令实例8:
0,30 18-23 * * * /application/apache/bin/apachectl graceful # 表示在每天18:00至23:00之间每隔30分钟重启一次Apache
# 命令实例9:
00 */1 * * * /application/apache/bin/apachectl graceful # 表示每隔一小时整点重启一次Apache
# 命令实例10:
00 11 * 4 1-3 /application/apache/bin/apachectl graceful # 表示4月的每周一到周三的上午11点整重启一次Apache
1)先在命令行调试成功。
2)再将命令复制到定时任务配置里。
3)然后保存,并使用tail -f
测试观察结果。
4)如果遇到问题,则可根据输出以及定时任务日志/var/log/cron
文件内容排错。
[root@localhost ~]# crontab -l|tail -3
#time sync by test at 20180429
*/5 * * * * /usr/sbin/ntpdate ntp1.aliyun.com &>/dev/null #<==主时间同步配置。
*/5 * * * * /usr/sbin/ntpdate ntp3.aliyun.com &>/dev/null #<==辅助时间同步配置。
CentOS7 默认没有安装
ntpdate
命令,需要事先执行yum
安装,命令是:yum install ntpdate -y
。服务器的时间同步很重要,因此,在工作中最好是配置两个不同地址的同步任务。
书写定时任务规则时应尽可能地加上注释(最好是英文注释),这是个很好的运维习惯和规范。
例如,什么人,在什么时间,因为谁(需求方),做了什么定时任务计划。 带注释的定时任务示例代码如下:
[root@localhost scripts]# crontab -l
#backup site dir by test at 202305 #<==清晰的注释,是专业、资深运维的习惯。
00 00 * * * /bin/sh /server/scripts/bak.sh >/dev/null 2>&1
如果定时任务计划直接使用Linux命令执行,不但看着不规范,而且也很容易出错,特别是带系统时间变量(如果含有“%”,则必须要转义,即“%”)的任务命令,如果能以文件的形式书写,则可以减少很多潜在的错误,并提升效率、规范,这是一个好习惯。
在定时任务中执行命令也会有一些限制,如时间变量问题,多个重定向命令混用问题等,示例代码如下:
[root@localhost scripts]# crontab -l
#backup site dir by test at 201805
00 00 * * * /bin/sh /server/scripts/bak.sh >/dev/null 2>&1 #<==写成脚本文件执行最佳。
[root@localhost scripts]# cat bak.sh #<==脚本文件内容如下。
cd /var/www/&&\
/bin/tar zcf /data/bak_$(date +%F).tar.gz ./html
要确保Cron
对应的执行者有访问Shell
脚本所在目录的权限,并且可执行该Shell
脚本(可用chmod
和chown
修改脚本权限和所有者)。当然,最佳方法是在要执行的任务脚本前加上/bin/sh
,然后执行,否则就有可能会因为忘了为脚本设定执行权限,而无法完成当次任务执行计划。本条要领是一个经验型的好习惯。执行其他语言的脚本也要加上对应语言的解释器,以下是相应的执行方法示例。
规范的Shell脚本定时任务执行方法:
/bin/sh/server/scripts/bak.sh
规范的Perl脚本定时任务执行方法:
/usr/bin/perl/server/scripts/bak.sh
规范的Python脚本定时任务执行方法:
/usr/bin/python/server/scripts/bak.py
定时任务(一般是脚本任务)规则的结尾最好加上“>/dev/null 2>&1”
,如果需要打印日志,则可以追加到指定的日志文件里(此时不要与/dev/null
同时存在),总之,定时任务计划脚本的结尾尽量不要留空。因为在默认情况下,定时任务每一次执行完毕之后,都会向对应的用户发邮件,如果不加将输出(正确或错误)定向到空的内容(>/dev/null 2>&1
),则可能会由于系统未开启邮件服务而导致邮件临时目录文件数猛增的隐患发生,大量小文件占用磁盘Inode节点数量(每个文件占一个Inode),以致磁盘Inode写满而无法再写入正常数据(故障提示:no space left on device.)的故障发生。
在“>/dev/null 2>&1”中,“>”表示重定向,“/dev/null”为特殊的字符设备文件,表示黑洞设备或空设备。“2>&1”表示让标准错误和标准输出一样,本命令的意思是将前面脚本的正常和错误输出都重定向到/dev/null,也就是什么都不输出。
下面三种让标准错误和标准输出都重定向到空的写法是等价的:
>/dev/null 2>&1
>1>/dev/null 2>/dev/null
>&>dev/null
需要root权限执行的任务可以登录到root用户下然后进行设置,如果执行任务不需要root权限,则可以登录到普通用户下(也可以直接在root下通过命令crontab-u test -e直接设置)进行设置。这里需要特别注意不同用户的环境变量问题,如果是调用了系统环境变量,例如/etc/profile等文件下的变量(如生产场景中Java程序的定时任务计划),那么最好是在程序脚本中将用到的环境变量重新export下。
在开发定时任务程序或脚本时,调试好脚本程序之后,应尽量将Debug及命令输出的内容信息屏蔽掉,如果确实需要输出日志,则可定向到指定的日志文件里,以避免随意输出不做重定向,从而导致系统垃圾的产生。
例如,打包时喜欢用tar命令的zcvf这几个参数。其中的v参数就是用于查看打包信息的。在做定时任务计划时,命令里就不要再带这个参数了。
定时任务执行的脚本要存放到规范路径下,其实,系统中所有的脚本存放都要有规范,这里推荐统一使用/server/scripts作为脚本的存放路径。
配置定时任务时,规范的操作过程具体如下。
1)尽量先在命令行测试成功,然后将成功的命令复制到脚本里,新手要在各个细小环节减少出错的机会。
2)然后执行测试脚本,测试成功后,将执行脚本的命令完整复制到定时任务配置里,尽量做到少手动输入命令。
3)先在测试环境下进行测试,然后在正式环境下规范部署。
4)要有检验任务是否正确执行的手段,例如,检查/var/log/cron日志文件,如果任务执行计划频率较低,也要想法确保任务的可执行性,此处可见下文调试定时任务的技巧。
定时任务脚本中,程序命令及路径应尽量使用全路径,这是个防止定时任务执行错误的好习惯,否则可能会导致命令行操作命令及执行脚本是正常的,但是放到定时任务中却无法正确执行的问题。当然,对于命令,除了写全路径之外,还可以在脚本中重新定义PATH环境变量,示例代码如下:
[root@localhost scripts]# crontab -l
#backup site dir by test at 202305
00 00 * * * /bin/sh /server/scripts/bak.sh >/dev/null 2>&1 #<==/bin/sh命令使用全路径。
[root@localhost scripts]# cat bak.sh #<==任务脚本内容如下。
cd /var/www/&&\
/bin/tar zcf /data/bak_$(date +%F).tar.gz ./html #<==/bin/tar备份命令使用全路径。
重新定义PATH环境变量的方法如下:
[root@localhost scripts]# echo $PATH #<==打印输出,根据输出选择路径进行重新定义。
/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
[root@localhost scripts]# cat bak.sh #<==脚本内容如下。
export PATH='/sbin:/bin:/usr/sbin:/usr/bin' #<=重新定义环境变量,包含脚本中所有执行命令所在的路径。
cd /var/www/&&\
tar zcf /data/bak_$(date +%F).tar.gz ./html #<==这里可以取消全路径了。
“%”号在Cron任务配置中被认为是newline,需要要用“\”来转义。使用crontab编辑任务时,如果有类似于“date+%F”的时间变量,则必须做如下转义“date+%F”,但是如果是在脚本中编写,那么“%”就不需要转义了。这也是笔者推荐定时任务使用脚本文件来执行的原因之一,定时任务使用命令执行时,执行命令中带有时间变量的写法(不建议使用此法)示例如下:
#tar comment by test at 202305
*/1 * * * * tar zcf /data/bak_$(date +\%F).tar.gz /var/www/html &>/dev/null
crontab执行Shell等脚本时只能识别很少的系统环境变量,用户在/etc/profile等文件中定义的普通变量一般是无法被定时任务服务识别的,如果在编写的脚本中需要使用这些环境变量,那么最好是使用export重新声明下该变量,这样脚本才能正常执行。
例如,在调试Java程序任务计划的时候,需要多注意环境变量的问题,务必要将环境变量的定义加到定时任务计划脚本里。Task.sh的作用是执行Java相关的程序,这里需要在脚本中重新定义相关的环境变量,示例代码如下:
[root@localhost ~]# cat /scripts/resin/shell/Task.sh
#!/bin/bash
export JAVA_HOME=/application/jdk1.6 #<==如下三行是安装Java相关环境需要的特殊变量。
export PATH=$JAVA_HOME/bin:$PATH
export SH_HOME=/application/resin/webapps/ROOT/
export LIB=$SH_HOME/WEB-INF/lib
...省略部分...
定时任务的配置结果如下:
#JAVA Shell by test 202307
00 9,14 * * * nohup /scripts/resin/shell/Task.sh & >/app/log.log 2>&1
在调试时,将任务执行频率调快一点,例如,每天执行的任务,可以改为每分钟、每5分钟执行一次,或者以当前时间为准,推迟5分钟以后,看能否执行,而且是不是按照你想要的去执行。如果正常没问题了,那么再改成需要的任务的执行时间。
需要强调一点的是,有些计划任务是不允许频繁执行的。例如,定时向数据库里插入数据,这样的任务就要在测试机上先测试好,然后再部署到正式线上,这样正式工作时出问题的机率就少了。
专业、规范的公司开发和运维人员配置服务器的操作流程至少是:办公室测试环境→IDC机房测试环境→IDC机房正式环境,在不同的环境上测试成功,再到更高级的环境是保障操作人员在正式环境下不出错的关键。
用正确的执行任务的时间测试完成以后,即可修改系统的当前时间,改成任务执行时间的前几分钟来进行测试(或者重启定时任务服务)。例如,定时任务于9:00执行,那么我们可以将系统时间改成8:55分,然后观察是不是正确执行了,当前时间比任务时间建议提前3分钟以上,否则可能就不会执行。
如果是生产环境服务器,则尽量不要修改时间测试,在测试环境下才可以使用这个手段。例如,若是要在周三的2:00执行,则可以将系统时间调整为周三凌晨1:55分查看执行结果。
要输出调试定时任务,可在脚本中加入日志输出,然后将输出打印到指定的日志中,并观察日志内容结果,以查看是否执行或正确执行。或者像下面一样,将脚本结果定向到一个log文件里。这里使用重定向符号“>”即可,不需要使用“>>”符号,这样日志就不会一直变大,如/app/log.log。示例代码如下:
#study task by test at 20121213
00 9,14 * * 6,0 /bin/sh /server/scripts/test.sh >/app/log.log 2>&1
提示:对于不好查看执行结果的定时任务计划可以这样调试。
也可以在脚本命令中通过参数打印信息输出,然后将输出重定向到指定的文件,示例代码如下:
[root@localhost scripts]## cat tar.sh
cd /
tar zcvf /tmp/etc_$(date +%F).tar.gz ./etc >/tmp/tmp.log 2>&1 #<==加v参数,结尾重定向到文件。
查看定时任务服务的日志,可以发现执行的以及不能执行的任务问题所在,示例日志信息如下:
[root@localhost scripts]# tail -f /var/log/cron
Nov 10 11:40:01 localhost crond[2605]: (root)CMD (echo "==" >> /tmp/test.log >/dev/null 2>&1)
Nov 10 11:40:01 localhost crond[2608]: (root)CMD (/usr/sbin/ntpdate pool.ntp.org >/dev/null 2>&1)
Nov 10 11:41:01 localhost crond[2615]: (root)CMD (/bin/sh /server/scripts/echo.sh >/dev/null 2>&1 #print date)
Nov 10 11:42:01 localhost crond[2625]: (root)CMD (/bin/sh /server/scripts/echo.sh >/dev/null 2>&1 #print date)
Nov 10 11:42:01 localhost crond[2627]: (root)CMD (echo "==" >> /tmp/test.log >/dev/null 2>&1)
Nov 10 11:42:01 localhost crond[2628]: (root)CMD (echo + >> /tmp/test.log)
tar.gz ./html
工作中可能会出现这样的问题:
在保存设置定时任务的规则时,系统提示“No space left on device”,此时用df -h
命令检查磁盘,发现还有剩余空间,再用df -i
命令检查则显示Inode
已被100%占用了,导致系统无法在/var
目录下创建文件。因为定时任务配置在/var/spool/cron
下,在ext3、ext4文件系统中,每个文件至少要占用一个Inode。
最后,经过检查发现在/var/spool/clientmqueue/
下有大量的小文件,执行ls/var/spool/clientmqueue命令查看,很长时间都没能显示出结果,执行cd/var/spool/clientm-queue;rm-f*
命令则会自动跳出来,无法实现删除。最后的解决方法是使用命令cd/var/spool/clientmqueue&&ls|xargs rm-f
进行清理。
在清理时,如果文件的数量特别多,那么执行ls|xargs rm-f命令也会长时间无反应,不要着急,这是命令正在处理中的正常表现。当然,我们也可以使用更快的删除方法,如直接使用cd/var/spool&&rm-fr clientmqueue删除上级目录,然后执行如下命令:
mkdir clientmqueue && chmod 770 clientmqueue && chown smmsp.smmsp -R /var/spool/clientmqueue
修改回/var/spool/clientmqueue目录在系统中原有的默认权限:
[root@localhost ~]# ls -ld /var/spool/clientmqueue/
drwxrwx--- 2 smmsp smmsp 4096 12-12 13:46 /var/spool/clientmqueue/
当系统中Crond定时任务执行的程序包含输出内容时,输出内容会以邮件的形式发回给执行任务的用户(默认是root),而sendmail、postfix等mail服务没有启动时,这些输出内容就会在邮件队列临时目录中产生大量很小的文件,导致消耗大量的Inode和block数量(在ext文件系统中,默认情况下格式化block的数量会远大于Inode的数量),一旦Inode数量耗尽,就会导致系统无法写入文件而报出上述错误“No space left on device”。
提示:上述为CentOS5系统中的故障案例,同样适合于CentOS6、CentOS7,只是后两者小文件多的路径改为postfix的临时邮件队列目录了。
1)尽量在Cron任务中的命令或脚本中的命令的结尾加上“>/dev/null 2>&1”,或者在写定时执行脚本时,将输出定向到指定文件中(适合于所有情况)。
2)当然也可以开启邮件服务,不过最好不做,因为邮件服务会带来额外的安全问题。
3)添加定时清理任务,比如,将find/var/spool/clientmqueue/-type f-mtime+30|xargs rm-f
放入定时任务,每周处理一次(适合于CentOS5),如果是CentOS6或CentOS7,则处理的路径为/var/spool/postfix/maildrop/
。