Systemd 服務:比啟動停止服務更進一步
來自專欄 Linux11 人贊了文章
在上一篇文章中,我們展示了如何創建一個 systemd 服務並使普通用戶可以啟動和終止遊戲伺服器。但到目前為止,使用這個服務並不比直接運行伺服器高明多少。讓我們更進一步,讓其可以向玩家發郵件,包括在伺服器可用時通知玩家,在伺服器關閉前警告玩家:
# minetest.service[Unit]Description= Minetest serverDocumentation= https://wiki.minetest.net/Main_Page[Service]Type= simpleExecStart= /usr/games/minetest --serverExecStartPost= /home/<username>/bin/mtsendmail.sh "Ready to rumble?" "Minetest Starting up"TimeoutStopSec= 180ExecStop= /home/<username>/bin/mtsendmail.sh "Off to bed. Nightie night!" " Minetest Stopping in 2 minutes"ExecStop= /bin/sleep 120ExecStop= /bin/kill -2 $MAINPID
這裡涉及幾個新的指令。首先是 ExecStartPost
指令,該指令可以在主進程啟動後馬上執行任何你指定的操作。在本例中,你執行了一個自定義腳本 mtsendmail
(內容如下),該腳本以郵件形式通知你的朋友伺服器已經啟動。
#!/bin/bash# mtsendmailecho $1 | mutt -F /home/<username>/.muttrc -s "$2" my_minetest@mailing_list.com
我們使用 Mutt 這個命令後郵件客戶端發送消息。雖然從實際效果來看,上述腳本僅有 1 行,但 systemd 單元的參數中不能包含管道及重定向操作,故我們需要將其封裝到腳本中。
順便提一下,還有一個 ExecStartPre
指令,用於在服務主進程執行之前進行指定操作。
接下來我們看到,關閉伺服器涉及了好幾條指令。TimeoutStopSec
指令用於設置 systemd 友好關閉服務的最大等候時間,默認值大約是 90 秒。超過這個最大等候時間,systemd 會強制關閉服務並報錯。考慮到你希望在徹底關閉伺服器前給用戶預留幾分鐘的時間,你需要將超時時間提高至 3 分鐘,這樣 systemd 就不會誤認為服務關閉時出現問題。
接下來是關閉服務的具體指令部分。雖然沒有 ExecStopPre
這樣的指令,但你可以通過多次使用 ExecStop
指令實現關閉伺服器前執行操作的目標。多個 ExecStop
指令按從上到下的順序依次運行,這樣你就可以在伺服器真正關閉前向用戶發送消息。
通過這個特性,你首先應該向你的朋友發郵件,警告其伺服器即將關閉,然後等待兩分鐘,最後關閉伺服器。可以使用 Ctrl + c
關閉 Minetest 伺服器,該操作會被轉換為一個中斷信號(SIGINT
);當你執行 kill -2 $MAINPID
時就會發送該中斷信號,其中 $MAINPID
是 systemd 變數,用於記錄你服務中主進程的 PID 信息。
看上去好多了!如果你此時啟動服務:
systemctl --user start minetest
服務會啟動 Minetest 伺服器並向你的用戶發送郵件。關閉服務的情形基本類似,只不過會額外留給用戶 2 分鐘時間退出登錄。
開機自啟動
下一步我們讓你的服務在主機啟動後立即可用,在主機關閉時自動關閉。
我們需要將你的服務文件移動到系統服務目錄,即 /etc/systemd/system/
:
sudo mv /home/<username>/.config/systemd/user/minetest.service /etc/systemd/system/
如果你希望此時啟動該服務,你需要擁有超級用戶許可權:
sudo systemctl start minetest
另外,可以使用如下命令檢查服務狀態:
sudo systemctl status minetest
你會發現服務很糟糕地處於失敗狀態,這是因為 systemd 不能通過上下文信息、特徵、配置文件得知具體使用哪個用戶運行該服務。在單元文件中增加 User
指令可以解決這個問題。
# minetest.service[Unit]Description= Minetest serverDocumentation= https://wiki.minetest.net/Main_Page[Service]Type= simpleUser= <username>ExecStart= /usr/games/minetest --serverExecStartPost= /home/<username>/bin/mtsendmail.sh "Ready to rumble?" "Minetest Starting up"TimeoutStopSec= 180ExecStop= /home/<username>/bin/mtsendmail.sh "Off to bed. Nightie night!" "Minetest Stopping in 2 minutes"ExecStop= /bin/sleep 120ExecStop= /bin/kill -2 $MAINPID
systemd 從 User
指令中得知應使用哪個用戶的環境變數來正確運行該服務。你可以使用 root 用戶,但這可能產生安全風險;使用你的個人用戶會好一些,但不少管理員的做法是為服務單獨創建一個用戶,這樣可以有效地將服務與其它用戶和系統組件相互隔離。
下一步我們讓你的服務在系統啟動時自動啟動,系統關閉時自動關閉。要達到這個目的,你需要 啟用 你的服務;但在這之前,你還需要告知 systemd 從哪裡 安裝 它。
對於 systemd 而言,安裝 意味著告知 systemd 在系統啟動的具體哪個步驟激活你的服務。以通用 Unix 列印系統(cups.service
)為例,它的啟動在網路框架啟動之後、其它列印服務啟動之前。又如,minetest.server
需要使用用戶郵件(及其它組件),需要等待網路和普通用戶對應的服務就緒後才可啟動。
你只需要在單元文件中添加一個新段和新指令:
...[Install]WantedBy= multi-user.target
你可以將其理解為「等待多用戶系統的全部內容就緒」。systemd 中的「目標」類似於舊系統中的運行級別,可以用於將主機轉移到一個或另一個狀態,也可以像本例中這樣讓你的服務等待指定狀態出現後運行。
你的最終 minetest.service
文件如下:
# minetest.service[Unit]Description= Minetest serverDocumentation= https://wiki.minetest.net/Main_Page[Service]Type= simpleUser= <username>ExecStart= /usr/games/minetest --serverExecStartPost= /home/<username>/bin/mtsendmail.sh "Ready to rumble?" "Minetest Starting up"TimeoutStopSec= 180ExecStop= /home/<username>/bin/mtsendmail.sh "Off to bed. Nightie night!" "Minetest Stopping in 2 minutes"ExecStop= /bin/sleep 120ExecStop= /bin/kill -2 $MAINPID[Install]WantedBy= multi-user.target
在嘗試新的服務之前,你還需要對郵件腳本做一些調整:
#!/bin/bash# mtsendmailsleep 20echo $1 | mutt -F /home/<username>/.muttrc -s "$2" my_minetest@mailing_list.comsleep 10
這是因為系統需要一定的時間啟動郵件系統(這裡等待 20 秒),也需要一定時間完成郵件發送(這裡等待 10 秒)。注意腳本中的等待時間數值適用於我的系統,你可能需要針對你的系統調整數值。
大功告成啦。執行如下操作:
sudo systemctl enable minetest
你的 Minetest 服務將在系統啟動時自動啟動,在系統關閉時友好關閉並通知你的用戶。
總結
事實上 Debian、 Ubuntu 和一些族類的發行版提供了 minetest-server
這個特別的軟體包,可以提供一部分上述功能,(但不包括郵件通知功能)。即使如此,你還是可以建立你獨有的自定義服務;事實上,你目前建立的服務比 Debian 默認提供的服務更加通用,可以提供更多功能。
更進一步的說,我們這裡描述的流程可以讓你將大多數簡單伺服器轉換為服務,類型可以是遊戲、網站應用或其它應用。同時,這也是你名副其實地踏入 systemd 大師殿堂的第一步。
via: https://www.linux.com/blog/learn/2018/5/systemd-services-beyond-starting-and-stopping
作者:Paul Brown 選題:lujun9972 譯者:pinewall 校對:wxy
本文由 LCTT 原創編譯,Linux中國 榮譽推出
推薦閱讀: