使用systemd ( systemctl ) 控制和托管zookeeper

网上有很多同学都贴过CentOS 7环境中使用systemd管理zookeeper,贴出来的systemd service配置文件也是五花八门,简单的抄到自己的环境里,大多都不能正常运行。本文按照需求描述,根据对zookeeper的启动文件和systemd.service的参数项的分析,来编写一个比较完整的zookeeper服务配置文件。

1. 需求描述:

使用systemd控制和托管zookeeper服务。

  1. 开机自动启动zookeeper
  2. zookeeper进程因故障退出后能自动重新启动
  3. 使用systemctl start|stop|restart|status zookeeper控制zookeeper启停。
  4. 使用普通账户运行zookeeper

本文zookeeper配置样例的环境条件,如下:

操作系统 CentOS 7
JAVA安装目录 /opt/java/jdk1.8.0_192-amd64
zookeeper安装目录 /opt/zookeeper
zookeeper配置文件 /opt/zookeeper/conf/zoo.cfg
zoo.cfg中的数据文件路径 # 快照(snapshot)文件路径
dataDir=/data/zookeeper/data
# 事务日志(datalog)文件路径
dataLogDir=/data/zookeeper/datalogs
pid文件 默认值,dataDir目录下zookeeper_server.pid,即/data/zookeeper/data/zookeeper_server.pid

2. 完整的zookeeper服务配置文件

先给出完整的zookeeper服务配置文件,方便一眼就懂的同学直接拿走。

[Unit]
Description=Zookeeper Service unit Configuration
After=network.target

[Service]
Type=forking
Environment=JAVA_HOME=/opt/java/jdk1.8.0_192-amd64
ExecStart=/opt/zookeeper/bin/zkServer.sh start /opt/zookeeper/conf/zoo.cfg
ExecStop=/opt/zookeeper/bin/zkServer.sh stop
PIDFile=/data/zookeeper/data/zookeeper_server.pid
KillMode=none
User=ibase
Group=ibase
Restart=on-failure
[Install]
WantedBy=multi-user.target

3. 操作步骤

/usr/lib/systemd/system目录中,创建zookeeper.service文件,填入以下内容:

[Unit]
Description=Zookeeper Service unit Configuration
After=network.target

[Service]
Type=forking
Environment=JAVA_HOME=/opt/java/jdk1.8.0_192-amd64
ExecStart=/opt/zookeeper/bin/zkServer.sh start /home/ibase/application/zookeeper-cmpv2/conf/zoo.cfg
ExecStop=/opt/zookeeper/bin/zkServer.sh stop
PIDFile=/data/zookeeper/data/zookeeper_server.pid
KillMode=none
User=ibase
Group=ibase
Restart=on-failure
[Install]
WantedBy=multi-user.target

执行以下命令重载unit配置文件

systemctl deamon-reload

将zookeeper服务加入开机启动项

systemctl enable zookeeper

执行systemctl enable zookeeper.service命令时,zookeeper.service的一个符号链接,就会放在/etc/systemd/system目录下面的multi-user.target.wants子目录之中。

使用systemctl命令启动zookeeper

systemctl start zookeeper

4. 启停测试

[root@n01 ~]# systemctl start zookeeper
[root@n01 ~]# systemctl status zookeeper -l
 zookeeper.service - Zookeeper
   Loaded: loaded (/usr/lib/systemd/system/zookeeper.service; disabled; vendor preset: disabled)
   Active: active (running) since 四 2020-09-03 17:36:43 CST; 3s ago
  Process: 19019 ExecStart=/opt/zookeeper/bin/zkServer.sh start /opt/zookeeper/conf/zoo.cfg (code=exited, status=0/SUCCESS)
 Main PID: 19029 (java)
    Tasks: 71
   CGroup: /system.slice/zookeeper.service
           └─19029 /usr/java/jdk1.8.0_192-amd64/bin/java -Dzookeeper.log.dir=/logs/zookeeper -Dzookeeper.root.logger=INFO,ROLLINGFILE -cp /opt/zookeeper/bin/../zookeeper-server/target/classes:/opt/zookeeper/bin/../build/classes:/opt/zookeeper/bin/../zookeeper-server/target/lib/*.jar:/opt/zookeeper/bin/../build/lib/*.jar:/opt/zookeeper/bin/../lib/slf4j-log4j12-1.7.25.jar:/opt/zookeeper/bin/../lib/slf4j-api-1.7.25.jar:/opt/zookeeper/bin/../lib/netty-3.10.6.Final.jar:/opt/zookeeper/bin/../lib/log4j-1.2.17.jar:/opt/zookeeper/bin/../lib/jline-0.9.94.jar:/opt/zookeeper/bin/../lib/audience-annotations-0.5.0.jar:/opt/zookeeper/bin/../zookeeper-3.4.14.jar:/opt/zookeeper/bin/../zookeeper-server/src/main/resources/lib/*.jar:/opt/zookeeper/bin/../conf: -server -Xms4096m -Xmx4096m -XX:MaxNewSize=256m -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.local.only=false org.apache.zookeeper.server.quorum.QuorumPeerMain /opt/zookeeper/conf/zoo.cfg

9月 03 17:36:42 n01.sers systemd[1]: Starting Zookeeper...
9月 03 17:36:42 n01.sers zkServer.sh[19019]: ZooKeeper JMX enabled by default
9月 03 17:36:42 n01.sers zkServer.sh[19019]: Using config: /opt/zookeeper/conf/zoo.cfg
9月 03 17:36:43 n01.sers zkServer.sh[19019]: Starting zookeeper ... STARTED
9月 03 17:36:43 n01.sers systemd[1]: Started Zookeeper.
[root@n01 ~]# systemctl stop zookeeper
[root@n01 ~]# systemctl status zookeeper -l
 zookeeper.service - Zookeeper
   Loaded: loaded (/usr/lib/systemd/system/zookeeper.service; disabled; vendor preset: disabled)
   Active: inactive (dead)

9月 03 17:36:02 n01.sers systemd[1]: Stopped Zookeeper.
9月 03 17:36:42 n01.sers systemd[1]: Starting Zookeeper...
9月 03 17:36:42 n01.sers zkServer.sh[19019]: ZooKeeper JMX enabled by default
9月 03 17:36:42 n01.sers zkServer.sh[19019]: Using config: /opt/zookeeper/conf/zoo.cfg
9月 03 17:36:43 n01.sers zkServer.sh[19019]: Starting zookeeper ... STARTED
9月 03 17:36:43 n01.sers systemd[1]: Started Zookeeper.
9月 03 17:36:55 n01.sers systemd[1]: Stopping Zookeeper...
9月 03 17:36:55 n01.sers zkServer.sh[19805]: ZooKeeper JMX enabled by default
9月 03 17:36:55 n01.sers zkServer.sh[19805]: Using config: /opt/zookeeper/bin/../conf/zoo.cfg
9月 03 17:36:55 n01.sers systemd[1]: Stopped Zookeeper.

5. 解释说明

5.1. zookeeper自身启停命令

在编写systemd Service unit Configuration文件之前,先看一下zookeeper自身启停命令,如下:

#启动命令,启动zookeeper进程
/bin/zkServer.sh start 
#停止命令,停止zookeeper进程
/bin/zkServer.sh stop
#重启命令,重新启动zookeeper进程
/bin/zkServer.sh restart 
#查看状态命令,显示zookeeper节点的角色,leader/follower
/bin/zkServer.sh status 

由于zookeeper是Java程序,因此运行启停命令前,需要设置JAVA_HOME环境变量。

zkServer.sh为shell脚本,接受start|stop|restart|status等常用参数。
start处理逻辑:

    echo  -n "Starting zookeeper ... "
    if [ -f "$ZOOPIDFILE" ]; then
      if kill -0 `cat "$ZOOPIDFILE"` > /dev/null 2>&1; then
         echo $command already running as process `cat "$ZOOPIDFILE"`. 
         exit 0
      fi
    fi
    nohup "$JAVA" "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \
    -cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT" 2>&1 < /dev/null &
    if [ $? -eq 0 ]
    then
      case "$OSTYPE" in
      *solaris*)
        /bin/echo "${!}\\c" > "$ZOOPIDFILE"
        ;;
      *)
        /bin/echo -n $! > "$ZOOPIDFILE"
        ;;
      esac
      if [ $? -eq 0 ];
      then
        sleep 1
        echo STARTED
      else
        echo FAILED TO WRITE PID
        exit 1
      fi
    else
      echo SERVER DID NOT START
      exit 1
    fi
    ;;

stop处理逻辑:

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

restart处理逻辑:

    shift
    "$0" stop ${@}
    sleep 3
    "$0" start ${@}
    ;;

5.2. systemd的Service unit Configuration文件配置项

可以通过在CentOS 7中通过man systemd.directive和man systemd.service命令查看,也可以通过阮一峰的blog查看,都比较详细,本文不再一一赘述,重点解释本文使用的配置项。
阮一峰blog地址:
http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html

  • 标识Type
    由于zookeeper自身的启动命令是shell脚本启动jar,属于forking一类。
Type字段/指令

simple(默认值):ExecStart字段启动的进程为主进程
forking:ExecStart字段将以fork()方式启动,此时父进程将会退出,子进程将成为主进程
oneshot:类似于simple,但只执行一次,Systemd 会等它执行完,才启动其他服务
dbus:类似于simple,但会等待 D-Bus 信号后启动
notify:类似于simple,启动结束后会发出通知信号,然后 Systemd 再启动其他服务
idle:类似于simple,但是要等到其他任务都执行完,才会启动该服务。一种使用场合是为让该服务的输出,不与其他服务的输出相混合
  • 定义环境变量。
    由于zookeeper为JAVA程序,必须在环境变量中指定JAVA_HOME,指向正确的JDK安装目录/opt/java/jdk1.8.0_192-amd64。查看zkServer.sh脚本可以知道,只需要指定了JAVA_HOME变量,不需要修改PATH变量,zookeeper即可运行。
Environment=JAVA_HOME=/opt/java/jdk1.8.0_192-amd64
  • 定义启停命令。
    直接使用zkServer.sh start|stop等命令填入service配置文件。无需再在外面包裹一层。

ExecStart,启动命令
ExecStop,停止命令

  • 定义 Systemd 如何停止服务。
    先看zkServer.sh stop的执行逻辑,首先检查PID文件,然后执行kill -9 (kill -SIGKILL)命令停止进程。

再看systemd停止服务(systemctl stop)的执行逻辑,由KillMode配置决定。

KillMode字段/指令:

control-group(默认值):当前控制组里面的所有子进程,都会被杀掉
process:只杀主进程
mixed:主进程将收到 SIGTERM 信号,子进程收到 SIGKILL 信号
none:没有进程会被杀掉,只是执行服务的 stop 命令。

KillMode,默认为control-group。对于service而言,在执行完stop命令后,对控制组中仍然运行的进程将由systemd执行kill命令,依次发送SIGTERM/SIGHUB、SIGKILL信号。

这里有一个定时器配置项,TimeoutStopSec,单位为秒,也可以用"5min 20s"这样的格式,设置为0表示禁用。默认为DefaultTimeoutStopSec,查看man systemd-system.conf,可以看到此值为90s。

两个用途:

  1. 如果设置了ExecStop,调用ExecStopin命令后,service超过该配置项时间后仍未停止,systemd向service进程发送SIGTERM信号。如果没有设置ExecStop,直接发送SIGTERM信号。
  2. SIGTERM信号后,超过该配置项时间后service仍未停止,systemd向service进程发送SIGKILL信号。

综上分析,ExecStop如果设置了zkServer.sh stop (kill -SIGKILL),那么systemd的KillMode可以直接设置为none,不需要做什么了。

  • PIDFILE字段/指令
    对于forking类型的进程,指明PID文件路径。

  • 设置zookeeper运行用户

User=normaluser
Group=normalgroup
  • 自动恢复
    Restart=on-failure

注意,通过systemd(例如systemctl stop|restart命令)来关闭的service,不受此配置约束。

该配置包含多个可选项,对于zookeeper,本文只讨论always和on-failure。首先通过man systemd.service查看这两个可选项的差别。

Restart Settings/Exit causes always on-failure
Clean exit code or signal X
Unclean exit code X X
Unclean Signal X X
Timeout X X
Watchdog X X

两者的区别在于,如果zookeeper进程exit code为0,或者被SIGHUP, SIGINT, SIGTERM or SIGPIPE这4个信号关闭,on-faiure不会重启该进程,always会。

这里我们关注的一件事是,通过直接执行./zkServer.sh stop来关闭zookeeper进程,这种情况下systemd是否会重启zookeeper进程。

分析zkServer.sh的stop处理逻辑源码,可以看到是向zookeeper进程发送信号9来关闭的,所以无论是设置为always还是on-failure,通过./zkServer.sh stop关闭zookeeper进程,systemd都依然会重启zookeeper。

所以如果想不触发systemd重启逻辑,一种方法是通过systemctl stop来调用./zkServer.sh stop来关闭,另一种方法是不要使用./zkServer.sh stop,而是通过向zookeeper进程发送信号15来关闭(kill $pid)。

你可能感兴趣的:(使用systemd ( systemctl ) 控制和托管zookeeper)