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命令 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章