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


相关推荐
涡能增压发动积20 小时前
同样的代码循环 10次正常 循环 100次就抛异常?自定义 Comparator 的 bug 让我丢尽颜面
后端
Wenweno0o20 小时前
0基础Go语言Eino框架智能体实战-chatModel
开发语言·后端·golang
swg32132120 小时前
Spring Boot 3.X Oauth2 认证服务与资源服务
java·spring boot·后端
tyung20 小时前
一个 main.go 搞定协作白板:你画一笔,全世界都看见
后端·go
gelald20 小时前
SpringBoot - 自动配置原理
java·spring boot·后端
殷紫川21 小时前
深入拆解 Java 内存模型:从原子性、可见性到有序性,彻底搞懂 happen-before 规则
java·后端
元宝骑士21 小时前
FIND_IN_SET使用指南:场景、优缺点与MySQL优化策略
后端·mysql
用户319523703477121 小时前
记一次 PostgreSQL WAL 日志撑爆磁盘的排查
后端
nghxni21 小时前
LightESB PlatformHttp v3.0.0:JSONPath 订单转换 HTTP 路由实战
后端
武子康1 天前
大数据-263 实时数仓-Canal 增量订阅与消费原理:MySQL Binlog 数据同步实践
大数据·hadoop·后端