Skip to content

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 模式

不要忘记,systemdService.Type 正是为此而生!当该值设定为 forking 时,systemd 将任由主进程进行 fork 、退出,并跟踪主进程 fork 出来的子进程。问题解决了……吗?

此时,如果只有一个 Minecraft 服务器并且没有其它需要使用 tmux 进程的干扰,已经可以使用了:尽管去 systemctl startsystemctl 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 启动