1. 程式人生 > >【轉載】迷之 crontab 異常:不執行、不報錯、無日誌

【轉載】迷之 crontab 異常:不執行、不報錯、無日誌

問題說明 :

谷歌雲服務 gce debian例項crontab不執行,經檢視發現crontab時區與系統時區不一致,系統時區為utc+8,crontab為utc。

修改後重啟cron服務沒有立即生效,第二天檢視crontab服務時區已正常,但仍無法執行任務,查閱到以下文章,問題解決。

######

1、背景

前幾天新同學入職,一不小心將跳板機上的 crontab 清空了,導致凌晨一大批任務異常,同事問了運維同學也沒有備份,這一百多個任務要是恢復起來可不是件容易的事兒。還好我去年某天開始做了定時備份,每分鐘一次 backup 到本地磁碟,最後很容易的將 crontab 給恢復了。

這件事情過後我也在想,一臺跳板機整個部門都共用一個賬號, Linux 水平和安全意識又參差不齊,其實很難避免以後還會誤操作,比如一下子將 home 目錄全乾掉。所以我想 backup 最好不要儲存在本地,於是想一條命令將其備份到 hadoop 叢集上去。

2、問題

當時覺得這個問題很簡單,於是隨手寫了一條類似這樣的命令:

*/1 * * * *  /bin/cat <(seq 10) >> /root/a.log 2>&1

本地測試了沒問題,但是 crontab 怎麼都不成功,也看不到錯誤日誌,a.log 一直是空的。

這個我就比較好奇了,按理說 a.log 應該是能拿到所有的標準輸出和標準錯誤的,究竟什麼原因導致 crontab 既不執行又不報錯呢?

3、分析

debug 終極大法還是得看日誌,本 case 最讓人疑惑的在於沒有日誌,如果能找到日誌所有的迷霧應該都能煙消雲散。

於是,我嘗試看看 /var/log 下有沒有 crontab 的執行日誌,看了下伺服器居然沒開啟 cron.log,由於非管理員沒許可權修改任何配置或設定,於是我在本地 WSL 裡用 Ubuntu 把問題復現了下。

3.1 開啟 cron.log

sudo vim /etc/rsyslog.d/50-default.conf
cron.*  /var/log/cron.log #將cron前面的註釋符去掉
#重啟rsyslog
#sudo /etc/init.d/rsyslog restart
sudo service rsyslog restart
sudo service cron restart

雖然能看到 crontab 執行日誌了,但全都是一些沒意義的日誌或 info 提示:

Mar 31 20:58:20 Surface-Pro5 crontab[223]: (root) BEGIN EDIT (root)
Mar 31 20:58:53 Surface-Pro5 crontab[223]: (root) REPLACE (root)
Mar 31 20:58:53 Surface-Pro5 crontab[223]: (root) END EDIT (root)
...
Mar 31 21:13:01 Surface-Pro5 CRON[451]: (CRON) info (No MTA installed, discarding output)
Mar 31 21:14:01 Surface-Pro5 CRON[471]: (CRON) info (No MTA installed, discarding output)
...

仔細觀察日誌發現貌似在提示我們 MTA 沒裝,crontab 輸出被丟棄了。同時檢視 sudo tail -f /var/mail/<user> 發現爆出大量 warning: unable to look up public/pickup: No such file or directory! 的警告。

3.2 安裝 postfix

由於 crontab 通知機制是將錯誤會以郵件形式發給所屬登入賬號或者系統管理員,如果沒有安裝郵件管理服務,那麼這部分資訊會被系統丟棄。那咱們安裝 postfix 即可:

sudo apt-get install postfix
sudo service postfix start

再次檢視日誌發現了報錯日誌:

  1 From [email protected]  Sat Mar 31 21:33:38 2018
  2 Return-Path: <[email protected]>
  3 X-Original-To: root
  4 Delivered-To: [email protected]
  5 Received: by Surface-Pro5.localdomain (Postfix, from userid 0)
  6     id CCE42300000000E229; Sat, 31 Mar 2018 21:25:02 +0800 (DST)
  7 From: [email protected] (Cron Daemon)
  8 To: [email protected]
  9 Subject: Cron <[email protected]> /bin/ls <(seq 10) >> /root/a.log 2>&1
 10 MIME-Version: 1.0
 11 Content-Type: text/plain; charset=UTF-8
 12 Content-Transfer-Encoding: 8bit
 13 X-Cron-Env: <SHELL=/bin/sh>
 14 X-Cron-Env: <HOME=/root>
 15 X-Cron-Env: <PATH=/usr/bin:/bin>
 16 X-Cron-Env: <LOGNAME=root>
 17 Message-Id: <[email protected]>
 18 Date: Sat, 31 Mar 2018 21:25:02 +0800 (DST)
 19 
 20 /bin/sh: 1: Syntax error: "(" unexpected

3.3 如何修復

看到郵件裡的錯誤提示咱們立馬就能明白 crontab 之所以無法執行,是因為 crontab 環境變數預設載入的是 sh,而非 bash,不支援程序代換這種語法,咱們有兩種辦法避免:

3.3.1 crontab 開頭指定 shell 型別

完整的 crontab 格式如下:

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
HOME=/
# .---------------- 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

也就是說,咱們可以在 crontab 檔案的開頭指定 shell 型別這樣就不會有問題了。

3.3.2 封裝成指令碼

其實不建議在 crontab 裡執行復雜邏輯,最好封裝成指令碼,這樣好控制,比如:

*/1 * * * *  bash a.sh >> /root/a.log 2>&1

3.4 重定向無法獲取錯誤的原因

雖然咱們根據錯誤日誌知道怎樣修改讓命令正常執行,但是我們並未回答文章開頭的疑問:究竟為何 2>&1 無法重定向拿到所有的標準輸出和標準錯誤?有點違反常理了。這個還和 shell 直譯器型別無關,比如下面這條命令,在 bash 下也是隻能拿到標準輸出,無法拿到標準錯誤:

ls <(ooxx) > debuglog/a.log 2>&1

這個問題的深層次原因得追溯到 shell 的一個概念:子程序

其實上圖中的命令這樣改也行:

ls <(ooxx >> debuglog/b.log 2>&1) >> debuglog/a.log 2>&1

因為 <() 是在子程序進行的,> debuglog/a.log 2>&1 只能拿到當前程序的標準輸出與標準錯誤。

另外需要注意的是通過()或管道fork出來的子程序,繼承了父程序的所有環境變數,和平時bash xxx.sh或者./xxx.sh起的不同的, 而$$是一起繼承的,但$BASHPID繼承後重新賦值了,這和新開個bash的方式是不同的。

除了上面的寫法,如果要深究茴字還有幾種寫法,那麼還有如下兩種寫法:

bash a.sh > debuglog/a.log 2>&1
bash -c "ls <(ooxx)" > debuglog/a.log 2>&1

至此,從文章開頭的問題,咱們從如何讓日誌輸出以及程式碼如何改寫,到最後的 root cause 都分析了一遍,希望能對大家有所啟發和參考。

【全文完】

###