如何讓 systemd 正確管理帶有後台程序的腳本?
我有一個 shell 腳本,它在後台執行三個程序,在前台執行幾個程序,然後是
trap
andwait
,並且我已經設置了一個單元文件,因此systemd
可以啟動它並在它失敗時重新啟動它。然而,我發現,如果一個程序死了,它不會殺死該腳本中的所有內容並重新啟動它。對於此應用程序,如果其中任何一個死亡,則必須重新啟動它們。
我看到了兩條合理的路徑:
- 配置單元文件並可能更改腳本,以便它檢測到異常並殺死它們,重新執行腳本。我不知道該怎麼做。
- 將三個後台程序中的每一個配置為具有單獨
.service
文件的獨立單元。但是我不知道如何編寫.service
文件以在其中任何一個失敗時殺死並重新啟動它們。我知道我可以安排他們的依賴關係,以便他們按順序開始,但我不知道如何讓它在 #2 死亡時殺死 #1,反之亦然。我不想寫一個經理,或者讓程序自己解決問題並自己死去——這就是
systemd
目的——我希望我只是錯過了正確的咒語。.服務文件:
[Unit] Description=Foobar Interface After=network.target [Service] Type=simple WorkingDirectory=/home/user/scripts ExecStart=/home/user/scripts/myscript.sh Restart=always [Install] WantedBy=multi-user.target
bash 腳本:
#!/usr/bin/env bash tty_port=/dev/ttyUSB0 #Clean up any old running processes pkill -f "cat ${tty_port}" pkill transport pkill backgroundprogram #Configure the target source /home/user/somescript.sh foregroundprogram #Set up the serial port stty -F $tty_port 115200 #Read from the port in the background cat $tty_port & tty_pid=$! #Wait for tty device to waken sleep 15 #Send commands to tty device echo "command1" > $tty_port sleep 1 echo "command2" > $tty_port sleep 1 #Start up the transport /home/user/transport &>> /dev/null & transport_pid=$! #Wait a bit for the transport to start sleep 1 #Start up the main process /home/user/backgroundprogram & background_pid=$! #Wait a bit for it to start sleep 1 #Finally, start the tty device echo "command3" > $tty_port trap "kill ${background_pid} ${tty_pid} ${transport_pid}; exit 1" INT wait
一切正常,寫入日誌等,但是當三個程序中的任何一個失敗時,它會繼續執行,不會殺死並重新啟動一切。
然而,我發現,如果一個程序死了,它不會殺死該腳本中的所有內容並重新啟動它。對於此應用程序,如果其中任何一個死亡,則必須重新啟動它們。
systemd 正在監視您的 shell 腳本,而不是它的子腳本。您不希望systemd 響應孩子的退出,因為這會導致您每次執行 command 時都重新啟動。考慮一下,如果我有一個執行的 shell 腳本……
date
我只是產生了一個子程序,它執行,然後退出。我不希望這觸發我的流程主管的任何行動。
如果您希望 systemd 監控子程序,則為每個程序創建一個單獨的單元文件:
- 一個用於配置和從串口讀取的單元
- 一為
/home/user/transport
- 一為
/home/user/backgroundprogram
您可以使用 systemd 依賴項來確保服務的正確啟動順序(並確保如果您停止一個服務,它們都會停止),並且您可以使用
EnvironmentFile
指令$tty_port
從文件中載入配置(如 )。您可能會將一些設置命令(“向 tty 設備發送命令…”)放在
ExecStartPre
一行中,或者他們可能會獲得自己的Type=oneshot
服務。
如果您可以將主腳本拆分為單獨的服務,則可以像這樣輕鬆解決它:
在下面的範例中,我擁有三個重生服務 s1、s2 和 s3,並通過目標 s.target 將它們作為一個組進行控制。
注意:
如果您
Requires
在 s.target 中配置這三個服務,那麼如果其中一個崩潰並重新生成,則該組中的所有參與程序都將重新啟動。或者,如果您將它們配置為
Wants
在 s.target 中,那麼如果其中一個崩潰並重新生成,則僅重新啟動該單個程序。為每個服務創建一個服務文件 s1, s2, s3:
/etc/systemd/system/s1.service :
[Unit] Description=my worker s1 After=network.target Before=foobar.service PartOf=s.target [Service] Type=simple ExecStart=/usr/local/bin/s1.sh Restart=always
(注意:如果您的服務相同,您可以創建一個s1@.service 文件而不是多個文件。在手冊中查找使用 @ 和 %i 的服務實例。)
現在創建需要 s1、s2 和 s3 服務的主要目標(組)文件:
/etc/systemd/system/s.target :
[Unit] Description=main s service Requires=s1.service s2.service s3.service # or # Wants=s1.service s2.service s3.service [Install] WantedBy=multi-user.target
完畢。
像往常一樣,您現在必須執行
systemctl daemon-reload
.現在您可以使用
systemctl start s.target
s1 啟動您的服務,s2 和 s3 已啟動。
您可以停止所有三個服務,同時
systemctl stop s.target
停止 s1、s2 和 s3。
當然,您可以像往常一樣啟動/停止/重新啟動/狀態各個服務:
systemctl status s1
如果您殺死 s1、s2 或 s3 程序,它將自動重生(Restart=always)。
如果使用
Requires
,則組中的所有程序都將重新啟動。PS:
systemctl enable s.target
如果要在啟動時啟動服務,請執行。PS:不幸的是,在使用 systemctl 時,您不能像使用“s1”那樣使用速記詞“s”來表示“s.target”,而不是輸入完整的“s1.service”。當你想管理組時,你必須輸入“s.target”。