Go 程序 OTA 子进程意外终止问题排查与解决

📌 背景描述

在公司实际业务嵌入式系统中,Go编写的主程序 probe-agent 负责运行核心业务,同时具备自升级(OTA)功能。主程序需要在运行中,在升级过程中,会进行如下操作:

  • 启动 OTA 子程序 ota-executor
  • ota-executor 进行旧程序备份、停止主程序、解压新程序包并完成升级、启动新程序等操作
  • 启动主程序后,通过curl 检测主程序的健康情况,如果主程序健康,则 OTA 成功,异步通知平台升级成功
  • 升级过程中,如果出现异常,就会进行回滚操作

但在我的测试过程中,遇到如下问题:

  • ota-executor 无法正常启动
  • ota-executor 启动成功后,只要执行 systemctl stop probe-agent停止主程序,ota-executor 就会退出,导致 OTA 中断
  • 即使使用 Go 的 syscall.SysProcAttr{Setsid: true} 尝试分离子进程,仍然无效

下面列出经过排查分析后的具体原因以及相对应的解决方案


⚠️ 问题一:OverlayFS 下无法执行二进制

现象

启动 ota-executor 报错是这样的:

plain 复制代码
fork/exec /home/probe-agent/ota-executor: operation not permitted

分析

1、首先第一直觉是二进制没有执行权限,于是查看了二进制的权限:

bash 复制代码
ls -l /home/probe-agent/ota-executor
-rwxr-xr-x 1 root root 10904 5月   7 09:09 /home/probe-agent/ota-executor

显示二进制有执行权限的,不是这个问题

2、再次查看二进制文件和服务器系统是否对应:

bash 复制代码
# 这里显示系统版本为 Ubuntu 18.04 并且是 x86_64

uname -a
Linux 192.168.1.1 4.15.0-45-generic #48-Ubuntu SMP Tue Jan 29 16:28:13 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
cat /proc/version

# 再查看二进制文件的打包是否是 x86_64
file /home/probe-agent/ota-executor
/home/probe-agent/ota-executor: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=c0f0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0, with

确定了运行环境和二进制文件关系是一致的,也不是这个问题

3、经过一番搜索,难道这还跟文件系统有关?

系统根目录为 OverlayFS:

bash 复制代码
# 查看文件系统
findmnt
# 输出结果
overlayroot on / type overlay (rw,relatime,...)

这里的结果显示:

  • 根目录 / 是通过 overlay 文件系统挂载的;
  • overlayroot 是挂载源(通常是 overlayfs 的 upperdir/merged);
  • overlay 是文件系统类型;
  • rw,relatime 表示是可读写的。

OverlayFS 常用于系统只读保护,但它存在以下限制:

  • 某些 overlay 的 upperdir 不支持 exec 权限
  • AppArmor、mount options、SELinux 等也可能限制执行
  • lsattr 查看权限时可能无效

也就是说,在我所在的这个服务器上不能在 /home 下执行可执行文件。

✅ 解决方案

OverlayFS 允许将文件系统挂载为只读,但执行文件必须位于可执行目录中。所以我把执行文件从 /home/... 移动到 /opt 或其他普通挂载路径下:

bash 复制代码
mv /home/probe-agent/ota-executor /opt/probe-agent/ota-executor
chmod +x /opt/probe-agent/ota-executor

这样就可以正常执行了。


⚠️ 问题二:systemd cgroup 杀死子进程

现象

ota-executor 在运行中执行 systemctl stop probe-agent,自己也会立即被杀掉。

分析

对于不熟悉 Linux 的程序员,在这样的情况下根本不知道应该从哪方面开始分析问题,所以只能交给度娘或者Google。

于是经过又经过一番搜索,最终找到如下答案:

主程序是通过 systemdprobe-agent.service 启动的:

  • 所有由此服务启动的进程(包括子进程)默认属于同一个 cgroup
  • systemd 管理下,当停止服务时,会清理其 cgroup 下的所有进程
  • 即使在子进程中调用 syscall.Setsid 创建独立会话,也不能逃离 cgroup 限制

查看进程是否独立:

bash 复制代码
cat /proc/57843/cgroup    # 57843 是OTA程序的进程ID

结果显示其仍归属于 system.slice/probe-agent.service

错误尝试

刚开始,我在在主程序中是这样启动OTA子进程的:

go 复制代码
cmd := exec.Command("/opt/probe-agent/ota-executor", "-request", "/path/to/request")
cmd.SysProcAttr = &syscall.SysProcAttr{
    Setsid: true,
}
cmd.Start()
cmd.Process.Release()

结果表明,这样启动确实可以启动子进程,但是子进程并未完全脱离主进程的 cgroup,当主程序被 systemctl stop 停止时,子进程也会被一起停止。


✅ 解决方案:使用 systemd-run 创建独立 cgroup

正确的方式是使用 systemd-run 启动 OTA 子程序,使其运行在独立 scope 中,不被主服务控制:

go 复制代码
cmd := exec.Command("systemd-run", "--scope", "/opt/probe-agent/ota-executor", "-request", requestFile)

也可选用:

bash 复制代码
systemd-run --unit=ota-executor /opt/probe-agent/ota-executor -request /path/to/request

这样做的好处:

  • 该进程运行在 ota-executor.scope,不再隶属于 probe-agent.service
  • 即使主程序被 systemctl stop,子进程也能存活

✅ 总结与建议

问题 原因 解决方案
无法执行 OTA 程序 OverlayFS 限制了执行权限 将文件移动到 /opt 等常规挂载目录
OTA 程序被误杀 systemctl stop 杀死所有在同一 cgroup 中的子进程 使用 systemd-run --scope 启动子进程,分离 cgroup

最后

  1. 如果程序需要启动另外一个子程序并让其能够独立运行,不受主程序影响,应使用 systemd-run 启动
  2. 在部署过程中,不要将可执行文件部署在 Overlay 或只读文件系统下
  3. 通过 /proc/<pid>/cgroup 检查进程的 cgroup,看看是否是独立的进程
  4. 使用 journalctl -u xxx.scope 可以单独查看 OTA 子进程日志

以上问题的发现和解决,很大程度上考验程序员是否熟悉 Linux 操作系统,足以体现了熟悉 Linux 系统的重要性,所以作为后端开发,熟悉 Linux 操作系统是非常有必要的,在关键时刻才能及早发现并解决问题。


相关推荐
奋进的芋圆9 小时前
DataSyncManager 详解与 Spring Boot 迁移指南
java·spring boot·后端
计算机程序设计小李同学9 小时前
个人数据管理系统
java·vue.js·spring boot·后端·web安全
Echo娴10 小时前
Spring的开发步骤
java·后端·spring
追逐时光者10 小时前
TIOBE 公布 C# 是 2025 年度编程语言
后端·.net
Victor35610 小时前
Hibernate(32)什么是Hibernate的Criteria查询?
后端
Victor35610 小时前
Hibernate(31)Hibernate的原生SQL查询是什么?
后端
_UMR_11 小时前
springboot集成Jasypt实现配置文件启动时自动解密-ENC
java·spring boot·后端
程序员小假11 小时前
我们来说说 Cookie、Session、Token、JWT
java·后端