怎麽做出 systemd 的服務
/ 閱讀時間 8 分鐘
目次
先前我有介紹過 systemd 的常用指令。接著在這篇文章,我要介紹怎麽寫出一個服務檔案。首先我會介紹服務檔案大概長怎樣,接著介紹常用的設定選項,最後列出一個範例做結尾。
架構
服務檔案由 section 組成,類似這樣:
# A section[SectionA]Key1=val1
# Another section[SectionB]Key2=val2Key3=val3section 由用中括號包起來的英文單字開頭,下面接著若干個設定。
每一行有一筆設定,包含設定選項跟設定值,中間以 = 隔開,左邊是選項的名稱,右邊是設定值。
服務檔案可以有註解。以 # 或 ; 開頭的一行會被當做註解被忽略。
服務有哪些 section?
服務檔案是 unit 檔案的一種,unit 檔案可以有 [Unit] 跟 [Install] section。不同種類的 unit 檔案可以有它們專屬的 section,所以服務檔案還可以有 [Service] section。因此服務檔案可能有 3 個 section:[Unit]、[Install] 與 [Service]。
常用的設定選項
下面介紹一些常用的設定選項。
接下來我在提及設定選項的名稱的時候,會比照 systemd 的文件在後面加一個 =,像這樣:Key=。
說明
在 [Unit] section 可以加入這些選項來說明 unit 檔案的用途:
Description=:unit 檔案的一句簡短的介紹。Documentation=:unit 檔案的文件位置。
相依性
systemd 其中一個功能就是管理服務的開始順序。
systemd 有分兩種相依性,一種是順序,另一種是需求。
順序
我們可以在 [Unit] section 用 Before= 與 After= 指定服務啟動的順序。
例如有兩個服務 a.service、b.service,要讓 a.service 比 b.service 早啟動的話,可以在 a.service 的 [Unit] section 加上 Before=b.service。
需求
這種相依性代表的意思是當某個服務啟動了,需要那個服務的服務就會啟動。這種相依性不一定會有時間差,有設定這種相依性的服務雙方有可能會同時啟動。
我們可以用幾種選項指定需求的相依性,不過我只會提到常用的兩類:Wants= / WantedBy=、Requires= / RequiredBy=。
如果 a.service 的 [Unit] section 有 Wants=b.service,代表 b.service 啟動了 a.service 才會啟動。
Requires= 跟 Wants= 有類似的意義,不過在啟動失敗的時候有差別。如果 Wants= 指定的服務啟動失敗了,這個服務還是會照樣執行。而如果 Requires= 指定的服務啟動失敗了,這個服務會停止;如果上述這個 Requires= 指定的服務也有在 After= 被指定,這個服務不會啟動。
這些設定選項放置的位置也需要注意。Wants= 跟 Requires= 這種主動型態的設定選項要放置在 [Unit] section 下,而 WantedBy= 跟 RequiredBy= 這種被動型態的設定選項要放置在 [Install] section 下。systemd 文件有一張表整理了這類關係,大家可以看看。
服務類型
服務的類型可以在 [Service] section 下的 Type= 設定,一共有 8 種,但是我只會提到常用的:simple、exec 跟 oneshot。
simple 跟 exec 的不同點是服務被視為「已啟動」的時間點不一樣。simple 類型的服務在被 fork 出去的當下就視為已啟動,而 exec 類型的服務則是在服務的執行檔(指的大概是 ExecStart= 的指令)已經執行的時候才視為已啟動。
oneshot 跟 simple 對於啟動的時間點是一樣的,不一樣的是 oneshot 在主行程結束時就會被視為已結束。
執行指令
要指令服務啟動要執行的指令,可以在 [Service] section 下的 ExecStart= 設定。除了 oneshot 類型的服務外,一個服務只能有一個 ExecStart= 設定。
oneshot 類型的服務可以有多個 ExecStart= 設定,它們會照順序執行。也可以沒有 ExecStart= 設定,但是需要在同個 section 有 RemainAfterExit=yes 以及至少一個 ExecStop= 設定,代表這個服務是設定在要停止時執行指令的。
我們也可以用 ExecStartPre= 在 ExecStart= 指定的指令前執行指令,或是用 ExecStartPost= 在 ExecStart= 指定的指令後執行指令。它們可以有複數個,一樣會照順序執行。
範例
下面用一個簡單的服務檔案做範例,這個檔案是在這篇介紹 Podman 自動更新的文章中,我有提到過的:
[Unit]Description=Podman auto-update serviceDocumentation=man:podman-auto-update(1)Wants=network-online.targetAfter=network-online.target
[Service]Type=oneshotExecStart=/usr/bin/podman auto-updateExecStartPost=/usr/bin/podman image prune -f
[Install]WantedBy=default.target首先我們看這個服務會執行什麼東西?/usr/bin/podman auto-update 也就是 Podman 自動更新 image 的指令。再來會接著執行 /usr/bin/podman image prune -f 把不會用到的 image 刪掉。
因為它是 oneshot 類型的服務,所以在第一個指令執行完成後,服務就會停止。
最後,這個服務依賴什麼東西?network-online.target 跟 default.target。這兩個都是 systemd 內建的特殊 unit,表示系統中的某件事情已經完成,default.target 表示開機完成,network-online.target 則表示網路已經可以使用。
WantedBy=default.target 是很常用的設定,代表開機時要啟動這個服務。不過官方現在不推薦用 default.target,而應該改用其他類似的 target,如 multi-user.target,詳情可以看這邊。
Wants=network-online.target 跟 After=network-online.target 也是很常用的設定,代表開機後要到網路可以用時才能啟動這個服務。如果網路不是 systemd 服務管的,這個設定等於沒有用,除非更改 network-online.target 的意涵。
結論
寫一個服務檔案要知道執行什麼指令以及要依賴哪些其他服務,套用上面的設定選項後,就可以做出簡單的服務了。
文件裡還有很多別的設定選項是我沒有提到的,如果這篇文章介紹的設定選項不夠用,可以看文件找找有沒有合適的設定選項。