网上有很多同学都贴过CentOS 7环境中使用systemd管理zookeeper,贴出来的systemd service配置文件也是五花八门,简单的抄到自己的环境里,大多都不能正常运行。本文按照需求描述,根据对zookeeper的启动文件和systemd.service的参数项的分析,来编写一个比较完整的zookeeper服务配置文件。
1. 需求描述:
使用systemd控制和托管zookeeper服务。
- 开机自动启动zookeeper
- zookeeper进程因故障退出后能自动重新启动
- 使用systemctl start|stop|restart|status zookeeper控制zookeeper启停。
- 使用普通账户运行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。
两个用途:
- 如果设置了ExecStop,调用ExecStopin命令后,service超过该配置项时间后仍未停止,systemd向service进程发送SIGTERM信号。如果没有设置ExecStop,直接发送SIGTERM信号。
- 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)。