使用 systemd 定時器代替 cron 作業
我正在致力於將我的 cron 作業遷移到 systemd 定時器上。我已經使用定時器多年了,但通常來說,我的學識只足以支撐我當前的工作。但在我研究 systemd 系列 的過程中,我發現 systemd 定時器有一些非常有意思的能力。
與 cron 作業類似,systemd 定時器可以在特定的時間間隔觸發事件(shell 腳本和程序),例如每天一次或在一個月中的特定某一天(或許只有在周一生效),或在從上午 8 點到下午 6 點的工作時間內每隔 15 分鐘一次。定時器也可以做到 cron 作業無法做到的一些事情。舉個例子,定時器可以在特定事件發生後的一段時間後觸發一段腳本或者程序去執行,例如開機、啟動、上個任務完成,甚至於定時器調用的上個服務單元的完成的時刻。
操作系統維護的計時器
當在一個新系統上安裝 Fedora 或者是任意一個基於 systemd 的發行版時,作為系統維護過程的一部分,它會在 Linux 宿主機的後台中創建多個定時器。這些定時器會觸發事件來執行必要的日常維護任務,比如更新系統資料庫、清理臨時目錄、輪換日誌文件,以及更多其他事件。
作為示例,我會查看一些我的主要工作站上的定時器,通過執行 systemctl status *timer 命令來展示主機上的所有定時器。星號的作用與文件通配相同,所以這個命令會列出所有的 systemd 定時器單元。
[root@testvm1 ~]# systemctl status *timer
● mlocate-updatedb.timer - Updates mlocate database every day
Loaded: loaded (/usr/lib/systemd/system/mlocate-updatedb.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
Trigger: Fri 2020-06-05 00:00:00 EDT; 15h left
Triggers: ● mlocate-updatedb.service
Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Updates mlocate database every day.
● logrotate.timer - Daily rotation of log files
Loaded: loaded (/usr/lib/systemd/system/logrotate.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
Trigger: Fri 2020-06-05 00:00:00 EDT; 15h left
Triggers: ● logrotate.service
Docs: man:logrotate(8)
man:logrotate.conf(5)
Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Daily rotation of log files.
● sysstat-summary.timer - Generate summary of yesterday's process accounting
Loaded: loaded (/usr/lib/systemd/system/sysstat-summary.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
Trigger: Fri 2020-06-05 00:07:00 EDT; 15h left
Triggers: ● sysstat-summary.service
Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Generate summary of yesterday's process accounting.
● fstrim.timer - Discard unused blocks once a week
Loaded: loaded (/usr/lib/systemd/system/fstrim.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
Trigger: Mon 2020-06-08 00:00:00 EDT; 3 days left
Triggers: ● fstrim.service
Docs: man:fstrim
Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Discard unused blocks once a week.
● sysstat-collect.timer - Run system activity accounting tool every 10 minutes
Loaded: loaded (/usr/lib/systemd/system/sysstat-collect.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
Trigger: Thu 2020-06-04 08:50:00 EDT; 41s left
Triggers: ● sysstat-collect.service
Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Run system activity accounting tool every 10 minutes.
● dnf-makecache.timer - dnf makecache --timer
Loaded: loaded (/usr/lib/systemd/system/dnf-makecache.timer; enabled; vendor preset: enabled)
Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
Trigger: Thu 2020-06-04 08:51:00 EDT; 1min 41s left
Triggers: ● dnf-makecache.service
Jun 02 08:02:33 testvm1.both.org systemd[1]: Started dnf makecache –timer.
● systemd-tmpfiles-clean.timer - Daily Cleanup of Temporary Directories
Loaded: loaded (/usr/lib/systemd/system/systemd-tmpfiles-clean.timer; static; vendor preset: disabled)
Active: active (waiting) since Tue 2020-06-02 08:02:33 EDT; 2 days ago
Trigger: Fri 2020-06-05 08:19:00 EDT; 23h left
Triggers: ● systemd-tmpfiles-clean.service
Docs: man:tmpfiles.d(5)
man:systemd-tmpfiles(8)
Jun 02 08:02:33 testvm1.both.org systemd[1]: Started Daily Cleanup of Temporary Directories.
每個定時器至少有六行相關信息:
- 定時器的第一行有定時器名字和定時器目的的簡短介紹
- 第二行展示了定時器的狀態,是否已載入,定時器單元文件的完整路徑以及預設信息。
- 第三行指明了其活動狀態,包括該定時器激活的日期和時間。
- 第四行包括了該定時器下次被觸發的日期和時間和距離觸發的大概時間。
- 第五行展示了被定時器觸發的事件或服務名稱。
- 部分(不是全部)systemd 單元文件有相關文檔的指引。我虛擬機上輸出中有三個定時器有文檔指引。這是一個很好(但非必要)的信息。
- 最後一行是計時器最近觸發的服務實例的日誌條目。
你也許有一些不一樣的定時器,取決於你的主機。
創建一個定時器
儘管我們可以解構一個或多個現有的計時器來了解其工作原理,但讓我們創建我們自己的 服務單元 和一個定時器去觸發它。為了保持簡單,我們將使用一個相當簡單的例子。當我們完成這個實驗之後,就能更容易理解其他定時器的工作原理以及發現它們正在做什麼。
首先,創建一個運行基礎東西的簡單的服務,例如 free 命令。舉個例子,你可能想定時監控空餘內存。在 /etc/systemd/system 目錄下創建如下的 myMonitor.server 單元文件。它不需要是可執行文件:
# This service unit is for testing timer units
# By David Both
# Licensed under GPL V2
#
[Unit]
Description=Logs system statistics to the systemd journal
Wants=myMonitor.timer
[Service]
Type=oneshot
ExecStart=/usr/bin/free
[Install]
WantedBy=multi-user.target
這大概是你能創建的最簡單的服務單元了。現在我們查看一下服務狀態同時測試一下服務單元確保它和我們預期一樣可用。
[root@testvm1 system]# systemctl status myMonitor.service
● myMonitor.service - Logs system statistics to the systemd journal
Loaded: loaded (/etc/systemd/system/myMonitor.service; disabled; vendor preset: disabled)
Active: inactive (dead)
[root@testvm1 system]# systemctl start myMonitor.service
[root@testvm1 system]#
輸出在哪裡呢?默認情況下,systemd 服務單元執行程序的標準輸出(STDOUT)會被發送到系統日誌中,它保留了記錄供現在或者之後(直到某個時間點)查看。(在本系列的後續文章中,我將介紹系統日誌的記錄和保留策略)。專門查看你的服務單元的日誌,而且只針對今天。-S 選項,即 --since 的縮寫,允許你指定 journalctl 工具搜索條目的時間段。這並不代表你不關心過往結果 —— 在這個案例中,不會有過往記錄 —— 如果你的機器以及運行了很長時間且堆積了大量的日誌,它可以縮短搜索時間。
[root@testvm1 system]# journalctl -S today -u myMonitor.service
-- Logs begin at Mon 2020-06-08 07:47:20 EDT, end at Thu 2020-06-11 09:40:47 EDT. --
Jun 11 09:12:09 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 11 09:12:09 testvm1.both.org free[377966]: total used free shared buff/cache available
Jun 11 09:12:09 testvm1.both.org free[377966]: Mem: 12635740 522868 11032860 8016 1080012 11821508
Jun 11 09:12:09 testvm1.both.org free[377966]: Swap: 8388604 0 8388604
Jun 11 09:12:09 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.
[root@testvm1 system]#
由服務觸發的任務可以是單個程序、一組程序或者是一個腳本語言寫的腳本。通過在 myMonitor.service 單元文件里的 [Service] 塊末尾中添加如下行可以為服務添加另一個任務:
ExecStart=/usr/bin/lsblk
再次啟動服務,查看日誌檢查結果,結果應該看上去像這樣。你應該在日誌中看到兩條命令的結果輸出:
Jun 11 15:42:18 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 11 15:42:18 testvm1.both.org free[379961]: total used free shared buff/cache available
Jun 11 15:42:18 testvm1.both.org free[379961]: Mem: 12635740 531788 11019540 8024 1084412 11812272
Jun 11 15:42:18 testvm1.both.org free[379961]: Swap: 8388604 0 8388604
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: sda 8:0 0 120G 0 disk
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: ├─sda1 8:1 0 4G 0 part /boot
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: └─sda2 8:2 0 116G 0 part
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: ├─VG01-root 253:0 0 5G 0 lvm /
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: ├─VG01-swap 253:1 0 8G 0 lvm [SWAP]
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: ├─VG01-usr 253:2 0 30G 0 lvm /usr
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: ├─VG01-tmp 253:3 0 10G 0 lvm /tmp
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: ├─VG01-var 253:4 0 20G 0 lvm /var
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: └─VG01-home 253:5 0 10G 0 lvm /home
Jun 11 15:42:18 testvm1.both.org lsblk[379962]: sr0 11:0 1 1024M 0 rom
Jun 11 15:42:18 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.
Jun 11 15:42:18 testvm1.both.org systemd[1]: Finished Logs system statistics to the systemd journal.
現在你知道了你的服務可以按預期工作了,在 /etc/systemd/system 目錄下創建 myMonitor.timer 定時器單元文件,添加如下代碼:
# This timer unit is for testing
# By David Both
# Licensed under GPL V2
#
[Unit]
Description=Logs some system statistics to the systemd journal
Requires=myMonitor.service
[Timer]
Unit=myMonitor.service
OnCalendar=*-*-* *:*:00
[Install]
WantedBy=timers.target
在 myMonitor.timer 文件中的 OnCalendar 時間格式,*-*-* *:*:00,應該會每分鐘觸發一次定時器去執行 myMonitor.service 單元。我會在文章的後面進一步探索 OnCalendar 設置。
到目前為止,在服務被計時器觸發運行時觀察與之有關的日誌記錄。你也可以跟蹤計時器,跟蹤服務可以讓你接近實時的看到結果。執行 journalctl 時帶上 -f 選項:
[root@testvm1 system]# journalctl -S today -f -u myMonitor.service
-- Logs begin at Mon 2020-06-08 07:47:20 EDT. --
執行但是不啟用該定時器,看看它運行一段時間後發生了什麼:
[root@testvm1 ~]# systemctl start myMonitor.service
[root@testvm1 ~]#
一條結果立即就顯示出來了,下一條大概在一分鐘後出來。觀察幾分鐘日誌,看看你有沒有跟我發現同樣的事情:
[root@testvm1 system]# journalctl -S today -f -u myMonitor.service
-- Logs begin at Mon 2020-06-08 07:47:20 EDT. --
Jun 13 08:39:18 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 13 08:39:18 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.
Jun 13 08:39:19 testvm1.both.org free[630566]: total used free shared buff/cache available
Jun 13 08:39:19 testvm1.both.org free[630566]: Mem: 12635740 556604 10965516 8036 1113620 11785628
Jun 13 08:39:19 testvm1.both.org free[630566]: Swap: 8388604 0 8388604
Jun 13 08:39:18 testvm1.both.org systemd[1]: Finished Logs system statistics to the systemd journal.
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: sda 8:0 0 120G 0 disk
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: ├─sda1 8:1 0 4G 0 part /boot
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: └─sda2 8:2 0 116G 0 part
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: ├─VG01-root 253:0 0 5G 0 lvm /
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: ├─VG01-swap 253:1 0 8G 0 lvm [SWAP]
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: ├─VG01-usr 253:2 0 30G 0 lvm /usr
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: ├─VG01-tmp 253:3 0 10G 0 lvm /tmp
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: ├─VG01-var 253:4 0 20G 0 lvm /var
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: └─VG01-home 253:5 0 10G 0 lvm /home
Jun 13 08:39:19 testvm1.both.org lsblk[630567]: sr0 11:0 1 1024M 0 rom
Jun 13 08:40:46 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 13 08:40:46 testvm1.both.org free[630572]: total used free shared buff/cache available
Jun 13 08:40:46 testvm1.both.org free[630572]: Mem: 12635740 555228 10966836 8036 1113676 11786996
Jun 13 08:40:46 testvm1.both.org free[630572]: Swap: 8388604 0 8388604
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: sda 8:0 0 120G 0 disk
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: ├─sda1 8:1 0 4G 0 part /boot
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: └─sda2 8:2 0 116G 0 part
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: ├─VG01-root 253:0 0 5G 0 lvm /
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: ├─VG01-swap 253:1 0 8G 0 lvm [SWAP]
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: ├─VG01-usr 253:2 0 30G 0 lvm /usr
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: ├─VG01-tmp 253:3 0 10G 0 lvm /tmp
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: ├─VG01-var 253:4 0 20G 0 lvm /var
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: └─VG01-home 253:5 0 10G 0 lvm /home
Jun 13 08:40:46 testvm1.both.org lsblk[630574]: sr0 11:0 1 1024M 0 rom
Jun 13 08:40:46 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.
Jun 13 08:40:46 testvm1.both.org systemd[1]: Finished Logs system statistics to the systemd journal.
Jun 13 08:41:46 testvm1.both.org systemd[1]: Starting Logs system statistics to the systemd journal...
Jun 13 08:41:46 testvm1.both.org free[630580]: total used free shared buff/cache available
Jun 13 08:41:46 testvm1.both.org free[630580]: Mem: 12635740 553488 10968564 8036 1113688 11788744
Jun 13 08:41:46 testvm1.both.org free[630580]: Swap: 8388604 0 8388604
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: sda 8:0 0 120G 0 disk
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: ├─sda1 8:1 0 4G 0 part /boot
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: └─sda2 8:2 0 116G 0 part
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: ├─VG01-root 253:0 0 5G 0 lvm /
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: ├─VG01-swap 253:1 0 8G 0 lvm [SWAP]
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: ├─VG01-usr 253:2 0 30G 0 lvm /usr
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: ├─VG01-tmp 253:3 0 10G 0 lvm /tmp
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: ├─VG01-var 253:4 0 20G 0 lvm /var
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: └─VG01-home 253:5 0 10G 0 lvm /home
Jun 13 08:41:47 testvm1.both.org lsblk[630581]: sr0 11:0 1 1024M 0 rom
Jun 13 08:41:47 testvm1.both.org systemd[1]: myMonitor.service: Succeeded.
Jun 13 08:41:47 testvm1.both.org systemd[1]: Finished Logs system statistics to the systemd journal.
別忘了檢查下計時器和服務的狀態。
你在日誌里大概至少注意到兩件事。第一,你不需要特地做什麼來讓 myMonitor.service 單元中 ExecStart 觸發器產生的 STDOUT 存儲到日誌里。這都是用 systemd 來運行服務的一部分功能。然而,它確實意味著你需要小心對待服務單元裡面執行的腳本和它們能產生多少 STDOUT。
第二,定時器並不是精確在每分鐘的 :00 秒執行的,甚至每次執行的時間間隔都不是剛好一分鐘。這是特意的設計,但是有必要的話可以改變這種行為(如果只是它挑戰了你的系統管理員的敏感神經)。
這樣設計的初衷是為了防止多個服務在完全相同的時刻被觸發。舉個例子,你可以用例如 Weekly,Daily 等時間格式。這些快捷寫法都被定義為在某一天的 00:00:00 執行。當多個定時器都這樣定義的話,有很大可能它們會同時執行。
systemd 定時器被故意設計成在規定時間附近隨機波動的時間點觸發,以避免同一時間觸發。它們在一個時間窗口內半隨機觸發,時間窗口開始於預設的觸發時間,結束於預設時間後一分鐘。根據 systemd.timer 的手冊頁,這個觸發時間相對於其他已經定義的定時器單元保持在穩定的位置。你可以在日誌條目中看到,定時器在啟動後立即觸發,然後在每分鐘後的 46 或 47 秒觸發。
大部分情況下,這種概率抖動的定時器是沒事的。當調度類似執行備份的任務,只需要它們在下班時間運行,這樣是沒問題的。系統管理員可以選擇確定的開始時間來確保不和其他任務衝突,例如 01:05:00 這樣典型的 cron 作業時間,但是有很大範圍的時間值可以滿足這一點。在開始時間上的一個分鐘級別的隨機往往是無關緊要的。
然而,對某些任務來說,精確的觸發時間是個硬性要求。對於這類任務,你可以向單元文件的 Timer 塊中添加如下聲明來指定更高的觸發時間跨度精確度(精確到微秒以內):
AccuracySec=1us
時間跨度可用於指定所需的精度,以及定義重複事件或一次性事件的時間跨度。它能識別以下單位:
usec,us,µsmsec,msseconds,second,sec,sminutes,minute,min,mhours,hour,hr,hdays,day,dweeks,week,wmonths,month,M(定義為 30.44 天)years,year,y(定義為 365.25 天)
所有 /usr/lib/systemd/system 中的定時器都指定了一個更寬鬆的時間精度,因為精準時間沒那麼重要。看看這些系統創建的定時器的時間格式:
[root@testvm1 system]# grep Accur /usr/lib/systemd/system/*timer
/usr/lib/systemd/system/fstrim.timer:AccuracySec=1h
/usr/lib/systemd/system/logrotate.timer:AccuracySec=1h
/usr/lib/systemd/system/logwatch.timer:AccuracySec=12h
/usr/lib/systemd/system/mlocate-updatedb.timer:AccuracySec=24h
/usr/lib/systemd/system/raid-check.timer:AccuracySec=24h
/usr/lib/systemd/system/unbound-anchor.timer:AccuracySec=24h
[root@testvm1 system]#
看下 /usr/lib/systemd/system 目錄下部分定時器單元文件的完整內容,看看它們是如何構建的。
在本實驗中不必讓這個定時器在啟動時激活,但下面這個命令可以設置開機自啟:
[root@testvm1 system]# systemctl enable myMonitor.timer
你創建的單元文件不需要是可執行的。你同樣不需要啟用服務,因為它是被定時器觸發的。如果你需要的話,你仍然可以在命令行裏手動觸發該服務單元。嘗試一下,然後觀察日誌。
關於定時器精度、事件時間規格和觸發事件的詳細信息,請參見 systemd.timer 和 systemd.time 的手冊頁。
定時器類型
systemd 定時器還有一些在 cron 中找不到的功能,cron 只在確定的、重複的、具體的日期和時間觸發。systemd 定時器可以被配置成根據其他 systemd 單元狀態發生改變時觸發。舉個例子,定時器可以配置成在系統開機、啟動後,或是某個確定的服務單元激活之後的一段時間被觸發。這些被稱為單調計時器。「單調」指的是一個持續增長的計數器或序列。這些定時器不是持久的,因為它們在每次啟動後都會重置。
表格 1 列出了一些單調定時器以及每個定時器的簡短定義,同時有 OnCalendar 定時器,這些不是單調的,它們被用於指定未來有可能重複的某個確定時間。這個信息來自於 systemd.timer 的手冊頁,有一些不重要的修改。
| 定時器 | 單調性 | 定義 |
|---|---|---|
OnActiveSec= |
X | 定義了一個與定時器被激活的那一刻相關的定時器。 |
OnBootSec= |
X | 定義了一個與機器啟動時間相關的計時器。 |
OnStartupSec= |
X | 定義了一個與服務管理器首次啟動相關的計時器。對於系統定時器來說,這個定時器與 OnBootSec= 類似,因為系統服務管理器在機器啟動後很短的時間後就會啟動。當以在每個用戶服務管理器中運行的單元進行配置時,它尤其有用,因為用戶的服務管理器通常在首次登錄後啟動,而不是機器啟動後。 |
OnUnitActiveSec= |
X | 定義了一個與將要激活的定時器上次激活時間相關的定時器。 |
OnUnitInactiveSec= |
X | 定義了一個與將要激活的定時器上次停用時間相關的定時器。 |
OnCalendar= |
定義了一個有日期事件表達式語法的實時(即時鐘)定時器。查看 systemd.time(7) 的手冊頁獲取更多與日曆事件表達式相關的語法信息。除此以外,它的語義和 OnActiveSec= 類似。 |
Table 1: systemd 定時器定義
單調計時器可使用同樣的簡寫名作為它們的時間跨度,即我們之前提到的 AccuracySec 表達式,但是 systemd 將這些名字統一轉換成了秒。舉個例子,比如你想規定某個定時器在系統啟動後五天觸發一次事件;它可能看起來像 OnBootSec=5d。如果機器啟動於 2020-06-15 09:45:27,這個定時器會在 2020-06-20 09:45:27 或在這之後的一分鐘內觸發。
日曆事件格式
日曆事件格式是定時器在所需的重複時間觸發的關鍵。我們開始看下一些 OnCalendar 設置一起使用的格式。
與 crontab 中的格式相比,systemd 及其計時器使用的時間和日曆格式風格不同。它比 crontab 更為靈活,而且可以使用類似 at 命令的方式允許模糊的日期和時間。它還應該足夠熟悉使其易於理解。
systemd 定時器使用 OnCalendar= 的基礎格式是 DOW YYYY-MM-DD HH:MM:SS。DOW(星期幾)是選填的,其他欄位可以用一個星號(*)來匹配此位置的任意值。所有的日曆時間格式會被轉換成標準格式。如果時間沒有指定,它會被設置為 00:00:00。如果日期沒有指定但是時間指定了,那麼下次匹配的時間可能是今天或者明天,取決於當前的時間。月份和星期可以使用名稱或數字。每個單元都可以使用逗號分隔的列表。單元範圍可以在開始值和結束值之間用 .. 指定。
指定日期有一些有趣的選項,波浪號(~)可以指定月份的最後一天或者最後一天之前的某幾天。/ 可以用來指定星期幾作為修飾符。
這裡有幾個在 OnCalendar 表達式中使用的典型時間格式例子。
| 日期事件格式 | 描述 |
|---|---|
DOW YYYY-MM-DD HH:MM:SS |
|
*-*-* 00:15:30 |
每年每月每天的 0 點 15 分 30 秒 |
Weekly |
每個周一的 00:00:00 |
Mon *-*-* 00:00:00 |
同上 |
Mon |
同上 |
Wed 2020-*-* |
2020 年每個周三的 00:00:00 |
Mon..Fri 2021-*-* |
2021 年的每個工作日(周一到周五)的 00:00:00 |
2022-6,7,8-1,15 01:15:00 |
2022 年 6、7、8 月的 1 到 15 號的 01:15:00 |
Mon *-05~03 |
每年五月份的下個周一同時也是月末的倒數第三天 |
Mon..Fri *-08~04 |
任何年份 8 月末的倒數第四天,同時也須是工作日 |
*-05~03/2 |
五月末的倒數第三天,然後 2 天后再來一次。每年重複一次。注意這個表達式使用了波浪號(~)。 |
*-05-03/2 |
五月的第三天,然後每兩天重複一次直到 5 月底。注意這個表達式使用了破折號(-)。 |
Table 2: OnCalendar 事件時間格式例子
測試日曆格式
systemd 提供了一個絕佳的工具用於檢測和測試定時器中日曆時間事件的格式。systemd-analyze calendar 工具解析一個時間事件格式,提供標準格式和其他有趣的信息,例如下次「經過」(即匹配)的日期和時間,以及距離下次觸發之前大概時間。
首先,看看未來沒有時間的日(注意 Next elapse 和 UTC 的時間會根據你當地時區改變):
[student@studentvm1 ~]$ systemd-analyze calendar 2030-06-17
Original form: 2030-06-17
Normalized form: 2030-06-17 00:00:00
Next elapse: Mon 2030-06-17 00:00:00 EDT
(in UTC): Mon 2030-06-17 04:00:00 UTC
From now: 10 years 0 months left
[root@testvm1 system]#
現在添加一個時間,在這個例子中,日期和時間是當作無關的部分分開解析的:
[root@testvm1 system]# systemd-analyze calendar 2030-06-17 15:21:16
Original form: 2030-06-17
Normalized form: 2030-06-17 00:00:00
Next elapse: Mon 2030-06-17 00:00:00 EDT
(in UTC): Mon 2030-06-17 04:00:00 UTC
From now: 10 years 0 months left
Original form: 15:21:16
Normalized form: *-*-* 15:21:16
Next elapse: Mon 2020-06-15 15:21:16 EDT
(in UTC): Mon 2020-06-15 19:21:16 UTC
From now: 3h 55min left
[root@testvm1 system]#
為了把日期和時間當作一個單元來分析,可以把它們包在引號里。你在定時器單元里 OnCalendar= 時間格式中使用的時候記得把引號去掉,否則會報錯:
[root@testvm1 system]# systemd-analyze calendar "2030-06-17 15:21:16"
Normalized form: 2030-06-17 15:21:16
Next elapse: Mon 2030-06-17 15:21:16 EDT
(in UTC): Mon 2030-06-17 19:21:16 UTC
From now: 10 years 0 months left
[root@testvm1 system]#
現在我們測試下 Table2 里的例子。我尤其喜歡最後一個:
[root@testvm1 system]# systemd-analyze calendar "2022-6,7,8-1,15 01:15:00"
Original form: 2022-6,7,8-1,15 01:15:00
Normalized form: 2022-06,07,08-01,15 01:15:00
Next elapse: Wed 2022-06-01 01:15:00 EDT
(in UTC): Wed 2022-06-01 05:15:00 UTC
From now: 1 years 11 months left
[root@testvm1 system]#
讓我們看一個例子,這個例子里我們列出了時間表達式的五個經過時間。
[root@testvm1 ~]# systemd-analyze calendar --iterations=5 "Mon *-05~3"
Original form: Mon *-05~3
Normalized form: Mon *-05~03 00:00:00
Next elapse: Mon 2023-05-29 00:00:00 EDT
(in UTC): Mon 2023-05-29 04:00:00 UTC
From now: 2 years 11 months left
Iter. #2: Mon 2028-05-29 00:00:00 EDT
(in UTC): Mon 2028-05-29 04:00:00 UTC
From now: 7 years 11 months left
Iter. #3: Mon 2034-05-29 00:00:00 EDT
(in UTC): Mon 2034-05-29 04:00:00 UTC
From now: 13 years 11 months left
Iter. #4: Mon 2045-05-29 00:00:00 EDT
(in UTC): Mon 2045-05-29 04:00:00 UTC
From now: 24 years 11 months left
Iter. #5: Mon 2051-05-29 00:00:00 EDT
(in UTC): Mon 2051-05-29 04:00:00 UTC
From now: 30 years 11 months left
[root@testvm1 ~]#
這些應該為你提供了足夠的信息去開始測試你的 OnCalendar 時間格式。systemd-analyze 工具可用於其他有趣的分析,我會在這個系列的下一篇文章來探索這些。
總結
systemd 定時器可以用於執行和 cron 工具相同的任務,但是通過按照日曆和單調時間格式去觸發事件的方法提供了更多的靈活性。
雖然你為此次實驗創建的服務單元通常是由定時器調用的,你也可以隨時使用 systemctl start myMonitor.service 命令去觸發它。可以在一個定時器中編寫多個維護任務的腳本;它們可以是 Bash 腳本或者其他 Linux 程序。你可以通過觸發定時器來運行所有的腳本來運行服務,也可以按照需要執行單獨的腳本。
我會在下篇文章中更加深入的探索 systemd 時間格式的用處。
我還沒有看到任何跡象表明 cron 和 at 將被廢棄。我希望這種情況不會發生,因為至少 at 在執行一次性調度任務的時候要比 systemd 定時器容易的多。
參考資料
網上有大量的關於 systemd 的參考資料,但是大部分都有點簡略、晦澀甚至有誤導性。除了本文中提到的資料,下列的網頁提供了跟多可靠且詳細的 systemd 入門信息。
- Fedora 項目有一篇切實好用的 systemd 入門,它囊括了幾乎所有你需要知道的關於如何使用 systemd 配置、管理和維護 Fedora 計算機的信息。
- Fedora 項目也有一個不錯的 備忘錄,交叉引用了過去 SystemV 命令和 systemd 命令做對比。
- 關於 systemd 的技術細節和創建這個項目的原因,請查看 Freedesktop.org 上的 systemd 描述。
- Linux.com 的「更多 systemd 的樂趣」欄目提供了更多高級的 systemd 信息和技巧。
此外,還有一系列深度的技術文章,是由 systemd 的設計者和主要實現者 Lennart Poettering 為 Linux 系統管理員撰寫的。這些文章寫於 2010 年 4 月至 2011 年 9 月間,但它們現在和當時一樣具有現實意義。關於 systemd 及其生態的許多其他好文章都是基於這些文章:
- Rethinking PID 1
- systemd for Administrators,Part I
- systemd for Administrators,Part II
- systemd for Administrators,Part III
- systemd for Administrators,Part IV
- systemd for Administrators,Part V
- systemd for Administrators,Part VI
- systemd for Administrators,Part VII
- systemd for Administrators,Part VIII
- systemd for Administrators,Part IX
- systemd for Administrators,Part X
- systemd for Administrators,Part XI
via: https://opensource.com/article/20/7/systemd-timers
作者:David Both 選題:lujun9972 譯者:tt67wq 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

















