Linux 多進程服務配置 systemd
整個項目由多個進程共同運行,現在需要一個可靠的保活機制,以便能夠在進程崩潰的時候能夠快速把它拉起來。有什么想法呢?最直觀的解決方案無非就是寫個保活腳本,在后台一直運行,如果發現某進程被關閉了,那么由腳本拉起來,但是腳本它自己掛掉怎么辦?(總不能使用腳本繼續保活保活腳本套娃吧)。此外,另一個辦法就是配置出來一個服務,讓Linux
操作系統幫你守護進程,顯然,這種辦法完全不需要擔心守護進程自己掛掉,畢竟是systemd
幫你守護,如果它掛掉了,操作系統應該也沒了。
sysvinit和systemd
sysvinit
和systemd
分別是兩個Linux
操作系統的初始化系統,Linux
操作系統的服務就是它們運行起來的。在centos7
之前使用的是sysvinit
,centos7
及之后使用的是systemd
。此外還有ubuntu
的UpStart
,不過新版的Ubuntu
也使用systemd
了。
比較明顯的特征就是,舊版的linux
使用service httpd start
啟動服務,新版的linux
使用systemctl start httpd
來啟動服務,此外使用initd
作為初始化系統的操作系統添加服務是在/etc/init.d/
中添加腳本,而使用systemd
作為初始化系統的操作系統只需要在/etc/systemd/system/
文件夾中添加配置文件就好了。
具體和systemd
相關的介紹可以看這里:淺析init和systemd【圖文】_babylater_51CTO博客
一般來說,為了保持系統ABI
的兼容性,系統的systemd
版本不會特別激進,所以需要老一點文檔,比如CentOS7
運行的systemd
對應的版本文檔應該是這個systemd.service (archive.org)
多進程保活
首先最官方的文檔在這里:systemd.service (www.freedesktop.org)
不過需要注意的是,需要注意操作系統上面使用的systemd
的版本,通過執行systemd --version
看到,比如我這個版本就是systemd 219
,而最新的文檔是systemd 250
(這個可以在上面url
頁面的右上角看到)
但是,如果正在使用的systemd
的版本和文檔版本相差太大,可能會出現不准確的情況。比如在systemd 230
之前是沒有StartLimitInterSec
這個選項的。此時就需要這個網站https://web.archive.org/,它會給網頁拍攝快照,相當棒。此外,[systemd.unit 中文手冊 金步國] (jinbuguo.com)這位譯者翻譯了相關文檔,不過也請注意文檔版本的問題。
創建配置文件(設定重試次數)
配置文件需要創建在/etc/systemd/system/
文件夾里面,比如一個配置文件如下,文件名/etc/systemd/system/program.service
[Unit]
Description=program's service
[Service]
Type=idle
Environment=LD_LIBRARY_PATH=.:/export/home/admin/usr/install/gcc940/lib
ExecStart=/export/home/admin/gamed/program/bin/program gamesys.conf 1
Restart=always
RestartSec=5
StandardOutput=null
StandardError=null
StartLimitInterval=40000000
StartLimitBurst=3
[Install]
WantedBy=multi-user.target
新版本的系統(
systemd 230
之后),沒有StartLimitInterval
需要使用StartLimitIntervalSec
,並且StartLimitIntervalSec
和StartLimitBurst
兩個項在[Unit]
節區!!!詳情請通過https://web.archive.org/查詢和系統systemd
版本對應的文檔
然后通過systemctl start program.service
啟動服務,通過systemctl status program.service
查看服務狀態和啟動失敗原因,通過systemctl stop program.service
關閉服務。
首先一個服務的配置文件是.service
作為后綴的,結構類似於windows
的.ini
配置文件,其中一般由三個部分。分別是
[Unit]
:包含與單元類型無關的通用信息[Service]
:其中是服務的屬性[Install]
: 該小節包含單元的啟用信息。 事實上,systemd(1) 在運行時並不使用此小節。 只有 systemctl(1) 的 enable 與 disable 命令在啟用/停用單元時才會使用此小節(個人理解:決定在systemd
處於什么目標狀態下有效)
其中有多個選項,例如上面的配置文件,含義分別如下
-
Description
是作為服務的描述 -
Type
表示服務的類型,可以是simple exec forking oneshot dbus notify
,一般使用simple
-
Environment
可以指定服務會用到的環境變量 -
ExecStart
是通過systemctl start xxx
啟動服務時執行的命令- 需要注意的是,實測這里不能直接指定
>
重定向到文件,如有需要見下面StandardOutput
- 需要注意的是,實測這里不能直接指定
-
Restart
表示重啟的條件,可以取no
,on-success
,on-failure
,on-abnormal
,on-watchdog
,on-abort
,always
之一 -
RestartSec
如果重啟,兩次重啟之間的間隔 默認是100ms
-
StandardOutput
標准輸出,老版本(比如Systemd 219
)不能直接指定文件,新版本可以這樣指定文件file:/var/log/xxx.log
-
StandardError
同上不過是標准錯誤 -
StartLimitInterval
和StartLimitBurst
這兩個表示在StartLimitInterval
指定的時間里重試失敗StartLimitBurst
次則放棄重試,- 可以通過
systemctl reset-failed
重置失敗次數, - 需要注意的是,手動停止服務也會被計數,並且如果設定上面兩個參數,失敗后必須手動重置
- 再者,
Systemd 230
之后使用StartLimitIntervalSec
替代了StartLimitInterval
這里很坑...所以需要注意文檔版本 - 新版本這個選項是在
Unit
節區
- 可以通過
-
WantedBy
它的值是一個或多個Target
,當前Unit
激活時(enable
)符號鏈接會放入/etc/systemd/system
目錄下面以Target
名 +.wants
后綴構成的子目錄中(這里放一個我自己的理解,
systemd
有類似於initd
的運行級別的東西,詳見淺析init和systemd【圖文】_babylater_51CTO博客 的Sysvinit 運行級別和 systemd 目標的對應表
,所以WantedBy
設置為multi-user.target
的含義就比較明確了,也即是systemdctl enable xx.service
之后,在對應目標狀態下啟用)
具體的含義見systemd.service (www.freedesktop.org)
以上配置能夠實現的效果就是進程因為任何方式停止運行(除了手動stop
),都會觸發重啟,但是在4000000
秒之內只重啟3
次,重試三次不成功,就不再嘗試,此時並不能直接systemctl start program
,需要首先systemctl reset-failed
(重置失敗計數)之后才能再次start
如果不使用
StartLimitIntervalSec
和StartLimitBurst
就不需要考慮systemctl reset-failed
的使用了
多進程服務管理
如果需要創建很多服務,但是服務的配置文件只有ExecStart
項有細微區別,那么可以考慮使用模板功能。
比如,創建服務文件/etc/systemd/system/ping@.service
[Unit]
Description=Ping service %i
[Service]
Type=simple
ExecStart=/usr/bin/ping %i
[Install]
WantedBy=multi-user.target
啟動進程可以這樣systemctl start ping@127.0.0.1.service
,實際上,啟動服務可以省略.service
后綴,也即是systemctl start ping@127.0.0.1
,如果要一次啟動多個服務,可以systemctl start ping@127.0.0.1 ping@127.0.0.2
,停止服務也是類似。
這樣,如果想要ping
別的地址,只需要修改命令中@
之后的字符串就好了。
也支持如下這樣拼接字符串
[Unit]
Description=Ping service %i
[Service]
Type=simple
ExecStart=/usr/bin/ping 127.0.0.%i
[Install]
WantedBy=multi-user.target
systemctl start ping@1
就能執行ping 127.0.0.1
服務
對於配置文件中的%i
其實是有大小寫區別的,%i
是轉義之后的字符串 %I
是不轉義的字符串,對於完整的說明符列表,見systemd.unit (www.freedesktop.org)
鏈式啟動(服務依賴)
有這么一種情況,需要同時啟動多個服務,並且他們有啟動順序的限制。那么可以像下面這么配置
假設有 A進程
、 B進程
、 C進程
,想要按順序依次啟動,那么可以這么配置
/etc/systemd/system/C.service
[Unit]
Description=C Process
Requires=B.service
After=B.service
[Service]
Type=simple
ExecStart=/export/CProgram
[Install]
WantedBy=multi-user.target
/etc/systemd/system/B.service
[Unit]
Description=B Process
Requires=A.service
After=A.service
[Service]
Type=simple
ExecStart=/export/BProgram
[Install]
WantedBy=multi-user.target
/etc/systemd/system/A.service
[Unit]
Description=A Process
[Service]
Type=simple
ExecStart=/export/AProgram
[Install]
WantedBy=multi-user.target
效果是,systemctl start C.service
之后幾個進程會依次啟動,Requires
指定了幾個服務之間的依賴關系,因為通過After
選擇指定了服務間啟動順序,所以幾個服務是依次啟動的。如果沒有After
,啟動順序不被保證
如果,此時systemctl stop A.service
,那么幾個服務都會被關閉,因為Requires
要求了前置服務必須存在,否則自身也不應該啟動。如果不想自身服務被關閉,那么可以把Requires
(要求)替換成Wants
(想要)。
如果要形成依賴鏈,除了After
也可以使用Before
完整說明參見systemd.unit (www.freedesktop.org)
指定關閉進程方式 - ExecStop
可以通過ExecStop
選項關閉由ExecStart
啟動的服務,因為有些程序需要發送特定的信號才能安全退出,所以這個選項會很有用。而對於其他被主進程拉起來的進程,按照KillMode
的設置處理,默認情況下,停止服務會關閉主進程以及主進程啟動的所有子進程。
而對於KillMode
有如下幾個設置,分別是
control-group
:會干掉主進程及子進程這是默認選項
mixed
:SIGTERM
信號被發送到主進程,而隨后的SIGKILL
信號被發送到單元控制組的所有剩余進程,可以通過KillSignal
設置關閉主進程的信號process
: 僅關閉主進程none
: 什么也不干
詳情見:systemd.kill (www.freedesktop.org)
例如,如果要事先約定某進程需要發送SIGUSR1
信號才能安全結束,那么可以在[Service]
節區設定ExecStop=kill -SIGUSR1 $MAINPID
。
上文
$MAINPID
類似於環境變量,表示主進程的pid
,完整列表見[systemd.exec (www.freedesktop.org)](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Environment Variables Set or Propagated by the Service Manager)
查看服務輸出 - journalctl
systemd
不僅用來運行服務,它同時也有日志服務,用於取代老系統的syslog
。
運行的服務標准輸出和錯誤輸出會被交給journald
管理,查看某個服務可以使用這樣的命令journalctl -u ping@1
帶-e
參數可以跳到最新一行 -f
參數可以看到實時輸出,-n
參數可以指定輸出的行數,-r
反序輸出。
例如journalctl -u ping@1 -e
或者 journalctl -u ping@1 -f
如果服務的輸出太多,那么可以在.servive
文件中的[Unit]
節區配置StandardOutput=null
也可以通過systemctl status xx.service
查看服務的部分輸出
具體使用參考