了解crontab

crontab很简单,基本格式是这样子的:
* * * * * command
5个*号分别对应了 分时日月周,最后是你想执行的任意命令
比如:
/1 * * * echo PWD

问题来了

最近做的一个事情是容器监控,需要定时采集一些系统或者业务指标,基本的架构是,物理机上运行Daemon进程,在每个容器内部使用crontab进行监控采集调度。一旦crontab和docker结合起来的时候,就问题多多了。

第一个问题:crond的启动问题

最早的Dockerfile是这样写的

RUN sudo echo "export TERM=xterm" >> /root/.bashrc
RUN sudo mkdir -p /usr/mycron
COPY crontabfile /usr/mycron/
RUN crontab /usr/mycron/crontabfile
ENTRYPOINT [ "/usr/bin/tail","-f","/etc/hosts" ]

写下上面的代码后,发现启动的容器想要执行的定时任务并不会执行,后来发现启动的容器中crond没有执行

### shell
#service crond status
crond is stopped

所以在Dockerfile中加上crond启动命令

#RUN service crond start

容器执行的结果是:

#service crond status
crond dead but pid file exists

分析原因,还是对docker了解不够,其实这个命令只是在build镜像时运行了一下,而容器启动时并不会执行任务启动crond服务的命令,所以有pid,但实际上进程并没有运行。
于是改造Dockerfile,将crond启动放在ENTRYPOINT时。

ENTRYPOINT [ "/usr/mycron/start.sh" ]

start.sh脚本内容

#!/bin/bash
service crond start
tail -f /etc/hosts

这中间可能是crontab写的有问题,需要看cron日志,却没有发现 /var/log/cron日志文件。查看/etc/syslog.conf配置,发现如下配置是生效的

cron.*                          /var/log/cron

于是查看/var/log/message,发现syslog服务没有启动

Aug 14 16:52:47 localhost.localdomain syslog-ng[4378]: Termination requested via signal, terminating;
Aug 14 16:52:47 localhost.localdomain syslog-ng[4378]: syslog-ng shutting down; version='3.1.2'

找到这个问题就好解决了,执行下面这个命令即可

service syslog restart

于是上面的两个日志变成了这样

#tailf /var/log/message
Jul 18 09:59:55 c72885913997 syslogd 1.4.1: restart.
Jul 18 09:59:55 c72885913997 kernel: klogd 1.4.1, log source = /proc/kmsg started.

#tail /var/log/cron
Jul 18 10:00:01 c72885913997 crond[8371]: (root) CMD (echo '$date' > /tmp/crontest.log)

至此,crontab基本在容器中run起来了,不久我们便发现第二个问题

第二个问题:crontab多个定时任务的问题

监控是基础服务,需要给所有docker 容器都增加监控能力,而现在监控能力依赖了crontab调度,如果产品也有依赖crontab的需求时,就出现问题了。
比如,业务在他自己的容器启动脚本中也增加了下面的代码

crontab /usr/yourapp/yourcronfile

这里再去看在执行的任务列表,发现就只有一个cron任务了:

#crontab -l
*/1 * * * * echo "`date`, yourapp cron is running" >> /tmp/crontest1.log

原来问题是,crontab就是根据配置文件,加载这个用户下所有cron任务,所以crontab命令执行多次时,就会被相互覆盖,了解到这个问题, 首先是这么解决的

#cat /usr/mycron/crontabfile >> /var/spool/cron/root
#cat /usr/yourapp/yourcronfile >> /var/spool/cron/root
#cat /var/spool/cron/root
*/1 * * * * echo "`date`" >> /tmp/crontest.log
*/1 * * * * echo "`date`, yourapp cron is running" >> /tmp/crontest1.log

同时注释掉Dockerfile中的下面这句代码

RUN sudo crontab /usr/mycron/crontabfile

因为默认情况下,执行下面命令时是从/var/spool/cron/root中加载cron任务的。

service crond start

目前已经可以解决多任务的问题了,但问题还是没有得到完美解决:
1、业务在引入监控能力时,需要先约定把cron任务放到/var/spool/cron/root中
2、业务的容器启动不确定是用的哪个用户,有可能是root也可能是admin等等,上述方案可以解决是root时的问题,如果是admin时,用户可能是crontab /home/admin/yourapp/yourcronfile的方式来启动。
总之是有太多的约定和风险,可能会随时相互影响,其实还是没有把crond用对,我们下面就着手解决这个问题

第三个问题:基础通用能力对业务任务有侵入性,导致cron行为不能确定

为了避免基础能力对业务能力的侵入性,man crontab看到关于crontab的如下介绍:

DESCRIPTION
       Crontab  is the program used to install, deinstall or list the tables used to drive the cron(8) daemon in ISC Cron.
       Each user can have their own crontab, and though these are files in /var/spool/ ,  they  are  not  intended  to  be
       edited directly. For SELinux in mls mode can be even more crontabs - for each range. For more see selinux(8).

       If  the cron.allow file exists, then you must be listed therein in order to be allowed to use this command.  If the
       cron.allow file does not exist but the cron.deny file does exist, then you must not be listed in the cron.deny file
       in  order  to  use this command.  If neither of these files exists, only the super user will be allowed to use this
       command.

每个用户可以自己的crontab,就是说,可以用多用户的方式来运行解决这个问题,于是改成如下实现。
首先创建一个脚本/usr/mymon/monitor.sh,用来管理监控能力的启动,这个脚本主要有以下内容:

sudo useradd -m -s /sbin/nologin mymon -G root -g root
cronfile='/usr/mymon/cron.conf'
sudo chown -R mymon:root /usr/mymon
monitorTask='*/1 * * * * echo '$date' > /tmp/crontest.log'
echo "$monitorTask" | sudo tee -a $cronfile
sudo crontab -u mymon $cronfile
sudo service crond restart

用户接入时调用一下这个脚本就可以了

#!/bin/bash
cat /usr/yourapp/yourcronfile >> /var/spool/cron/root
/usr/mymon/monitor.sh
service crond restart
tail -f /etc/hosts

看一下结果。

#sudo crontab -u mymon -l
*/1 * * * * echo  >> /tmp/crontest.log

#sudo crontab -l
*/1 * * * * echo "`date`, yourapp cron is running" >> /tmp/crontest1.log

### 系统 cronfile
#ll /var/spool/cron/
total 8
-rw------- 1 root root 38 Jul 18 13:44 mymon
-rw-r--r-- 1 root root 73 Jul 18 13:44 root

### 输出结果文件
#ll /tmp/
total 8
-rw-r--r-- 1 root  root 216 Jul 18 13:48 crontest1.log
-rw-r--r-- 1 mymon root   1 Jul 18 13:48 crontest.log

第四个问题:补充怎么解决系统命令>>的问题

可以看到上面的一系列操作中,有很多cat file >> file1,或者echo “…” >> file1的句式,通常情况下不会有问题,一旦跨用户操作文件时就会有问题了。
比如:

sudo echo "$monitorTask"  >> $cronfile

实际上这段话要拆成两部分,sudo echo “ monitorTask">> cronfile,也就是说sudo只对echo生效,怎么解决这个问题就是用tee来代替。

echo "$monitorTask" | sudo tee -a $cronfile

结束!

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐