Systemd

我可以配置或啟用什麼來確定 systemd 對服務採取“停止”操作的原因?

  • April 10, 2021

我們有一個啟動第三方代理的 systemd 服務單元;稱之為“服務c”。服務單元執行正常——至少,據我所知!在一個更新檔週期之後,systemd 會啟動這個服務單元(如預期的那樣),但是它會在成功啟動它大約兩秒後轉身並*停止服務單元。*我完全有理由相信該服務第一次成功啟動。重啟後登錄,可以看到服務確實沒有執行;那時,我可以手動啟動服務單元(systemctl start service-c),它會按預期啟動服務。

我想知道為什麼 systemd 認為它應該停止服務單元。我可以配置或啟用什麼來確定 systemd 為何採取“停止”操作?

我知道systemd LogLevel 選項並已將其設置為“調試”,而不是預設的“資訊”。

類似的構想是Environment=SYSTEMD_LOG_LEVEL=debug在service unit文件中設置,不過我不是特別需要調試的service,而是systemd本身。

服務單元配置為:

# /etc/systemd/system/service-c.service
[Unit]
Description=service c
After=network-online.target local-fs.target

[Service]
Type=forking
ExecStart=/local-path/start.service-c
ExecStop=/local-path/stop.service-c
Restart=on-failure

[Install]
WantedBy=multi-user.target

…證據是:

$ systemctl status service-c
● service-c.service - service c
  Loaded: loaded (/etc/systemd/system/service-c.service; enabled; vendor preset: disabled)
  Active: inactive (dead) since Wed 2021-04-07 17:49:30 EDT; 14h ago
 Process: 3162 ExecStop=/local-path/stop.service-c (code=exited, status=0/SUCCESS)
 Process: 1319 ExecStart=/local-path/start.service-c (code=exited, status=0/SUCCESS)
Main PID: 1478 (code=exited, status=0/SUCCESS)

/local-path是系統上本地目錄的混淆版本。

由於這是一個持續存在的問題,因此在上次重新啟動後,我檢測了“停止”包裝腳本以記錄程序父樹(使用pstree -a -A -l -p -s $$));該日誌文件顯示:

04/07/2021 17:49:19  stop.service-c:  
systemd,1 --switched-root --system --deserialize 22
 `-stop.service-c,3162 /local-path/stop.service-c
     `-pstree,3178 -a -A -l -p -s 3162

… 其中 PID 3162 對應於 systemd 對停止腳本的呼叫。這在我看來就像 systemd 正在為服務呼叫 ExecStop。

systemd 在完成啟動後大約兩秒鐘停止此服務;代理的日誌文件具有以下時間戳:

04/07/2021 17:49:12  start.service-c:  Starting agent
04/07/2021 17:49:17  start.service-c:  startup success
04/07/2021 17:49:19  stop.service-c:  Executing from /agent/home as user

……結尾……

04/07/2021 17:49:30  stop.service-c:  Finished with RC=0

…這對應於 systemd 的“死亡”時間戳 17:49:30。

“Restart=on-failure”指令將重新啟動服務,但 systemd 告訴我服務已成功啟動:

Apr 07 17:49:10 hostname systemd[1]: Starting service c...
Apr 07 17:49:17 hostname systemd[1]: Started service c.

由於服務乾淨地啟動,並且由於 systemd 沒有嘗試重新啟動服務,我不認為 Restart 參數正在發揮作用。

也許有趣的是,journalctl 中沒有相應的“正在停止服務 c…”日誌(就像我手動停止服務時一樣),但證據表明 systemd 呼叫了 ExecStop。

我目前正在執行 systemd 219。

我想知道為什麼 systemd 認為它應該停止服務單元。我可以配置或啟用什麼來確定 systemd 為何採取“停止”操作?

為了查看服務的實時狀態,您可以:

  • 使用systemd-cgls -l <service-cgroup-path>命令:在那裡您將看到當時所有服務的程序。可以使用systemctl show -p ControlGroup <service-name>命令檢索服務的 cgroup 路徑。在較新的版本systemd(不在 v219 中)中,您還可以使用方便的-u <service-name>選項來systemd-cgls代替服務的 cgroup 路徑
  • 要獲得詳細資訊,您可以使用非常詳細的systemctl show <service-name>命令:這將提供大量有關服務狀態的systemd資訊

要調查“可疑停止”情況,將這些命令添加為ExecStop命令是正確的。您可以簡單地將它們添加到您自己的腳本的開頭stop.service-c(如果它確實是一個腳本)。

或者,您也可以在命令之前將它們作為附加ExecStop命令添加,如下所示:stop.service-c

[Service]
Type=forking
ExecStart=/local-path/start.service-c
ExecStop=-/bin/sh -c 'systemd-cgls -l -u %n && systemctl show %n'
ExecStop=/local-path/stop.service-c
Restart=on-failure

請注意,當它出現在帶引號的字元串中時,它也會%n被正確處理。systemd

或者,您也可以:

[Service]
Type=forking
ExecStart=/local-path/start.service-c
ExecStop=-/usr/bin/systemd-cgls -l -u %n
ExecStop=-/bin/systemctl show %n
ExecStop=/local-path/stop.service-c
Restart=on-failure

還要注意-命令的前綴,以便忽略它們的退出狀態,以防它們因深不可測的原因而失敗。

當然,您也可以將它們用作ExecStartPost命令,以便在服務被認為“成功啟動”後立即思考實時狀態systemd。(再次 make 忽略它們的退出狀態,或者systemd如果它們失敗將關閉整個服務)。

關於systemd-cgls的輸出 run as ExecStopcommand ,你應該注意MainPID那個時候程序是否仍然出現:如果它確實出現了,那麼它證明ExecStop確實已經systemd按照你的建議自主執行了。否則(如果MainPID程序在“停止”時沒有出現在systemd-cgls’ 的輸出中),則表示該ExecStop程序已作為程序自行退出的結果而執行。(有關其他推理,請參見下文)。您可能還需要注意服務程序的 PID 編號以及(現已死亡的)命令的 PID 編號,以嘗試推斷出什麼MainPID``ExecStart``fork(2)-ing 自服務啟動以來一直在進行,因為這與服務非常相關,type=forking以便評估它是否表現良好。(有關其他推理,請參見下文)

關於systemctl show’s output run as command ,我想說在您的特定情況下ExecStop要注意的最相關的屬性是:

  • MainPID: 讀取0服務的主程序是否自行退出,否則讀取服務的主程序的 PID,如果它還活著並且確實被停止systemd
  • ExecMainExitTimestamp: 如果服務的主程序已自行退出,則以格式讀取退出時間date,否則如果程序仍處於活動狀態並且確實被停止,則根本不讀取systemd
  • ExecMainExitTimestampMonotonic:如上,但讀取 Linux 的單調時鐘並讀取0程序是否還活著
  • ExecMainCode:這對應於1code=中的字元串,僅報告符號的十進制值而不是它們的英文翻譯:根據 Linux 目前的符號值(這是從 開始),該欄位讀取程序是否仍然存在因此確實即將被 停止,否則讀取程序是否已經自行 -ed,如果它已經被-ed(在這個案例中顯然不是by ),依此類推systemctl status``CLD_*``CLD_*``enum``1``ExecMainCode``0``systemd``1``_exit(2)``2``kill(2)``systemd

但是請注意,如果在服務啟動時無法檢測到服務的主程序,則上述欄位與服務的目前狀態對應。(解釋見下文)。它們寧願對應能夠完全完成檢測的最近執行。systemd``systemd


進一步的見解

在您的推理中,我可以看到兩個值得額外澄清的關鍵點:

type=forking服務

type=forkingservices 對於 來說特別棘手systemd,尤其是在使用時GuessMainPID=yes(預設值,因此您目前正在為您的代理使用什麼)。對於這些服務類型,該ExecStart命令預計會fork(2)自行執行一次,然後退出,而其分叉的程序預計會隨著服務的執行而長壽和繁榮MainPID。別的:

  1. 如果這樣的分叉程序寧可再次分叉然後也退出,將作為實際服務的責任委託給它自己的“第二個”分叉程序,則GuessMainPID只會迷失方向,systemd只是認為服務已經定期自發地完成,因此完成清理所有內容(即執行ExecStop等)的職責,但記錄Stopping service...消息,因為就systemd目前而言,它只對服務的故意退出做出反應
  2. 相反,如果ExecStart 原始程序fork(2)在退出之前 s 兩次(或更多),則在原始程序最終退出時GuessMainPID投降並systemd限制將所有內容拆除。這是一個更好的情況,因為服務的實際程序仍然存在,但它還不理想,因為這樣也不會完全跟踪事件,因此至少會導致例如不一致/不完整的日誌日誌。ExecStart systemd

ExecStop執行

這些ExecStop命令也會MainPID程序自行成功退出時執行,只要主程序也已成功啟動(這是您手頭的情況)。我知道這似乎違反直覺,但這只是正常的行為systemd:它只是將服務的ExecStop命令視為在該服務之後進行清理的首選方式,然後再訴諸(預設情況下,請參閱systemd.kill(5))先發送 SIGTERM,然後可能再發送 SIGKILL。

它沒有在systemd.service(5)手冊頁中的任何地方明確說明,但可以通過一些文件來推斷,尤其是那些關於Exec*命令可用的環境變數的文件。查看和變數$SERVICE_RESULT$EXIT_CODE``$EXIT_STATUSExecStop了解它們可以採用什麼值,它們具有什麼語義意義,以及它們對 和 命令精確可用的事實ExecStopPost

除了非顯式(或個人解釋)文件之外,讓我們看看執行該行為的來源。從 v219 開始,這裡service_sigchld_event()呼叫service_enter_running()了一個事件,該事件引用了一個已知處於“執行”狀態的子程序,然後後一個函式在所有情況下呼叫service_enter_stop()RemainAfterExit=yes“停止”操作,除非type=dbus服務的主程序沒有被檢測到*(見type=forking上面的解釋)*或對照組不健康。

至於為什麼人們systemd決定這樣做,我不知道,因為我不是systemd開發人員,但我可以看到這種行為的有用性,以便為所有仍然存在但“未知”的服務程序提供機會以最好的方式通知他們即將終止,然後按照systemd最後的手段得到嚴厲的 SIGTERM 和 SIGKILL ,因為它繼續關閉整個控制組。這個措施對type=forking服務特別有用,因為這些服務最難systemd正確追踪,如type=段落中所解釋的systemd.service(5),並且因為systemd試圖清理遺留/延遲/實施不佳的服務,這些服務在退出之前沒有正常關閉。

高溫高壓


1.code=後面跟一個代表程序“退出原因”的詞:無論是,exited還是曾經killedtrapped甚至是dumped;在實踐中:字面意思是翻譯對欄位有效的各種CLD_*siginfo_t.si_code``sigaction(2)

引用自:https://unix.stackexchange.com/questions/644160