1、背景
前幾天新同學入職,一不小心將跳板機上的 crontab 清空了,導致凌晨一大批任務異常,同事問了運維同學也沒有備份,這一百多個任務要是恢復起來可不是件容易的事兒。還好我去年某天開始做了定時備份,每分鐘一次 backup 到本地磁盤,最後很容易的將 crontab 給恢復了。
這件事情過後我也在想,一臺跳板機整個部門都共用一個賬號, Linux 水平和安全意識又參差不齊,其實很難避免以後還會誤操作,比如一下子將 home 目錄全乾掉。所以我想 backup 最好不要保存在本地,於是想一條命令將其備份到 hadoop 集群上去。
2、問題
當時覺得這個問題很簡單,於是隨手寫了一條類似這樣的命令:
<code>*/1 * * * * /bin/cat > /root/a.log 2>&1/<code>
本地測試了沒問題,但是 crontab 怎麼都不成功,也看不到錯誤日誌,a.log 一直是空的。
這個我就比較好奇了,按理說 a.log 應該是能拿到所有的標準輸出和標準錯誤的,究竟什麼原因導致 crontab 既不執行又不報錯呢?
3、分析
debug 終極大法還是得看日誌,本 case 最讓人疑惑的在於沒有日誌,如果能找到日誌所有的迷霧應該都能煙消雲散。
於是,我嘗試看看 /var/log 下有沒有 crontab 的執行日誌,看了下服務器居然沒開啟 cron.log,由於非管理員沒權限修改任何配置或設置,於是我在本地 WSL 裡用 Ubuntu 把問題復現了下。
3.1 開啟 cron.log
<code>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/<code>
雖然能看到 crontab 執行日誌了,但全都是一些沒意義的日誌或 info 提示:
<code>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) .../<code>
仔細觀察日誌發現貌似在提示我們 MTA 沒裝,crontab 輸出被丟棄了。同時查看 sudo tail -f /var/mail/ 發現爆出大量 warning: unable to look up public/pickup: No such file or directory! 的警告。
3.2 安裝 postfix
由於 crontab 通知機制是將錯誤會以郵件形式發給所屬登錄賬號或者系統管理員,如果沒有安裝郵件管理服務,那麼這部分信息會被系統丟棄。那咱們安裝 postfix 即可:
<code>sudo apt-get install postfix sudo service postfix start/<code>
再次查看日誌發現了報錯日誌:
<code> 1 From [email protected] Sat Mar 31 21:33:38 2018 2 Return-Path: 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 /bin/ls > /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: 14 X-Cron-Env: 15 X-Cron-Env: 16 X-Cron-Env: 17 Message-Id: <20180331133337.CCE42300000000E229> 18 Date: Sat, 31 Mar 2018 21:25:02 +0800 (DST) 19 20 /bin/sh: 1: Syntax error: "(" unexpected/<code>
3.3 如何修復
看到郵件裡的錯誤提示咱們立馬就能明白 crontab 之所以無法執行,是因為 crontab 環境變量默認加載的是 sh,而非 bash,不支持進程代換這種語法,咱們有兩種辦法避免:
3.3.1 crontab 開頭指定 shell 類型
完整的 crontab 格式如下:
<code>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/<code>
也就是說,咱們可以在 crontab 文件的開頭指定 shell 類型這樣就不會有問題了。
3.3.2 封裝成腳本
其實不建議在 crontab 裡執行復雜邏輯,最好封裝成腳本,這樣好控制,比如:
<code>*/1 * * * * bash a.sh >> /root/a.log 2>&1/<code>
3.4 重定向無法獲取錯誤的原因
雖然咱們根據錯誤日誌知道怎樣修改讓命令正常執行,但是我們並未回答文章開頭的疑問:究竟為何 2>&1 無法重定向拿到所有的標準輸出和標準錯誤?有點違反常理了。這個還和 shell 解釋器類型無關,比如下面這條命令,在 bash 下也是隻能拿到標準輸出,無法拿到標準錯誤:
<code>ls debuglog/a.log 2>&1/<code>
這個問題的深層次原因得追溯到 shell 的一個概念:子進程
其實上圖中的命令這樣改也行:
<code>ls > debuglog/b.log 2>&1) >> debuglog/a.log 2>&1/<code>
因為 debuglog/a.log 2>&1 只能拿到當前進程的標準輸出與標準錯誤。
另外需要注意的是通過()或管道fork出來的子進程,繼承了父進程的所有環境變量,和平時bash xxx.sh或者./xxx.sh起的不同的, 而$是一起繼承的,但$BASHPID繼承後重新賦值了,這和新開個bash的方式是不同的。
除了上面的寫法,如果要深究茴字還有幾種寫法,那麼還有如下兩種寫法:
<code>bash a.sh > debuglog/a.log 2>&1 bash -c "ls debuglog/a.log 2>&1/<code>
至此,從文章開頭的問題,咱們從如何讓日誌輸出以及代碼如何改寫,到最後的 root cause 都分析了一遍,希望能對大家有所啟發和參考。
結尾:
小編近幾年在學習Python!對於想學習Python的朋友們,我想說:很多人學了一個星期就放棄了,為什麼呢?其實沒有好的學習資料給你去學習,你們是很難堅持的,這是小編收集的Python入門學習資料。關注,轉發,後臺(我主頁上方)如下圖操作,即可免費靈取!希望對你們有幫助!