超越 Cron:為什麼你應該使用 systemd Timers

幾十年來,「cron job」一直是類 Unix 系統中排程任務的金標準。它是一個如此基礎的運算原語,以至於這個術語已成為一種借代——無論實際執行的守護進程(daemon)是什麼,我們幾乎將任何排程動作都稱為「cron job」。然而,隨著系統管理技術的演進,傳統 cron 的局限性變得越來越明顯。

在現代 Linux 生態系統中,systemd timers 提供了一個強大且整合的替代方案,在解決 cron 缺點的同時,也引入了以往難以或無法實現的功能。如果你仍在使用 crontab,以下是為什麼現在是時候進行轉換的原因。

傳統 Cron 的局限性

雖然 cron 簡單且成熟,但它在專業環境中經常會帶來摩擦。常見的痛點包括:

  • 環境歧義性: cron 中的 $PATH 設定可能難以預測,這常導致腳本在 shell 中可以運行,但在由 daemon 執行時卻失敗。
  • 輸出「黑洞」: 預設情況下,stdoutstderr 經常會遺失,或者被發送到很少有人監控的本地郵件系統。正如一位社群成員所指出的,許多管理員不得不將所有內容透過管道(pipe)傳送到 logger,僅僅是為了確保在 syslog 中具備可見性。
  • 不透明的執行歷史: 要確定一個任務是否運行、為何失敗,或上次何時執行,需要翻找分散的日誌。
  • 晦澀的語法: 雖然功能強大,但 cron 的排程語法(例如 01,31 04,05 1-15 1,6 *)並不直觀,通常需要外部工具來解讀。

systemd Timers 如何運作

與 cron 將排程與指令結合在單一行的做法不同,systemd 將兩者解耦為兩個獨立的單元(unit):一個 Service unit (.service) 和一個 Timer unit (.timer)。

1. Service Unit

Service unit 定義了「執行什麼」。它是一個標準的 systemd service 檔案,指定了執行檔及其參數。這種分離允許你透過 systemctl start 手動觸發任務進行除錯,而無需等待 timer 觸發。

2. Timer Unit

Timer unit 定義了「何時執行」該 service。預設情況下,timer 會尋找與其同名的 service unit(例如,backup.timer 將觸發 backup.service)。

進階排程能力

systemd timers 最顯著的優點之一是其排程邏輯的靈活性。它們支援兩種主要的觸發類型:

日曆事件 (Calendar Events)

這些與 cron 的牆鐘排程(wall-clock schedules)類似(例如 OnCalendar=daily)。然而,systemd 提供了一種更具可讀性的語法,以及一個強大的驗證工具:systemd-analyze calendar。這讓你可以在部署之前測試時間表達式,並確切地看到下一次何時會到期。

單調定時器 (Monotonic Timers,相對時間)

這是 timer 真正超越 cron 的地方。你可以根據其他事件而非牆鐘來排程任務:

  • OnBootSec=: 在系統啟動後經過 X 時間執行。
  • OnUnitActiveSec=: 在 service 最後一次啟動後經過 X 時間執行。

這對於像清理 /tmp 這樣的任務非常理想,在這種情況下,在啟動後一小時執行,然後之後每小時執行一次,比在一個可能與系統高峰期重疊的固定時間執行更合理。

企業級功能

Systemd timers 包含幾項內建功能,可解決常見的分散式系統問題:

解決「驚群效應」(Thundering Herd)

當數千台機器被排程在午夜準時執行任務時,可能會導致中央伺服器崩潰。Systemd 使用 RandomizedOffsetSec= 來解決此問題,它將執行時間分散在均勻分佈中,確保更新或檢查點不會同時衝擊網路。

持久性與可靠性

如果機器在排程事件期間關機,傳統的 cron job 就會直接錯過。透過設定 Persistent=true,systemd 會將最後一次觸發時間儲存在磁碟上。如果 timer 被啟動且發現錯過了排程執行,它會立即觸發 service。

系統整合

  • WakeSystem=: Timers 可以被配置為從掛起(suspend)狀態喚醒系統以執行關鍵任務。
  • Journal Integration: 所有輸出都會自動被 journalctl 擷取,提供了一個集中化、可搜尋的執行歷史。
  • 可見性: systemctl list-timers 指令可以提供所有活動排程的鳥瞰圖,顯示每個任務最後一次運行以及下一次預定執行時間。

社群觀點與權衡

儘管有這些優點,轉向 systemd timers 並非沒有爭辯。一些使用者認為失去了 crontab -l 的簡便性,因為 timers 分散在 /etc/systemd/system//usr/lib/systemd/system/ 中的 unit 檔案中。

其他人則覺得 unit 檔案的語法「醜陋」或不如 crontab 的單行指令來得直接。正如一位使用者所指出的,向初學者解釋「獨立的 service 與 timer unit」的概念比解釋一行 cron 指令更為複雜。

然而,對於管理複雜基礎設施的人來說,權衡的結果明顯傾向於 systemd。能夠使用 ExecCondition= 進行原生條件執行,以及與 Ansible 等工具進行部署整合,使得 timers 在生產環境中成為更穩健的選擇。

實作總結清單

如果你正在從 cron 遷移到 systemd timers,請記住以下提示:

  1. 使用 systemd-analyze calendar 來驗證你的排程。

  2. 利用 Persistent=true 處理任何在停機期間絕不能被跳過的任務。

  3. 使用 RandomizedOffsetSec= 處理任何會衝擊網路 API 或共享資源的任務。

  4. 檢查 systemctl list-timers 定期監控系統的排程健康狀況。

Sources