skip to content
鰭狀漏斗

怎麽做出 systemd 的服務

/ 閱讀時間 8 分鐘

先前我有介紹過 systemd 的常用指令。接著在這篇文章,我要介紹怎麽寫出一個服務檔案。首先我會介紹服務檔案大概長怎樣,接著介紹常用的設定選項,最後列出一個範例做結尾。

架構

服務檔案由 section 組成,類似這樣:

# A section
[SectionA]
Key1=val1
# Another section
[SectionB]
Key2=val2
Key3=val3

section 由用中括號包起來的英文單字開頭,下面接著若干個設定。

每一行有一筆設定,包含設定選項跟設定值,中間以 = 隔開,左邊是選項的名稱,右邊是設定值。

服務檔案可以有註解。以 #; 開頭的一行會被當做註解被忽略。

服務有哪些 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.serviceb.service,要讓 a.serviceb.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 種,但是我只會提到常用的:simpleexeconeshot

simpleexec 的不同點是服務被視為「已啟動」的時間點不一樣。simple 類型的服務在被 fork 出去的當下就視為已啟動,而 exec 類型的服務則是在服務的執行檔(指的大概是 ExecStart= 的指令)已經執行的時候才視為已啟動。

oneshotsimple 對於啟動的時間點是一樣的,不一樣的是 oneshot 在主行程結束時就會被視為已結束。

執行指令

要指令服務啟動要執行的指令,可以在 [Service] section 下的 ExecStart= 設定。除了 oneshot 類型的服務外,一個服務只能有一個 ExecStart= 設定。

oneshot 類型的服務可以有多個 ExecStart= 設定,它們會照順序執行。也可以沒有 ExecStart= 設定,但是需要在同個 section 有 RemainAfterExit=yes 以及至少一個 ExecStop= 設定,代表這個服務是設定在要停止時執行指令的。

我們也可以用 ExecStartPre=ExecStart= 指定的指令前執行指令,或是用 ExecStartPost=ExecStart= 指定的指令後執行指令。它們可以有複數個,一樣會照順序執行。

範例

下面用一個簡單的服務檔案做範例,這個檔案是在這篇介紹 Podman 自動更新的文章中,我有提到過的:

podman-auto-update.service
[Unit]
Description=Podman auto-update service
Documentation=man:podman-auto-update(1)
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/podman auto-update
ExecStartPost=/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.targetdefault.target。這兩個都是 systemd 內建的特殊 unit,表示系統中的某件事情已經完成,default.target 表示開機完成,network-online.target 則表示網路已經可以使用。

WantedBy=default.target 是很常用的設定,代表開機時要啟動這個服務。不過官方現在不推薦用 default.target,而應該改用其他類似的 target,如 multi-user.target,詳情可以看這邊

Wants=network-online.targetAfter=network-online.target 也是很常用的設定,代表開機後要到網路可以用時才能啟動這個服務。如果網路不是 systemd 服務管的,這個設定等於沒有用,除非更改 network-online.target 的意涵

結論

寫一個服務檔案要知道執行什麼指令以及要依賴哪些其他服務,套用上面的設定選項後,就可以做出簡單的服務了。

文件裡還有很多別的設定選項是我沒有提到的,如果這篇文章介紹的設定選項不夠用,可以看文件找找有沒有合適的設定選項。

參考資料