運維排查 | 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
  • 隨著Aspire發佈preview5的發佈,Microsoft.Extensions.ServiceDiscovery隨之更新, 服務註冊發現這個屬於老掉牙的話題解決什麼問題就不贅述了,這裡主要講講Microsoft.Extensions.ServiceDiscovery(preview5)以及如何 ...
  • 概述:通過使用`SemaphoreSlim`,可以簡單而有效地限制非同步HTTP請求的併發量,確保在任何給定時間內不超過20個網頁同時下載。`ParallelOptions`不適用於非同步操作,但可考慮使用`Parallel.ForEach`,儘管在非同步場景中謹慎使用。 對於併發非同步 I/O 操作的數量 ...
  • 1.Linux上安裝Docken 伺服器系統版本以及內核版本:cat /etc/redhat-release 查看伺服器內核版本:uname -r 安裝依賴包:yum install -y yum-utils device-mapper-persistent-data lvm2 設置阿裡雲鏡像源:y ...
  • 概述:WPF界面綁定和渲染大量數據可能導致性能問題。通過啟用UI虛擬化、非同步載入和數據分頁,可以有效提高界面響應性能。以下是簡單示例演示這些優化方法。 在WPF中,當你嘗試綁定和渲染大量的數據項時,性能問題可能出現。以下是一些可能導致性能慢的原因以及優化方法: UI 虛擬化: WPF提供了虛擬化技術 ...
  • 引言 上一章節介紹了 TDD 的三大法則,今天我們講一下在單元測試中模擬對象的使用。 Fake Fake - Fake 是一個通用術語,可用於描述 stub或 mock 對象。 它是 stub 還是 mock 取決於使用它的上下文。 也就是說,Fake 可以是 stub 或 mock Mock - ...
  • 為.net6在CentOS7上面做準備,先在vmware虛擬機安裝CentOS 7.9 新建CentOS764位的系統 因為CentOS8不更新了,所以安裝7;簡單就一筆帶過了 選擇下載好的操作系統的iso文件,下載地址https://mirrors.aliyun.com/centos/7.9.20 ...
  • 經過前面幾篇的學習,我們瞭解到指令的大概分類,如:參數載入指令,該載入指令以 Ld 開頭,將參數載入到棧中,以便於後續執行操作命令。參數存儲指令,其指令以 St 開頭,將棧中的數據,存儲到指定的變數中,以方便後續使用。創建實例指令,其指令以 New 開頭,用於在運行時動態生成並初始化對象。方法調用指... ...
  • LiteDB 是一個輕量級的嵌入式 NoSQL 資料庫,其設計理念與 MongoDB 類似,但它是完全使用 C# 開發的,因此與 C# 應用程式的集成非常順暢。與 SQLite 相比,LiteDB 提供了 NoSQL(即鍵值對)的數據存儲方式,並且是一個開源且免費的項目。它適用於桌面、移動以及 We ...
  • 1 開源解析和拆分文檔 第三方的工具去對文件解析拆分,去將我們的文件內容給提取出來,並將我們的文檔內容去拆分成一個小的chunk。常見的PDF word mark down, JSON、HTML。都可以有很好的一些模塊去把這些文件去進行一個東西去提取。 優勢 支持豐富的文檔類型 每種文檔多樣化選擇 ...
  • OOM是什麼?英文全稱為 OutOfMemoryError(記憶體溢出錯誤)。當程式發生OOM時,如何去定位導致異常的代碼還是挺麻煩的。 要檢查OOM發生的原因,首先需要瞭解各種OOM情況下會報的異常信息。這樣能縮小排查範圍,再結合異常堆棧、heapDump文件、JVM分析工具和業務代碼來判斷具體是哪 ...