運維排查 | Systemd 之服務停止後狀態為 failed

来源:https://www.cnblogs.com/edisonfish/p/18109224
-Advertisement-
Play Games

哈嘍大家好,我是鹹魚。 我們知道 CentOS 7 之後,Systemd 代替了原來的 SystemV 來管理服務,相比 SystemV ,Systemd 能夠很好地解決各個服務間的依賴關係,還能讓所有的服務同時啟動,而不是串列啟動。 通常情況下,yum 安裝的軟體會由系統的包管理器(如 RPM)安 ...


哈嘍大家好,我是鹹魚。

我們知道 CentOS 7 之後,Systemd 代替了原來的 SystemV 來管理服務,相比 SystemV ,Systemd 能夠很好地解決各個服務間的依賴關係,還能讓所有的服務同時啟動,而不是串列啟動。

通常情況下,yum 安裝的軟體會由系統的包管理器(如 RPM)安裝,並且會配置相應的 systemd 服務,因此由 systemd 來管理。然而,在一些情況下,特別是當採用源碼編譯安裝軟體或者軟體本身並沒有提供 systemd 管理的解決方案時,就需要手動編寫 systemd 服務文件(service 文件)來管理這些軟體。

那今天我們就來看看手動編寫 systemd 服務文件來管理軟體時發現的一些問題。

問題出現

我們的 zookeeper 是通過源碼編譯來安裝,為了方便管理,決定改成通過 systemd 來管理。

下麵是 zookeeper 的 service 文件

# zookeeper
[Unit]
Description=Zookeeper
After=network.target

[Service]
Type=forking
ExecStart=/opt/zookeeper/bin/zkServer.sh start
ExecStop=/opt/zookeeper/bin/zkServer.sh stop
PIDFile=/var/lib/zookeeper/zookeeper_server.pid

[Install]
WantedBy=multi-user.target

可以看到,配置文件分成幾個區塊,每個區塊包含若幹條鍵值對。

[Unit]

Unit 部分的 Description 欄位給出當前服務的簡單描述接下來的設置是啟動順序:

  1. After 欄位:表示 zookeeper.service 應該在哪些服務之後啟動。
  2. Before欄位,表示 zookeeper.service 應該在哪些服務之前啟動。

註意,AfterBefore欄位只涉及啟動順序,不涉及依賴關係。

[Service]

Service 部分定義如何啟動當前服務。

  • ExecStart:啟動進程時執行的命令。

  • ExecStop:停止服務時執行的命令。

  • Type:定義啟動類型

    1. simple(預設值):ExecStart欄位啟動的進程為主進程
    2. forking:ExecStart欄位將以fork()方式啟動,此時父進程將會退出,子進程將成為主進程
    3. oneshot:類似於simple,但只執行一次,Systemd 會等它執行完,才啟動其他服務
    4. dbus:類似於simple,但會等待 D-Bus 信號後啟動
    5. notify:類似於simple,啟動結束後會發出通知信號,然後 Systemd 再啟動其他服務
    6. idle:類似於simple,但是要等到其他任務都執行完,才會啟動該服務。一種使用場合是為讓該服務的輸出,不與其他服務的輸出相混合

[Install]

Install 部分定義如何安裝這個配置文件,即怎樣做到開機啟動

  • WantedBy:表示該服務所在的 Target。

Target 的含義是服務組,表示一組服務。WantedBy=multi-user.target指的是,kafka 和 zookeeper 所在的 Target 是 multi-user.target

這個設置非常重要,因為執行systemctl enable 命令時,zookeeper .service 的一個符號鏈接,就會放在/etc/systemd/system目錄下麵的multi-user.target.wants子目錄之中。

Systemd 有預設的啟動 Target。

[root@localhost ~]# systemctl get-default
multi-user.target

上面的結果表示,預設的啟動 Target 是 multi-user.target。在這個組裡的所有服務,都將開機啟動。這就是為什麼 systemctl enable 命令能設置開機啟動的原因。

編寫好 service 文件之後,我們執行下麵的命令來啟動 zookeeper:

[root@localhost ~]# systemctl start zookeeper.service

接著看下 zookeeper 的運行狀態

[root@localhost ~]# systemctl status zookeeper.service       
● zookeeper.service - Zookeeper
   Loaded: loaded (/usr/lib/systemd/system/zookeeper.service; enabled; vendor preset: disabled)
   Active: active (running) since 一 2024-04-01 09:10:23 CST; 2s ago
  Process: 60955 ExecStop=/opt/zookeeper/bin/zkServer.sh stop (code=exited, status=0/SUCCESS)
  Process: 61116 ExecStart=/opt/zookeeper/bin/zkServer.sh start (code=exited, status=0/SUCCESS)
 Main PID: 61132 (java)
   CGroup: /system.slice/zookeeper.service
           └─61132 java -Dzookeeper.log.dir=/opt/zookeeper/bin/../logs -Dzookeeper.log.file=zookeeper--server-localhost.localdomain.log -Dzookeepe...

4月 01 09:10:22 localhost.localdomain systemd[1]: Starting Zookeeper...
4月 01 09:10:22 localhost.localdomain zkServer.sh[61116]: /usr/sbin/java
4月 01 09:10:22 localhost.localdomain zkServer.sh[61116]: ZooKeeper JMX enabled by default
4月 01 09:10:22 localhost.localdomain zkServer.sh[61116]: Using config: /opt/zookeeper/bin/../conf/zoo.cfg
4月 01 09:10:23 localhost.localdomain zkServer.sh[61116]: Starting zookeeper ... STARTED
4月 01 09:10:23 localhost.localdomain systemd[1]: Started Zookeeper.

active (running) 表示運行正常

當我們執行 systemctl stop zookeeper.service 命令停止 zookeeper 的時候,問題出現了

[root@localhost ~]# systemctl status zookeeper.service 
● zookeeper.service - Zookeeper
   Loaded: loaded (/usr/lib/systemd/system/zookeeper.service; enabled; vendor preset: disabled)
   Active: failed (Result: exit-code) since 一 2024-04-01 09:10:30 CST; 906ms ago
  Process: 61183 ExecStop=/opt/zookeeper/bin/zkServer.sh stop (code=exited, status=0/SUCCESS)
  Process: 61116 ExecStart=/opt/zookeeper/bin/zkServer.sh start (code=exited, status=0/SUCCESS)
 Main PID: 61132 (code=exited, status=143)

4月 01 09:10:23 localhost.localdomain systemd[1]: Started Zookeeper.
4月 01 09:10:29 localhost.localdomain systemd[1]: Stopping Zookeeper...
4月 01 09:10:29 localhost.localdomain zkServer.sh[61183]: /usr/sbin/java
4月 01 09:10:29 localhost.localdomain zkServer.sh[61183]: ZooKeeper JMX enabled by default
4月 01 09:10:29 localhost.localdomain zkServer.sh[61183]: Using config: /opt/zookeeper/bin/../conf/zoo.cfg
4月 01 09:10:29 localhost.localdomain systemd[1]: zookeeper.service: main process exited, code=exited, status=143/n/a
4月 01 09:10:30 localhost.localdomain zkServer.sh[61183]: Stopping zookeeper ... STOPPED
4月 01 09:10:30 localhost.localdomain systemd[1]: Stopped Zookeeper.
4月 01 09:10:30 localhost.localdomain systemd[1]: Unit zookeeper.service entered failed state.
4月 01 09:10:30 localhost.localdomain systemd[1]: zookeeper.service failed.

可以看到,zookeeper 服務在停止後並不是 inactive ,而是 failed 狀態,最後兩行輸出里有 Unit zookeeper.service entered failed state./zookeeper.service failed 欄位

問題定位

我們接著看上面的輸出,可以看到在設置了 Type=forking 後,服務在啟動或關閉時執行對應的腳本會開啟一個進程,並且兩個進程都成功執行了(返回狀態碼為 0 )。

  Process: 61183 ExecStop=/opt/zookeeper/bin/zkServer.sh stop (code=exited, status=0/SUCCESS)
  Process: 61116 ExecStart=/opt/zookeeper/bin/zkServer.sh start (code=exited, status=0/SUCCESS)
  Main PID: 61132 (code=exited, status=143)

但是主進程退出時返回的狀態碼卻是 143,而不是狀態碼 0。

接著看下 zookeeper 進程還在不在:

[root@localhost ~]# jps -l
61287 sun.tools.jps.Jps

[root@localhost ~]# ps -ef | grep zookeeper
root      61300  61250  0 09:49 pts/0    00:00:00 grep --color=auto zookeeper

奇怪,明明 zookeeper 進程已經成功退出了,但是 systemd 卻說它退出失敗

此時我註意到儘管在停止服務時,狀態碼為 0,但也只是表明執行 /opt/zookeeper/bin/zkServer.sh stop 命令本身成功完成,這個狀態碼並不代表腳本內部的執行邏輯一定是成功的。

我們看下 zkServer.sh 腳本中關於 stop 的邏輯

stop)
    echo -n "Stopping zookeeper ... "
    if [ ! -f "$ZOOPIDFILE" ]
    then
      echo "no zookeeper to stop (could not find file $ZOOPIDFILE)"
    else
      $KILL  $(cat "$ZOOPIDFILE")
      rm "$ZOOPIDFILE"
      sleep 1
      echo STOPPED
    fi
    exit 0
    ;;

沒有發現有什麼不妥,接著我們註釋掉 ExecStop 欄位,採用 systemd 預設的方式來停止服務。

預設情況下,systemd 將向進程發送 SIGTERM 信號(相當於 kill 命令發送的終止信號),等待一段時間後,如果服務進程未正常退出,則發送 SIGKILL 信號(相當於 kill -9 命令發送的強制終止信號)強制終止服務進程。

然後重新啟停一下 zookeeper ,看下狀態:

[root@localhost ~]# systemctl status zookeeper.service 
● zookeeper.service - Zookeeper
   Loaded: loaded (/usr/lib/systemd/system/zookeeper.service; enabled; vendor preset: disabled)
   Active: failed (Result: exit-code) since 一 2024-04-01 10:03:04 CST; 1s ago
  Process: 61453 ExecStart=/opt/zookeeper/bin/zkServer.sh start (code=exited, status=0/SUCCESS)
 Main PID: 61469 (code=exited, status=143)

4月 01 10:02:55 localhost.localdomain systemd[1]: Started Zookeeper.
4月 01 10:02:55 localhost.localdomain zkServer.sh[61453]: /usr/sbin/java
4月 01 10:02:55 localhost.localdomain zkServer.sh[61453]: ZooKeeper JMX enabled by default
4月 01 10:02:55 localhost.localdomain zkServer.sh[61453]: Using config: /opt/zookeeper/bin/../conf/zoo.cfg
4月 01 10:02:56 localhost.localdomain zkServer.sh[61453]: Starting zookeeper ... STARTED
4月 01 10:03:04 localhost.localdomain systemd[1]: Stopping Zookeeper...
4月 01 10:03:04 localhost.localdomain systemd[1]: zookeeper.service: main process exited, code=exited, status=143/n/a
4月 01 10:03:04 localhost.localdomain systemd[1]: Stopped Zookeeper.
4月 01 10:03:04 localhost.localdomain systemd[1]: Unit zookeeper.service entered failed state.
4月 01 10:03:04 localhost.localdomain systemd[1]: zookeeper.service failed.
[root@localhost ~]# jps -l
61524 sun.tools.jps.Jps
[root@localhost ~]# ps -ef | grep zookeeper
root      61537  61250  0 10:04 pts/0    00:00:00 grep --color=auto zookeeper

還是一樣的問題,zookeeper 已經成功退出但是卻顯示 failed 狀態,狀態碼是 143。

從上面我們得知:無論是通過 zkserver.sh 還是 systemd 預設方式來關閉服務,本質上都是向 zookeeper 進程發送 SIGTERM 信號(數值為 15 ),雖然 zookeeper 進程成功退出,但是 systemd 將此解釋為異常退出,因為預期的退出狀態碼為 0。

而根據 POSIX 規範:【因接收到信號而終止的命令的退出狀態應報告為大於 128】,所以被信號中斷的進程退出時會返回 128 加上信號數值作為退出狀態碼。

也就是說,當 zookeeper 進程收到 SIGTERM 信號時,會返回 128 + 15 也就是 143 作為退出狀態碼,這也就是為什麼進程在成功退出後 systemctl 顯示為 failed 狀態。

解決問題

既然知道了進程在退出時的狀態碼是 143 但是 systemd 不會解釋為成功,因為預期的退出狀態碼為 0,那麼我們只需要讓 systemd 把狀態碼 143 也解釋為成功就行。

所以在 zookeeper 的 service 文件中添加下麵的配置:

[Service]
...
SuccessExitStatus=143
...

表示當服務進程以狀態碼 143 正常退出時,systemd 將其視為成功退出而不是異常退出。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 實驗準備: 一臺dns1域控制器,一臺虛擬機登錄域用戶驗證設置 一:“財務處”用戶自動映射z盤 打開dns1,在c盤下新建share目錄,添加test文本文件 修改share目錄的共用為所有人 完成可以看到share目錄的網路路徑 在共用裡面複製網路路徑 打開組策略管理器 右鍵財務部GPO,點擊編輯 ...
一周排行
    -Advertisement-
    Play Games
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...