l24.Linux系统定时任务Cron(d)服务应用实践(二)

957 阅读11分钟

“ 本文正在参加「金石计划 . 瓜分6万现金大奖」 ”

13.5 生产环境下的定时Cron书写要领

13.5.1 要领1:为定时任务规则加上必要的注释

书写定时任务规则时应尽可能地加上注释(最好是英文注释),这是个很好的运维习惯和规范。例如,什么人,在什么时间,因为谁(需求方),做了什么定时任务计划。如果这些都标记清楚了,那么其他的运维人员(同事)可以很容易理解任务的信息,从而提升团队工作的效率。若不写注释,则会给以后的维护和任务交接带来麻烦。带注释的定时任务示例代码如下:

[root@centos7 /server/scripts]# crontab -l
#backup size dir by neteagle at 20201017    #清晰的注释,是专业、资深运维的习惯。
00 00 * * * /bin/sh /server/scripts/bak.sh >/dev/null 2>&1

13.5.2 要领2:所有的定时任务尽量都以脚本的形式执行

如果定时任务计划直接使用Linux命令执行,不但看着不规范,而且也很容易出错,特别是带系统时间变量(如果含有“%",则必须要转义,即“%“)的任务命令,如果能以文件的形式书写,则可以减少很多潜在的错误,并提升效率、规范,这是一个好习惯。在定时任务中执行命令也会有一些现实,如时间变量问题,多个重定向命令混用问题等,示例代码如下:

[root@centos7 /server/scripts]# crontab -l
#backup size dir by neteagle at 20201017
00 00 * * * /bin/sh /server/scripts/bak.sh >/dev/null 2>&1
[root@centos7 /server/scripts]# cat bak.sh 
cd /var/www/&&\
/bin/tar zcf /data/bak_$(date +%F).tar.gz ./html

13.5.3 要领3:在执行的Shell脚本前加上/bin/sh

要确保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.sh

13.5.4 要领4:定时任务中在命令或脚本的结尾加上>/dev/null 2>&1

定时任务(一般是脚本任务)规则的结尾最好加上“>/dev/null 2>&1”,如果需要打印日志,则可以追加到指定的日志文件里(此时不要与/dev/null同时存在),总之,定时任务计划脚本的结尾尽量不要留空。因为在默认情况下,定时任务每一次执行完毕之后,都会想对应的用户发邮件,如果不加将输出(正确或错误)定向到空的内容(>/dev/null 2>&1),则可能会由于系统未开启邮件服务而导致邮件临时目录文件数猛增的隐患发生,大量小文件占用磁盘Inode节点数量(每个文件占一个Inode),以致磁盘Inode写满而无法再写入正常数据(故障提示:no space left on device.)的故障发生。

在CentOS 5下,小文件过多的目录一般为sendmail临时邮件文件目录/var/spool/clientmqueue,而CentOS 6下则是postfix临时队列目录/var/spool/postfix/maildrop。

在">/dev/null 2>&1"中,“>”表示重定向,“/dev/null”为特殊的字符设备文件,表示黑洞设备或空设备。“2>&1”表示让标准错误和标准输出一样,本命令的意思是将前面脚本的正常和错误输出都重定向到/dev/null,也就是什么都不输出。

下面三种让标准错误和标准输出都重定向到空的写法是等价的:

>/dev/null 2>&1 等价于 1>/devnull 2>/dev/null  等价于&>/dev/null

13.5.5 要领5:在指定用户下执行相关定时任务

需要root权限执行的任务可以登录到root用户下然后进行设置,如果执行任务不需要root权限,则可以登录到普通用户下(也可以直接在root下通过命令crontab -u neteagle -e直接设置)进行设置。这里需要特别注意不同用户的环境变量问题,如果调用了系统环境变量,例如/etc/profile等文件下的变量(如生产场景中Java程序的定时任务计划),那么最好是在程序脚本中将用到的环境变量重新exprot下(下文有案例)。

13.5.6 要领6:生产任务计划程序中不要随意打印输出信息

在开发定时任务程序或脚本时,调试好脚本程序之后,应尽量将Debug及命令输出的内容信息拼比掉,如果确实需要输出日志,则可定向到指定的日志文件里,以避免随意输出不做重定向,从而导致系统垃圾的产生。

例如,有的读者打包时喜欢用tar命令的zcvf这几个参数。其中的v参数就是用于查看打包信息的。在做定时任务计划时,命令里就不要再带这个参数了。

13.5.7 要领7:定时任务执行的脚本要存放到规范路径下

定时任务执行的脚本要存放到规范路径下,其实,系统中所有脚本存放都要有规范,这里推荐统一使用/server/scripts作为脚本的存放路径。

13.5.8 要领8:配置定时任务要规范操作过程,减少出错

配置定时任务时,规范的操作过程具体如下。

1)尽量先在命令行测试成功,然后将成功的命令复制到脚本里,新手要在各个细小环节减少出错的机会。

2)然后执行测试脚本,测试成功后,将执行脚本的命令完整复制到定时任务配置里,尽量做到少动手输入命令。

3)先在测试环境下进行测试,然后在正式环境下规范部署。

4)要有检验任务是否正确执行的手段,例如,检查/var/log/cron日志文件,如果任务执行计划频率较低,也要想法确保任务的可执行性,此处可见下文调试定时任务的技巧。

13.5.9 要领9:定时任务脚本中程序命令及路径尽量使用全路径

定时任务脚本中,程序命令及路径应尽量使用全路径,这是个防止定时任务执行错误的好习惯,否则可能会导致命令行操作命令及执行脚本是正常的,但是放到定时任务中却无法正确执行的问题。当然对于命令,除了写全路径之外,还可以在脚本中重新定义PATH环境变量,示例代码如下:

[root@centos7 /server/scripts]# crontab -l
#backup size dir by neteagle at 20201017
00 00 * * * /bin/sh /server/scripts/bak.sh >/dev/null 2>&1  #/bin/sh命令使用全路径。
[root@centos7 /server/scripts]# cat bak.sh  #任务脚本内容如下。
cd /var/www/&&\
/bin/tar zcf /data/bak_$(date +%F).tar.gz ./html    #/bin/tar备份命令使用全路径。

重新定义PATH环境变量的方法如下:

[root@centos7 /server/scripts]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@centos7 /server/scripts]# cat bak.sh  #脚本内容如下。
export PATH='/sbin:/bin:/usr/sbin:/usr/bin'     #重新定义环境变量,包含脚本中所有执行命令所在的路径。
cd /var/www/&&\
tar zcf /data/bak_$(date +%F).tar.gz ./html #这里可以取消全路径了。

13.5.10 要领10:时间变量%号要使用反斜线转义

“%”号在Cron任务配置中被认为是newline,需要用“\”来转义。使用crontab编辑任务时,如果有类似于"date +%F"的时间变量,则必须做如下转义"date +%F",但是如果是在脚本中编写,那么“%”就不需要转义了。这也是笔者推荐定时任务使用脚本文件来执行的原因之一,定时任务使用命令执行时,执行命令中带有时间变量的写法(不建议使用此法)示例如下:

#tar comment by neteagle at 20201017
*/1 * * * * tar zcf /data/bak_$(date +%F).tar.gz /var/www/html &>/dev/null

13.5.11 要领11:若脚本中调用了系统环境变量,则要重新定义

crontab执行Shell等脚本时只能识别很少的系统环境变量,用户在/etc/profile等文件中定义的普通变量一般是无法被定时任务服务识别的,如果在编写的脚本中需要使用这些环境变量,那么最好是使用export重新声明下该变量,这样脚本才能正常运行。

例如,在调试Java程序任务计划的时候,需要多注意环境变量的问题,务必要将环境变量的定义加到定时任务计划脚本里。Task.sh的作用是执行Java相关的程序,这里需要在脚本中重新定义相关的环境变量,示例代码如下:

[root@centos7 ~]# cat /scripts/resin/shell/Task.sh
#!/bin/bash
export JAVA HOME=/application/jdk1.6
export PATH=$JAVA_HOME/bin:$PATH
export SH_HOME=/application/resin/webapps/ROOT/
export LIB=$SH_HOME/WEB-INF/lib
...

定时任务的配置结果如下:

#JAVA Shell by neteagle 20201017
00 9,14 * * * nohup /scripts/resin/shell/Task.sh & >/app/log.log 2>&1

13.6 调试Cron定时任务的技巧总结

13.6.1 增加执行任务的频率以调试任务

在调试时,将任务执行频率调快一点,例如,每天执行的任务,可以改为每分钟、每5分钟执行一次,或者以当前时间为准,推迟5分钟以后,看能否执行,而且是不是按照你想要的去执行。如果正常每问题了,那么再改成需要的任务的执行时间。

需要强调一点的是,有些计划任务是不允许频繁执行的。例如,定时向数据库插入数据,这样的任务就要在测试机上先测试好,然后再部署到正式线上,这样正式工作时出问题的机率就少了。

专业、规范的公司开发和运维人员配置服务器的操作流程至少是:办公室测试环境→IDC机房测试环境→IDC机房正式环境,在不同的环境上测试成功,再到更高级的环境是保障操作人员在正式环境下不出错的关键。

13.6.2 调整系统时间调试任务(不能用于生产环境)

用正确的执行任务的时间测试完成以后,即可修改系统的当前时间,改成任务执行时间的前几分钟来进行测试(或者重启定时任务服务)。例如,定时任务于6:00执行,那么我们可以将系统时间改成8:55分,然后观察是不是正确执行了,当前时间比任务时间建议提前3分钟以上,否则可能就不会执行了,如果是生产环境服务器,则尽量不要修改时间测试,在测试环境下才可以使用这个手段。例如,若是要在周三的2:00执行,则可以将系统时间调整为周三凌晨1:55分查看执行结果。

13.6.3 通过脚本日志输出调试定时任务

要输出调试定时任务,可在脚本中加入日志输出,然后将输出打印到指定的日志中,并观察日志内容结果,以查看是否执行或正确执行。或者像下面一样,将脚本结果定向到一个log文件里。这里使用重定向符号“>”即可,不需要使用“>>”符号,这样日志就不会一直变大,如/app/log/log。示例代码如下:

#study task by neteagle at 20201017
00 9,14 * * 6,0 /bin/sh /server/scripts/neteagle.sh >app/log/log 2>&1

提示: 对于不好查看执行结果的定时任务计划可以这样调试。

也可以在脚本命令中通过参数打印信息输出,然后将输出重定向到指定的文件,示例代码如下:

[root@centos7 ~]# cat tar.sh
cd /
tar zcvf /tmp/etc_$(date +%F).tar.gz ./etc >/tmp/tmp.log 2>&1   #加v参数,结尾重定向到文件。

13.6.4 通过Crond定时任务服务日志调试定时任务

查看定时任务服务日志,可以发现执行的以及不能执行的任务问题所在,示例日志信息如下:

[root@centos7 ~]# tail -f /var/log/cron
Oct 17 16:15:01 centos7 CROND[2867]: (root) CMD (/usr/sbin/ntpdate ntp3.aliyun.com &>/dev/null)
Oct 17 16:15:01 centos7 CROND[2868]: (root) CMD (/usr/sbin/ntpdate ntp1.aliyun.com &>/dev/null)
Oct 17 16:20:01 centos7 CROND[2880]: (root) CMD (/usr/sbin/ntpdate ntp1.aliyun.com &>/dev/null)
Oct 17 16:20:01 centos7 CROND[2881]: (root) CMD (/usr/lib64/sa/sa1 1 1)
Oct 17 16:20:01 centos7 CROND[2882]: (root) CMD (/usr/sbin/ntpdate ntp3.aliyun.com &>/dev/null)
Oct 17 16:25:01 centos7 CROND[2895]: (root) CMD (/usr/sbin/ntpdate ntp3.aliyun.com &>/dev/null)
Oct 17 16:25:01 centos7 CROND[2897]: (root) CMD (/usr/sbin/ntpdate ntp1.aliyun.com &>/dev/null)
Oct 17 16:30:02 centos7 CROND[2908]: (root) CMD (/usr/sbin/ntpdate ntp1.aliyun.com &>/dev/null)
Oct 17 16:30:02 centos7 CROND[2910]: (root) CMD (/usr/sbin/ntpdate ntp3.aliyun.com &>/dev/null)
Oct 17 16:30:02 centos7 CROND[2909]: (root) CMD (/usr/lib64/sa/sa1 1 1)

13.7 crontab生产案例故障分析及解决

13.7.1 No space left on device常见企业故障案例

13.7.1.1 故障描述及说明

工作中可能会出现这样的问题:在保存设置定时任务的规则时,系统提示“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/clientmqueue;rm -f *命令则会自动跳出来,无法实现删除。最后的解决方法是使用命令cd /var/spool/clientmqueue && ls |xargs rm -f进行清理。

在清理时,如果文件的数量特别多,那么执行ls |xargs rm -f命令也会长时间无反应,不要着急,这是命令正在处理中的正常表现。当然我们也可以使用更快的删除方法,如直接使用cd /var/spool && rm -rf clientmqueue删除上级目录,然后执行如下命令:

mkdir clientmqueue && chmod 770 clientmqueue && chown smmsp.smmsp -R /var/spool/clientmqueue

修改回/var/spool/clientmqueue目录在系统中原有的默认权限:

[root@centos5 ~]# ll -d /var/spool/clientmqueque/
drwxrwx--- 2 smmsp smmsp 4096 12-12 13:46 /var/spool/clientmqueque/
[root@centos7 ~]# ll -d /var/spool/postfix/maildrop/
drwx-wx---. 2 postfix postdrop 6 Oct 15 21:08 /var/spool/postfix/maildrop/

13.7.1.2 故障原因分析

当系统中Crond定时任务执行的程序包包含输出内容时,输出内容会以邮件的形式发回给执行任务的用户(默认是root),而sendmail、postfix等mail服务没有启动时,这些输出内容就会在邮件队列临时目录中产生大量很小的文件,导致消耗大量的Inode和Block数量(在ext文件系统中,默认情况下格式化block的数量回远大于Inode的数量),一旦Inode数量耗尽,就会导致系统无法写入文件而报出上述错误“No space left on device”。

提示:上述为CentOS 5系统中的故障案例,同样适合于CentOS 6、CentOS 7,只是后两者小文件多的路径改为/var/spool/postfix/maildrop/的临时邮件队列目录了。

13.7.1.3 预防方法

1)尽量在Cron任务中的命令或脚本中的命令结尾加上“>/dev/null 2>&1”,或者在写定时执行脚本时,将输出定向到指定文件中(适合于所有情况)。

2)当然也可以开启邮件服务,不过最好不做,因为邮件服务会带来额外的安全问题。

3)添加定时清理任务,比如,将find /var/spool/clientmqueue/ -type f -mtime +30 |xargs rm -f放入定时任务,每周处理一次(适合于CentOS 5),如果是CentOS 6或CentOS 7,则处理的路径为/var/spool/postfix/maildrop/。

13.7.2 Crond export变量生产案例

曾有网友在生产环境下碰到了问题,笔者为其解答后,他针对该案例进行了总结,一起来看看。

编写一个重启resin的脚本,由于业务原因,需要在某一个时间定时重启一次resin服务器,于是就在Cron里面配置了如下内容:

20 17 * * * 1-5 /usr/local/bin/resin_restart.sh

其中resin_restart.sh的内容具体如下:

#!/bin/sh
/usr/local/bin/xxresin_stop.sh
/usr/local/bin/xxresin_start.sh

现在问题来了,服务器虽然定时重启了,但是系统却报出了如下错误:

d13-1.png

为什么已经在profile里配置了环境变量,却还是找不到呢?

后来,在QQ交流群385168604中找到了热心的老男孩教育在线老师,并请教,得到的回答是:这里因为export变量问题而导致的。

具体原因为Crond执行Shell时只能识别为数不多的系统环境变量,普通环境变量一般是无法识别的,如果在编写的脚本中需要使用变量,那么最好是使用export重新声明下该变量,以确保脚本能够正确执行。以后要将这一点做为一个开发的基本规范。

然后再在resin重启脚本里重新定义了一下环境变量,脚本如下:

#!/bin/sh

下面就是环境变量的具体定义:

d13-2.png

经过测试,修改后的定时任务可以顺利重启,问题解决完毕。

13.8 有关Cron定时任务的企业面试题

1)在每周6的凌晨3:15执行一次/home/shell/collect.pl,并将标准输出和标准错误输出到/dev/null设备,请写入crontab中的语句。

2)crontab在11月份内,每天的早上6点到中午12点之间,每隔2小时执行一次/usr/bin/httpd.sh,如何实现?

3)crontab文件由六个域组成,每个域之间用空格进行分割,其排列正确的为下面哪一项(B)。

A、MIN HOUR DAY MONTH YEAR COMMAND

B、MIN HOUR DAY MONTH DAYOFWEEK COMMAND

C、COMMAND HOUR DAY MONTH DAYOFWEEK

D、COMMAND YEAR MONTH DAY HOUR MIN

13.9 定时任务知识逻辑图(学习方法)

学习方法、学习能力的提高,远大于学到的知识,下面就来分享下笔者课程学员总结的定时任务知识逻辑图,如图13-3所示。

通过图13-3,我们可以清晰了解定时任务的编写及调试前后的相关知识,这个方法值得所有读者学习。

t13-3.png

图13-3 定时任务知识逻辑图

13.10 本章重点

1)定时任务的时间及特殊字符书写语法。

2)编写定时任务的众多要领,都是规范。

3)连个企业故障案例总结。

4)调试Cron定时任务技巧总结。