crontab 定时任务在 ThinkPHP 内使用时的问题

摘要

我们项目框架是 ThinkPHP5.0.24 ,系统环境为 Linux(Red Hat 4.8.5-11)
因为一些原因,每次到月初,项目应用总会报错:

error_log(/******/runtime/log/201903/20_sql.log): failed to open stream: Permission denied

记录一下,解决的过程和方法。


心路历程

round 1

根据报错信息可以知道,有可能是文件夹的权限问题。
解决方法是: 通过命令行或者图形化工具授予该日志文件夹 777 的权限

如果问题就这么解决了,就不会有这篇文章了。

round 2

这个月处理完之后,下个月同样会出现这个问题。

这就引起重视了呀。不然,难道每个月都要调闹钟、手动授权新增的文件夹写入的权限吗?

这不行。

欸!新增文件夹

为什么新增的文件夹会权限不足呢?

查看各级日志文件夹信息:

> ll runtime/log/201903/
total 588
-rw-r--r-- 1 root root 370701 Mar 20 23:24 20.log
-rw-r--r-- 1 root root 217029 Mar 20 21:52 20_sql.log
-rw-r--r-- 1 root root 1192016 Mar 21 15:03 21_cli.log 

此时文件夹内只能生成所属 root 的文件。

> ll runtime/log/
total 4
drwxr-xr-x 2 root root 4096 Mar 20 17:55 201903 // 此时报错,error 日志无权限写入
// 改成 drwxrwxrwx 2 root root 4096 Mar 20 17:55 201903 可解决,但是下月问题重现
// 删除此文件夹,主动请求接口生成:drwxr-xr-x 2 www www 4096 Mar 20 17:55 201903 可顺利写入

经过删除文件夹「runtime/log/201903/」,跑接口测试重新生成文件夹发现,此时生成的文件夹所属组和所属用户是 www
那么说明,出问题的这个文件夹(所属 root)不是跑框架的接口记录日志时生成的。

那么这个 root 所属的文件夹是谁生成的呢?
观察成功生成的日志文件可发现有以 _cli.log 结尾的文件。这不是定时任务的日志文件吗?只有它能正常运行?

原因是:以 root 权限设置的 crontab 里每分钟执行的计划任务,在每个月初第一时间执行并在框架下创建了 _cli.log 类型的日志。所以该月的文件夹所属就为 root ,而用户 www 在此文件夹内默认没有写入的权限,从而导致的报错。

round 3

既然知道了是 crontab 的定时任务的问题,那就重点盘它吧。
在进一步了解 crontab 的知识1后得知,在 root 下可以设定指定用户(比如我的 www 用户)的时程表。原文如下:

-u user 是指设定指定 user 的时程表,这个前提是你必须要有其权限(比如说是 root)才能够指定他人的时程表。如果不使用 -u user 的话,就是表示设定自己的时程表。

通过以下操作:

> crontab -e -u www

把原来每分钟执行框架脚本的定时任务全部搬到了这个只针对 www 用户的时程表里。这样第一时间生成的文件夹「runtime/log/201903/」的所属就是 www 了。

搬到以 www 用户运行的时程表后,发现:

  1. 生成的日志文件夹「runtime/log/201903/」的所属用户(组)确实变为了 www ,也没有发生错误日志生成权限不足的问题(也就是上文提及的问题成功解决了,证明这个方向是可取的);
  2. 没有生成 _cli.log 等定时任务运行产生的日志文件,也就是说新的定时任务没有执行成功。

round 4

排查为何定时任务没有执行成功。

先排查系统日志

> tail -f /var/log/cron
Mar 21 10:18:01 localhost CROND[22349]: (www) CMD (「时程表中的 command」)
... (www) MAIL (mailed 58 bytes of output but got status 0x004b#012)

注意到上面的 MAIL 的信息,查了一下:

这个其实是 postfix 的配置问题,本次的问题不在这,是计划任务在执行完之后会通过 postfix 发邮件给执行完的用户邮箱,现在发不出去导致的,修改下配置就行了。

修改配置:

> vim /etc/postfix/main.cf
inet_interfaces = all
inet_protocols = all

确保这两个都是all 就可以了,然后重启 postfix。

重启后确实没有了那条关于 MAIL 的信息,但是会有如下提醒:

You have new mail in /var/spool/mail/root

通过命令查看:

> tail -f /var/spool/mail/root 
X-Cron-Env: <SHELL=/bin/bash>
X-Cron-Env: <HOME=/usr/share/httpd>
X-Cron-Env: <PATH=/usr/bin:/bin>
X-Cron-Env: <LOGNAME=www>
X-Cron-Env: <USER=www>
Message-Id: <***@***.localdomain>
Date: Thu, 21 Mar 2019 10:17:01 +0800 (CST)

/bin/bash: ***.log: Permission denied

好了,Permission denied,也就是说时程表里的任务后面的输出日志权限不足咯。

因为时程表改成了指定用户的了,所以权限不足也正常。

找到原因了,那么解决方案有两个:

  1. 找到输出日志的默认文件夹,授权给 www 用户写入的权限;
  2. 删除输出日志。

基于种种原因想偷懒,比如时程表里跑的是框架内的 php 文件,会在框架内的日志文件夹生成 _cli.log_error_cli.log 的日志,所以就选择了直接删掉了时程表中的输出日志部分。

删除了输出日志,但是如果报错,依旧会发送 mail/var/spool/mail/root ,此时可以在命令后面加上 >/dev/null 2>&1 ,就不会发送 mail 了。

至此,定时任务恢复正常。
也就是 KO!


其它问题

1. 日志文件生成的所有者为 root

通过命令:

> crontab -e

默认设定当前用户的时程表。

如果以 root 用户登陆(果然要尽量避免啊摔),设定的时程表就属于 root 的了。

2. 关于 TP5 生成的错误日志是否报错的问题

测试如下:

try {
	Log::write('test', 'error');
	//Log::error('test:');
} catch (\Exception $e) {
	echo '抛出异常啦!';
}

同样是无写入 error 文件的权限,Log::write 会抛出异常,中断程序;而 Log::error 不会抛出异常,也不生成文件。

总结

虽然解决了困惑已久的问题很有成就感,但是归根结底是因为使用了 root 登陆管理服务器导致的问题。
流下了没有技术的泪水啊。希望同道中人能避免踩坑吧。


  1. Linux crontab命令 ↩︎

你可能感兴趣的:(总结归纳)