You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ubuntu后台systemctl服务中nohup失效致更新中断问题求助

问题描述

我有一个运行在Ubuntu系统上的Java应用,以root账号作为后台服务通过systemctl启停。该应用具备自动更新功能,更新时会启动runupdate.sh脚本。

Java调用更新脚本的代码如下:

public void installUpdate() {
    final List<String> cmd = List.of("sh",script.getAbsolutePath());
    final Process process = new ProcessBuilder(cmd)
                        .directory(script.getParentFile())
                        .inheritIO()
                        .start();
}

runupdate.sh脚本内容:

#!/bin/sh

BASEDIR=$(dirname "$0")
chmod -R +x $BASEDIR/myapplication_3.23.99_amd64.deb
nohup dpkg -i $BASEDIR/myapplication_3.23.99_amd64.deb >update.log 2>&1 &

但停止服务时更新进程似乎被中断,update.log内容(德语系统)如下:

(Lese Datenbank ... 275752 Dateien und Verzeichnisse sind derzeit installiert.)
Vorbereitung zum Entpacken von .../myapplication_3.23.99_amd64.deb ...
Removed /etc/systemd/system/multi-user.target.wants/myapplication.service.

包状态停留在:Status: install reinstreq half-configured

直接在终端运行runupdate.sh则一切正常。需要解释该现象或提供避免更新中断的方法。

问题原因

systemctl停止服务时,默认会向整个控制组发送SIGTERM终止信号。你的Java进程启动的runupdate.sh和后续的dpkg进程,默认都属于同一个控制组。当Java进程被终止时,这些子进程也会收到终止信号,导致dpkg的更新流程被打断,最终包处于半配置状态。

虽然用了nohup,但它只负责忽略SIGHUP信号(终端退出时发送),对SIGTERM无效,所以无法阻止进程被中断。

解决方法

1. 让更新进程脱离原控制组

修改runupdate.sh,用setsid命令让dpkg运行在独立的会话中,彻底脱离原服务的控制组:

#!/bin/sh

BASEDIR=$(dirname "$0")
# 注:deb包不需要+x权限,这条可以删掉
# chmod -R +x $BASEDIR/myapplication_3.23.99_amd64.deb
setsid dpkg -i $BASEDIR/myapplication_3.23.99_amd64.deb >$BASEDIR/update.log 2>&1 &

setsid会创建新的会话和进程组,这样即使原Java服务被终止,dpkg进程也不会收到终止信号,能完成更新。

2. 修改Java代码,让子进程脱离进程组

直接在启动脚本的命令中加入setsid,让脚本进程一开始就独立:

public void installUpdate() {
    final List<String> cmd = List.of("setsid", "sh", script.getAbsolutePath());
    final Process process = new ProcessBuilder(cmd)
                        .directory(script.getParentFile())
                        .inheritIO()
                        .start();
}

这样runupdate.sh和后续的dpkg都会运行在新的进程组里,不受原Java进程终止的影响。

3. 调整systemd服务的终止模式

编辑你的Java服务配置文件(比如/etc/systemd/system/myapplication.service),添加KillMode=process:

[Service]
KillMode=process

默认KillMode=control-group会终止控制组内所有进程,改成process后,systemctl只会终止主Java进程,不会影响它启动的子进程。但要注意,如果服务有其他需要随主进程一起终止的子进程,可能会残留,按需选择。

4. 用disown让进程脱离shell控制

在runupdate.sh中启动dpkg后,用disown把它从shell的作业列表中移除:

#!/bin/sh

BASEDIR=$(dirname "$0")
chmod -R +x $BASEDIR/myapplication_3.23.99_amd64.deb
nohup dpkg -i $BASEDIR/myapplication_3.23.99_amd64.deb >$BASEDIR/update.log 2>&1 &
disown $!

这种方式能避免shell退出时发送信号给dpkg,但可靠性不如setsid,因为如果原控制组被发送信号,仍可能影响到进程。

内容的提问来源于stack exchange,提问作者Christian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 19:02:07