Appearance
tmux 与 systemd 共同反应
缘起
为了启动多个 Minecraft 服务器,并能随时进入命令行与服务器交互式通信,使用 tmux 进行进程管理,通过 systemd 来实现开机自启。
ini
[Unit]
Description=Minecraft
After=network.target
[Service]
Type=simple
User=minecraft
Group=minecraft
WorkingDirectory=/path/to/server/1
ExecStart=/usr/bin/tmux new -d -s 'mc1' 'java -jar server.jar --nogui'
ExecStop=/usr/bin/tmux send-keys -t 'mc1' '/stop' Enter
Restart=Always
Nice=5
[Install]
WantedBy=multi-user.target然而,服务没能正常启动起来。原因是:
tmux命令运行后,会创建守护进程,然后直接退出。systemd监测到tmux退出了,于是开始执行ExecStop中的内容;tmux的终端接收到/stop字符串,但是在 Minecraft 还没启动起来之前,/stop命令不能生效;- Minecraft 启动后,处理
/stop命令,Minecraft 服务器退出,tmux进程也结束。
表现为:每次运行 systemctl start 后,Minecraft 服务器的 java 进程就会启动,占用 CPU 并逐渐分配大量的内存,然后该进程就突然消失。
互联网搜索结果中,有一些能运行,但是治标不治本的方案,比如:不写 ExecStop,并且把 Restart 改为 on-failure 。如此,虽然 systemd 检测到 tmux 退出,但是程序状态不是 on-failure ,也没有 ExecStop 可供执行,表现为虽然 systemctl status 显示状态为 dead ,但是后台仍能找到正确的 tmux 与 Minecraft 进程。
forking 模式
不要忘记,systemd 的 Service.Type 正是为此而生!当该值设定为 forking 时,systemd 将任由主进程进行 fork 、退出,并跟踪主进程 fork 出来的子进程。问题解决了……吗?
此时,如果只有一个 Minecraft 服务器并且没有其它需要使用 tmux 进程的干扰,已经可以使用了:尽管去 systemctl start 、 systemctl stop ,没有任何问题!但是,当依样画葫芦写出第二个 Minecraft 服务时,问题又来了:两个 tmux 服务不能同时启动!当启动第二个 Minecraft 服务器时,可以发现,其表现与没换成 forking 模式时如出一辙。原因是:
- 第一个 service 启动时,tmux 没有检测到守护进程,因此 fork 出了守护进程;
- 第二个 service 启动时,tmux 检测到守护进程,于是将创建新会话的工作交给了守护进程,自己直接退出;
systemd在 tmux 退出前没有检测到进程的 fork,认为服务启动失败,执行ExecStop。
oneshot 模式
既然第二个 Minecraft 的启动命令能够:运行后就退出,那么看起来 oneshot 模式非常合适。将 RemainAfterExit 设置为 true 以后,systemd 将会在命令退出后将状态仍保持 active ,那么你将可以正常使用 systemctl stop 命令来停止 Minecraft 服务器。
其它选择
经过一堆考虑,我并没有找到一个特别合适的解决方案。不管哪个,都似乎一定要设置为 oneshot ,这意味着,当 Minecraft 服务器意外挂掉的话,systemd 不会收到通知,而是保持状态为 active。
- 一个占位命令用
forking模式拉起tmux,其它 Minecraft 服务器随后以oneshot模式启动 - 所有 Minecraft 服务器都使用
oneshot启动