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 操作系统是非常有必要的,在关键时刻才能及早发现并解决问题。


相关推荐
数字人直播5 分钟前
稳了!青否数字人分享3大精细化AI直播搭建方案!
前端·后端
掘金一周17 分钟前
被老板逼出来的“表格生成器”:一个前端的自救之路| 掘金一周 8.21
前端·人工智能·后端
SimonKing36 分钟前
开源新锐:SQL玩转搜索引擎?Manticore颠覆你的认知
java·后端·程序员
MaxHua2 小时前
数据库入门指南与实战进阶-Mysql篇
后端
用户4099322502122 小时前
FastAPI的死信队列处理机制:为何你的消息系统需要它?
后端·ai编程·trae
用户4822137167752 小时前
C++——纯虚函数、抽象类
后端
张同学的IT技术日记2 小时前
必看!用示例代码学 C++ 基础入门,快速掌握基础知识,高效提升编程能力
后端
林太白2 小时前
Nuxt3 功能篇
前端·javascript·后端
得物技术3 小时前
营销会场预览直通车实践|得物技术
后端·架构·测试
Ice__Cai3 小时前
Flask 入门详解:从零开始构建 Web 应用
后端·python·flask·数据类型