cron 用了幾十年,它哪裡不好?
問題不在功能,而在可觀測性。一個 cron job 失敗,你通常不知道——除非有另一套機制在監控它。crontab -l 看不出上次有沒有跑、跑了多久、有沒有出錯。排錯方式是翻 /var/log/syslog 找幾行不完整的 log,或是把 stdout/stderr 自己導向某個檔案。
systemd timer 解決的核心問題就是這個:每次執行都有完整的 log,journalctl 一個指令就能看,失敗有 exit code、有 timestamp、有完整的 stderr 輸出。
這篇文章不是說 cron 不好,而是幫你判斷什麼情況下應該換成 systemd timer——以及怎麼換。
systemd timer 的基本結構
一個 systemd timer 由兩個檔案組成:.service 定義要跑什麼,.timer 定義什麼時候跑。這比 cron 多一個檔案,但換來的是完整的 systemd 整合。
以一個每天凌晨 3 點備份資料庫為例:
建立 service 檔(/etc/systemd/system/db-backup.service):
1 | [Unit] |
建立 timer 檔(/etc/systemd/system/db-backup.timer):
1 | [Unit] |
啟用並啟動 timer:
1 | sudo systemctl daemon-reload |
list-timers 會告訴你下次觸發時間是什麼時候,這是 cron 完全沒有的能力。
為什麼 Persistent=true 很重要
cron 有個行為:如果機器在預定的執行時間是關機狀態,那次任務就直接跳過,永遠不補跑。對 24/7 運行的伺服器影響不大,但 VPS 有時候會重開機維護、或是意外宕機。
Persistent=true 告訴 systemd:如果上次預定的執行時間被錯過了,機器開機後立刻補跑一次。備份任務、日誌清理、資料同步,這些都應該設定這個選項。
看執行結果只需要一個指令
這是 systemd timer 最明顯的優勢:
1 | # 看最近的執行結果 |
stdout 和 stderr 都自動進 journal,不需要在 script 裡手動 redirect。執行時間、exit code、完整輸出,全部都有。
相比之下,傳統 cron 的除錯方式是:
1 | # 找這個 cron job 有沒有跑 |
相依性管理
假設你有一個備份腳本,需要在 PostgreSQL 服務啟動之後才能跑,cron 沒辦法表達這個關係——你只能在 script 裡自己檢查。
systemd 直接支援:
1 | [Unit] |
加了 Requires=postgresql.service,如果 PostgreSQL 沒有跑起來,這個任務就不會執行。After= 確保執行順序,Requires= 確保相依關係。
另一個實用場景是「開機後等 N 分鐘再執行」:
1 | [Timer] |
這個設定讓任務在開機後 10 分鐘執行第一次,之後每 6 小時執行一次。cron 的 @reboot 沒辦法加延遲,只能在 script 裡 sleep,又髒又不可靠。
資源限制:避免備份任務把 CPU 吃光
因為 service 是 systemd 管理的單元,可以直接用 systemd 的 cgroup 機制限制資源:
1 | [Service] |
備份或壓縮任務通常不急,但如果不限制資源,跑起來可能讓線上服務的回應變慢。cron job 沒有這個機制,你只能在 script 開頭加 nice -n 19 調整 CPU 優先度,磁碟 I/O 則完全沒辦法控制。
用 systemd-analyze calendar 驗證時間表達式
systemd 的時間語法和 cron 不同,可以在套用前先驗證:
1 | # 驗證並看下幾次觸發時間 |
輸出會告訴你接下來五次的觸發時間,避免寫錯語法結果 job 從來沒跑。常見的 cron 語法對照:
| cron | systemd OnCalendar |
|---|---|
0 3 * * * |
*-*-* 03:00:00 |
*/5 * * * * |
*:0/5 |
0 0 * * 1 |
Mon *-*-* 00:00:00 |
@reboot |
OnBootSec=0 |
手動觸發測試
不用等到預定時間,可以直接觸發 service 測試:
1 | sudo systemctl start db-backup.service |
這比 cron 方便太多。cron 要測試通常得改時間、等執行、看 log,或是直接跑 script 但環境變數又跟 cron 執行環境不一樣。
什麼時候繼續用 cron
systemd timer 不是在所有情況下都更好。這些場景繼續用 cron:
容器環境:Docker 容器裡通常沒有 systemd,cron 還是標準選擇。在容器裡需要排程任務,裝 cron 或用 supercronic 更適合。
簡單的一次性任務:如果只是 0 4 * * * /usr/bin/some-command,而且你也不在意 log,cron 一行搞定,沒必要為此建兩個 unit 檔。
需要跨平台:腳本需要在 FreeBSD、macOS 或 minimal Linux 上跑,cron 是唯一可靠的選項。
對於在 systemd 系統上的新排程任務,特別是需要可靠性和可觀測性的生產環境任務,systemd timer 是更好的預設選擇。
如果你在 NCSE Network 的 VPS 上有排程任務需要管理,NVMe SSD 搭配臺灣機房讓你的自動化腳本跑得穩、跑得快。詳情見 ncse.tw。