• 运维排查 | Systemd 之服务停止后状态为 failed


    哈喽大家好,我是咸鱼。

    我们知道 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    可以看到,配置文件分成几个区块,每个区块包含若干条键值对。

    [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
    
    • 1
    • 2

    上面的结果表示,默认的启动 Target 是 multi-user.target。在这个组里的所有服务,都将开机启动。这就是为什么 systemctl enable 命令能设置开机启动的原因。

    编写好 service 文件之后,我们执行下面的命令来启动 zookeeper:

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

    接着看下 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.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    可以看到,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)
    
    • 1
    • 2
    • 3

    但是主进程退出时返回的状态码却是 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
    
    • 1
    • 2
    • 3
    • 4
    • 5

    奇怪,明明 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
        ;;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    没有发现有什么不妥,接着我们注释掉 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.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    [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
    
    • 1
    • 2
    • 3
    • 4

    还是一样的问题,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
    ...
    
    • 1
    • 2
    • 3
    • 4

    表示当服务进程以状态码 143 正常退出时,systemd 将其视为成功退出而不是异常退出。

  • 相关阅读:
    gitlab对/api/v4/可泄露敏感信息处理
    LearnOpenGL - Android OpenGL ES 3.0 基础特效实现
    XPS测试中CHN含量为什么测不准?-科学指南针
    MQTT搭建(Windows和Linux)版
    Java中级面试题记录(四)
    Java数据结构—链表与LinkedList
    可视化3个10分类
    4.1保护模式
    【封装UI组件库系列】全局样式的定义与重置
    vue节流防抖-代码
  • 原文地址:https://blog.csdn.net/s_alted/article/details/137243924