Cron表示式轉systemd定時器工具

免費工具,將cron表示式(分 時 日 月 星期)轉換為systemd定時器的OnCalendar=語法。同時對日期與星期同時限定時的OR語義差異進行提示,適用於將cron任務遷移到Docker/Kubernetes環境。

cron欄位與systemd組成部分對照表

cron表示式的5個欄位與systemd的OnCalendar=語法對應關係如下。

位置 cron欄位 systemd對應部分
1 分鐘 時間部分的分鐘(HH:MM:SS中的MM)
2 小時 時間部分的小時(HH:MM:SS中的HH)
3 日(日期) 日期部分的日(年-月-日中的日)
4 月 日期部分的月(年-月-日中的月)
5 星期 星期字首(例如Mon、Mon..Fri)

由於cron沒有秒欄位,systemd的秒部分始終為"00"。當日期和月份均不受限(*)時,systemd的年-月-日部分可以完全省略。

cron星期編號與systemd星期縮寫對照表

cron用0〜7的數字表示星期,而systemd使用英文星期縮寫。

cron星期編號 systemd星期縮寫
0 (7) Sun
1 Mon
2 Tue
3 Wed
4 Thu
5 Fri
6 Sat

常見轉換示例

cron表示式 OnCalendar= 含義
* * * * *
OnCalendar=*-*-* *:*:00
每分鐘執行一次(相當於cron的"* * * * *")。
*/15 * * * *
OnCalendar=*:00/15:00
每15分鐘執行一次。
0 9 * * 1-5
OnCalendar=Mon..Fri 09:00:00
工作日(週一至週五)上午9點執行。
0 0 1 * *
OnCalendar=*-*-01 00:00:00
每月1日0點執行。
0 0 1 1 *
OnCalendar=*-01-01 00:00:00
每年1月1日0點執行。
0 0 1 * 1
OnCalendar=Mon 00:00:00
OnCalendar=*-*-01 00:00:00
同時指定日期(1日)與星期(週一)的範例。在 cron 中這是 OR 條件(滿足其一即執行)。若寫成一行「Mon *-*-01」就會變成 AND 條件(1日且是週一),一年只執行一到三次,因此拆成兩行以保持相同的意思。

同時限定日期與星期(都不以 * 開頭)的 cron 表示式,如果寫成一行 OnCalendar=,意思就會改變(cron 是 OR,而 systemd 在一行內是 AND)。在同一個 .timer 單元中分成星期一行、日期一行來寫,就會在符合其中任一條件的日子執行,與 cron 的意思相同。

把 cron 式改寫成 systemd 計時器

要把排程由 cron 遷往 systemd 計時器,便須把「分 時 日 月 星期」這五個欄位譯成 OnCalendar= 的寫法。輸入一條 cron 式,本工具便產出與之對應的 OnCalendar=,並把值得當心之處一併報作警告。

這道轉換中最要緊的,是「日」與「星期」同時給出時二者的脾性。 cron 把這兩者讀作或:0 0 1 * 1 意為「每月一日或每個星期一」,一個月裡可能跑上五回。而 systemd 的 OnCalendar= 卻把同樣的寫法當作與。 它成了「一日且星期一」,一年裡只跑一到三回。機械地照搬,作業的跑動頻次便悄然變了。 本工具會偵出這一組合,並把 OnCalendar= 拆成兩行,保住原來的意思。

轉換的步驟

  1. 輸入 cron 式 把分、時、日、月、星期這五個欄位以空格分開寫出。
  2. 查看產生的 OnCalendar= 您會得到與之對應的 systemd 寫法。
  3. 閱讀警告 日與星期同時給出時,含義的變動會被點出來。
  4. 謄進計時器單元 貼進 .timer 檔案的 [Timer] 一節即可。

用好本工具的小技巧

  • 如果同時限定日期與星期,cron 會按 OR 條件解釋。由於 systemd 在一行內按 AND 處理,本工具會輸出兩行 OnCalendar=(一個定時器寫多行時,只要符合其中任意一行就會執行)。
  • Debian、Ubuntu 系的官方 Docker 映像等最小化容器往往不含 cron 守護程序(Alpine Linux 內建 BusyBox 的 crond,但需要自行啟動)。如果主機執行 systemd,遷移到 systemd 定時器是一個實用的選擇。
  • 可以使用"systemd-analyze calendar"命令驗證生成的OnCalendar行,該命令還會顯示接下來的計劃執行時間。
  • 與單行crontab不同,systemd定時器由兩個檔案組成:.timer單元(計劃部分)和.service單元(實際執行的任務)。
  • systemd 的重複寫法(例如 00/15)無法指定結束值,因此 cron 中「10-20/5」這類範圍內的步長會展開為「10,15,20」這樣的列舉。星期的步長(*/2)在 systemd 中也不能寫成「Sun/2」,所以會展開為「Sun,Tue,Thu,Sat」。

這些情境會用到

遷往容器環境

容器裡往往沒有 cron 守護行程,排程便須遷往 systemd 計時器或別的安排。

想留下執行的紀錄

systemd 計時器會在 journald 裡留痕,事後可以追查。

想讓作業之間有相依

「在另一單元啟動之後再跑」這類事,cron 表達不出來。

盤點既有的排程

把一批 cron 式一併轉換,正是核對間隔是否合乎本意的好時機。

排程的術語

cron 式
以分、時、日、月、星期五個欄位表示時刻的寫法。日與星期被讀作或。
OnCalendar=
在 systemd 計時器中言明時刻的鍵。日與星期被讀作與。
計時器單元
副檔名為 .timer 的檔案,它啟動同名的 .service。
Persistent=
該跑的時刻系統若正停著,此設定會讓計時器開機後補跑。 其行事與 cron 的 anacron 相仿。
AccuracySec=
觸發時刻的容差。預設有一分鐘的寬裕,故須嚴絲合縫時應予指明。
journald
systemd 的日誌收集機制。由計時器啟動的作業,其輸出亦存於此。

常見問題

OnCalendar=的格式為"星期 年-月-日 時:分:秒"(星期和日期部分可省略)。"*"表示"任意值",".."表示範圍,"/"表示間隔,","用於列舉多個值。例如"Mon..Fri 09:00:00"表示工作日上午9點整。

systemd定時器提供了cron本身沒有的功能:通過journalctl集中管理執行日誌、等待依賴的服務先啟動、以及可配置的失敗自動重試。容器基礎映象也越來越傾向於不包含cron守護程序以保持輕量,因此在基於systemd的主機上,定時器成為自然的替代方案。

按照Vixie cron的語義,當日期與星期都被限定(非"*")時,只要滿足其中一個條件任務就會執行,即OR條件。而systemd的OnCalendar語法始終將星期和日期部分作為AND條件處理(必須同時滿足)。本工具偵測到這種情況時,會分成星期一行、日期一行共兩行 OnCalendar= 輸出,以保持與 cron 相同的意思。另外,像 */2 這樣以 * 開頭的日期指定在 cron 中也被視為不受限制,與星期是 AND 關係,因此保持一行轉換。

多數情況下可以直接使用,但建議在部署到正式環境前,用 systemd-analyze calendar 命令列出接下來的執行時間,確認與原 cron 表示式預期的時間一致。另外也請一併檢查 cron 沒有的設定,例如在系統停機期間錯過的執行會在啟動後補做的 Persistent=。

標準的5欄位cron表示式沒有秒欄位,因此生成的OnCalendar行中的秒部分始終為"00"。systemd的OnCalendar語法本身支援指定秒,但本工具從cron轉換時不會使用該功能。
工具君

閒話 ― 從cron到systemd定時器

systemd大約於2010年前後作為管理Linux啟動流程的初始化系統出現,此後成為大多數發行版的標準配置。其中的定時器功能與cron類似,提供週期性執行能力,但增加了cron從未擁有的功能:依賴關係解析、通過journalctl集中管理執行日誌,以及可配置的失敗重試行為。

與此同時,Docker和Kubernetes等容器環境中,為了保持映象體積小巧,越來越多的基礎映象根本不包含cron守護程序。當需要在此類環境中執行週期性任務時,現實的選擇要麼依賴Kubernetes CronJob等編排層面的機制,要麼在主機本身執行systemd的情況下交給systemd定時器處理。

OnCalendar=語法比cron的5個欄位表達能力更強,支援指定年份精度和秒級精度,但正因如此,一些cron表示式(同時限定日期與星期的OR條件)在簡單轉換下會改變含義。本工具明確地提示這一警告,正是為了讓遷移過程不至於陷入這個容易被忽略的陷阱。