go程序需三步适配supervisor:开头输出启动日志、忽略默认信号并手动监听sigterm/sigint、配置startsecs≥3;否则supervisor误判退出。

Go 程序不能直接用 supervisor 管理——因为 supervisor 只认 shell 启动的长期运行进程,而 Go 编译出的二进制默认是前台阻塞式,但没做信号处理时,supervisor 会误判为“已退出”。
supervisor 启动 Go 二进制失败:常见错误现象
你写好 main.go,编译成 myapp,配好 supervisor 的 [program:myapp],却看到日志里反复出现:STARTED → STOPPED (exit status 0) 或 CRASHED (pid 12345 exited unexpectedly)。这不是 Go 程序崩了,是 supervisor 认为你“启动完就立刻结束了”。
根本原因:supervisor 默认用 autostart=true + startsecs=1 判断进程是否“真正跑起来了”。它等 1 秒,发现你的 Go 进程没持续输出、也没保持在后台活跃状态(比如没忽略 SIGHUP、没正确响应 SIGTERM),就判定启动失败。
- Go 程序没加
log.Printf或fmt.Println开头输出,supervisor 看不到“启动成功信号” - 程序启动后立即 panic 或 exit,比如配置加载失败没兜底
- 用了
os.Exit(0)在初始化阶段退出,supervisor 当成正常终止
让 supervisor 正确识别 Go 进程:必须做的三件事
不是改 supervisor 配置就能解决,得从 Go 代码和部署两头配合。
- Go 主函数开头加一句
log.Println("server started on :8080")(或你监听的地址),确保有可被 supervisor 捕获的 stdout 输出 - 禁用 Go 默认的信号行为干扰:在
main()开头加signal.Ignore(signal.SIGHUP, signal.SIGINT, signal.SIGTERM)—— supervisor 用SIGTERM停止进程,如果你不忽略,Go 默认会直接退出;但你要自己实现优雅关闭逻辑,所以先忽略再手动监听 - supervisor 配置中必须设
autorestart=true和startsecs=3(别用默认的 1),给 Go 初始化留出缓冲时间;同时加stdout_logfile=/var/log/myapp.log方便排查
示例最小可行配置片段:
[program:myapp] command=/opt/bin/myapp directory=/opt/myapp autostart=true autorestart=true startsecs=3 stdout_logfile=/var/log/myapp.log redirect_stderr=true
Go 进程被 supervisor 杀死后无法优雅退出?信号处理要自己写
supervisor 发 SIGTERM 时,Go 默认会立刻退出,来不及关 DB 连接、清缓存、刷日志。这不是 supervisor 的问题,是 Go 程序没接管信号。
- 不要依赖
defer在 main 函数末尾清理——main 返回即进程结束,defer根本没机会执行 - 用
signal.Notify(c, syscall.SIGTERM, syscall.SIGINT)监听,收到后触发自定义 shutdown 流程 - shutdown 逻辑里要用
context.WithTimeout控制最大等待时间,避免卡死导致 supervisor 强杀(默认 10 秒后发SIGKILL) - 务必在 shutdown 完成后再调用
os.Exit(0),否则 supervisor 记录为非正常退出
关键代码示意:
func main() {
log.Println("server started")
// ... 启动 HTTP server 等
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT)
<p>最常被跳过的点:supervisor 不会帮你等 Go 的 <code>http.Server.Shutdown</code> 完成——它只管发信号、等进程消失。你得确保 shutdown 逻辑足够快(通常 </p>golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











