Systemd 定時器:三種使用場景
![](https://linuxstory.org/wp-content/uploads/2024/06/093439gam6n0m35b3hibic.jpg)
在這個 systemd 系列教程中,我們已經在某種程度上討論了 systemd 定時器單元。不過,在我們開始討論 sockets 之前,我們先來看三個例子,這些例子展示了如何最佳化利用這些單元。
簡單的類 cron 行為
我每周都要去收集 Debian popcon 數據,如果每次都能在同一時間收集更好,這樣我就能看到某些應用程序的下載趨勢。這是一個可以使用 cron 任務來完成的典型事例,但 systemd 定時器同樣能做到:
# 類 cron 的 popcon.timer
[Unit]
Description= 這裡描述了下載並處理 popcon 數據的時刻
[Timer]
OnCalendar= Thu *-*-* 05:32:07
Unit= popcon.service
[Install]
WantedBy= basic.target
實際的 popcon.service
會執行一個常規的 wget
任務,並沒有什麼特別之處。這裡的新內容是 OnCalendar=
指令。這個指令可以讓你在一個特定日期的特定時刻來運行某個服務。在這個例子中,Thu
表示 「在周四運行」,*-*-*
表示「具體年份、月份和日期無關緊要」,這些可以翻譯成 「不管年月日,只在每周四運行」。
這樣,你就設置了這個服務的運行時間。我選擇在歐洲中部夏令時區的上午 5:30 左右運行,那個時候伺服器不是很忙。
如果你的伺服器關閉了,而且剛好錯過了每周的截止時間,你還可以在同一個計時器中使用像 anacron 一樣的功能。
# 具備類似 anacron 功能的 popcon.timer
[Unit]
Description= 這裡描述了下載並處理 popcon 數據的時刻
[Timer]
Unit=popcon.service
OnCalendar=Thu *-*-* 05:32:07
Persistent=true
[Install]
WantedBy=basic.target
當你將 Persistent=
指令設為真值時,它會告訴 systemd,如果伺服器在本該它運行的時候關閉了,那麼在啟動後就要立刻運行服務。這意味著,如果機器在周四凌晨停機了(比如說維護),一旦它再次啟動後,popcon.service
將會立刻執行。在這之後,它的運行時間將會回到例行性的每周四早上 5:32.
到目前為止,就是這麼簡單直白。
延遲執行
但是,我們提升一個檔次,來「改進」這個基於 systemd 的監控系統。你應該記得,當你接入攝像頭的時候,系統就會開始拍照。假設你並不希望它在你安裝攝像頭的時候拍下你的臉。你希望將拍照服務的啟動時間向後推遲一兩分鐘,這樣你就有時間接入攝像頭,然後走到畫框外面。
為了完成這件事,首先你要更改 Udev 規則,將它指向一個定時器:
ACTION=="add", SUBSYSTEM=="video4linux", ATTRS{idVendor}=="03f0",
ATTRS{idProduct}=="e207", TAG+="systemd", ENV{SYSTEMD_WANTS}="picchanged.timer",
SYMLINK+="mywebcam", MODE="0666"
這個定時器看起來像這樣:
# picchanged.timer
[Unit]
Description= 在攝像頭接入的一分鐘後,開始運行 picchanged
[Timer]
OnActiveSec= 1 m
Unit= picchanged.path
[Install]
WantedBy= basic.target
在你接入攝像頭後,Udev 規則被觸發,它會調用定時器。這個定時器啟動後會等上一分鐘(OnActiveSec= 1 m
),然後運行 picchanged.path
,它會監視主圖片的變化。picchanged.path
還會負責接觸 webcan.service
,這個實際用來拍照的服務。
在每天的特定時刻啟停 Minetest 伺服器
在最後一個例子中,我們認為你決定用 systemd 作為唯一的依賴。講真,不管怎麼樣,systemd 差不多要接管你的生活了。為什麼不擁抱這個必然性呢?
你有個為你的孩子設置的 Minetest 服務。不過,你還想要假裝關心一下他們的教育和成長,要讓他們做作業和家務活。所以你要確保 Minetest 只在每天晚上的一段時間內可用,比如五點到七點。
這個跟之前的「在特定時間啟動服務」不太一樣。寫個定時器在下午五點啟動服務很簡單…:
# minetest.timer
[Unit]
Description= 在每天下午五點運行 minetest.service
[Timer]
OnCalendar= *-*-* 17:00:00
Unit= minetest.service
[Install]
WantedBy= basic.target
…可是編寫一個對應的定時器,讓它在特定時刻關閉服務,則需要更大劑量的橫向思維。
我們從最明顯的東西開始 —— 設置定時器:
# stopminetest.timer
[Unit]
Description= 每天晚上七點停止 minetest.service
[Timer]
OnCalendar= *-*-* 19:05:00
Unit= stopminetest.service
[Install]
WantedBy= basic.target
這裡棘手的部分是如何去告訴 stopminetest.service
去 —— 你知道的 —— 停止 Minetest. 我們無法從 minetest.service
中傳遞 Minetest 伺服器的 PID. 而且 systemd 的單元辭彙表中也沒有明顯的命令來停止或禁用正在運行的服務。
我們的訣竅是使用 systemd 的 Conflicts=
指令。它和 systemd 的 Wants=
指令類似,不過它所做的事情正相反。如果你有一個 b.service
單元,其中包含一個 Wants=a.service
指令,在這個單元啟動時,如果 a.service
沒有運行,則 b.service
會運行它。同樣,如果你的 b.service
單元中有一行寫著 Conflicts= a.service
,那麼在 b.service
啟動時,systemd 會停止 a.service
.
這種機制用於兩個服務在嘗試同時控制同一資源時會發生衝突的場景,例如當兩個服務要同時訪問印表機的時候。通過在首選服務中設置 Conflicts=
,你就可以確保它會覆蓋掉最不重要的服務。
不過,你會在一個稍微不同的場景中來使用 Conflicts=
. 你將使用 Conflicts=
來乾淨地關閉 minetest.service
:
# stopminetest.service
[Unit]
Description= 關閉 Minetest 服務
Conflicts= minetest.service
[Service]
Type= oneshot
ExecStart= /bin/echo "Closing down minetest.service"
stopminetest.service
並不會做特別的東西。事實上,它什麼都不會做。不過因為它包含那行 Conflicts=
,所以在它啟動時,systemd 會關掉 minetest.service
.
在你完美的 Minetest 設置中,還有最後一點漣漪:你下班晚了,錯過了伺服器的開機時間,可當你開機的時候遊戲時間還沒結束,這該怎麼辦?Persistent=
指令(如上所述)在錯過開始時間後仍然可以運行服務,但這個方案還是不行。如果你在早上十一點把伺服器打開,它就會啟動 Minetest,而這不是你想要的。你真正需要的是一個確保 systemd 只在晚上五到七點啟動 Minetest 的方法:
# minetest.timer
[Unit]
Description= 在下午五到七點內的每分鐘都運行 minetest.service
[Timer]
OnCalendar= *-*-* 17..19:*:00
Unit= minetest.service
[Install]
WantedBy= basic.target
OnCalendar= *-*-* 17..19:*:00
這一行有兩個有趣的地方:(1) 17..19
並不是一個時間點,而是一個時間段,在這個場景中是 17 到 19 點;以及,(2) 分鐘欄位中的 *
表示服務每分鐘都要運行。因此,你會把它讀做 「在下午五到七點間的每分鐘,運行 minetest.service」
不過還有一個問題:一旦 minetest.service
啟動並運行,你會希望 minetest.timer
不要再次嘗試運行它。你可以在 minetest.service
中包含一條 Conflicts=
指令:
# minetest.service
[Unit]
Description= 運行 Minetest 伺服器
Conflicts= minetest.timer
[Service]
Type= simple
User= <your user name>
ExecStart= /usr/bin/minetest --server
ExecStop= /bin/kill -2 $MAINPID
[Install]
WantedBy= multi-user.targe
上面的 Conflicts=
指令會保證在 minstest.service
成功運行後,minetest.timer
就會立即停止。
現在,啟用並啟動 minetest.timer
:
systemctl enable minetest.timer
systemctl start minetest.timer
而且,如果你在六點鐘啟動了伺服器,minetest.timer
會啟用;到了五到七點,minetest.timer
每分鐘都會嘗試啟動 minetest.service
。不過,一旦 minetest.service
開始運行,systemd 會停止 minetest.timer
,因為它會與 minetest.service
「衝突」,從而避免計時器在服務已經運行的情況下還會不斷嘗試啟動服務。
在首先啟動某個服務時殺死啟動它的計時器,這麼做有點反直覺,但它是有效的。
總結
你可能會認為,有更好的方式來做上面這些事。我在很多文章中看到過「過度設計」這個術語,尤其是在用 systemd 定時器來代替 cron 的時候。
但是,這個系列文章的目的不是為任何具體問題提供最佳解決方案。它的目的是為了儘可能多地使用 systemd 來解決問題,甚至會到荒唐的程度。它的目的是展示大量的例子,來說明如何利用不同類型的單位及其包含的指令。我們的讀者,也就是你,可以從這篇文章中找到所有這些的可實踐範例。
儘管如此,我們還有一件事要做:下回中,我們會關注 sockets 和 targets,然後我們將完成對 systemd 單元的介紹。
你可以在 Linux 基金會和 edX 中,通過免費的 Linux 介紹課程中,學到更多關於 Linux 的知識。
via: https://www.linux.com/blog/intro-to-linux/2018/8/systemd-timers-two-use-cases-0
作者:Paul Brown 選題:lujun9972 譯者:StdioA 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive