Alpine Linux (OpenRC) 服务无法停止的排查与修复
在 Alpine Linux 上部署服务时,遇到问题,当执行 rc-service <服务名> stop 时,命令提示成功,但通过 ps 或 ss 查看时,发现进程依然还在运行,甚至端口依然被占用。
1. 问题现象
最初,编写了一个简单的 OpenRC 脚本来启动我的 Go 程序。程序能够正常启动,端口也能正常监听。
但在尝试停止服务时,奇怪的事情发生了:
# 尝试停止服务
v6:~# rc-service phj stop
* WARNING: phj is already stopped
# 或者
* Stopping phj ... [ ok ]
# 查看服务状态
v6:~# rc-service phj status
* status: stopped
# 但是,查看进程列表,它竟然还在!
v6:~# ps aux | grep phj
5278 root 18:35 [phj_stock_check]
# 端口也还在监听
v6:~# ss -tunlp | grep 37376
tcp LISTEN 0 4096 *:37376 *:* users:(("phj_stock_check",pid=5278,fd=3))
进程 5278 就像一个“孤魂野鬼”,脱离了 OpenRC 的管控,kill 命令有时甚至无法杀掉它(或者杀掉后立刻被某种机制重启,或者由于 pid 丢失无法管理)。
2. 原因分析
OpenRC 默认使用 start-stop-daemon 或 supervise-daemon 来管理后台进程。问题的核心在于:服务管理器找不到对应的 PID 文件(pidfile)来跟踪进程。
在最初的脚本中,我并没有显式指定 pidfile。这就导致了以下后果:
- 启动时:
supervise-daemon启动了进程,因为它不知道应该把 PID 写到哪里,或者没有锁定 PID。 - 停止时:当你执行
stop命令时,supervise-daemon尝试去读取 PID 文件以获取需要杀死的进程 ID。由于找不到 PID 文件(或 PID 不匹配),它认为进程已经不存在了,于是什么也没做。 - 结果:服务状态显示
stopped,但二进制进程依然在后台运行。
3. 解决方案
要让 supervise-daemon 正确地管理进程的生命周期(启动、停止、重启),我们必须显式指定 pidfile,并确保脚本能够正确处理这个文件。
以下是修复后的服务脚本,路径为 /etc/init.d/phj:
#!/sbin/openrc-run
# 1. 指定使用 supervise-daemon 作为监控器
supervisor=supervise-daemon
name="PHJ Stock Checker"
description="拼好鸡监控"
# 2. 配置命令路径、运行用户和工作目录
command="/opt/phj/phj_stock_checker_linux_amd64"
command_user="root"
directory="/opt/phj"
# 3. 【关键修复】显式指定 pidfile
# supervise-daemon 需要这个文件来跟踪进程状态
# ${RC_SVCNAME} 会自动解析为当前服务的名字 (即 phj)
pidfile="/var/run/${RC_SVCNAME}.pid"
# 环境变量
export API_KEY=123
depend() {
need localmount net
}
start_pre() {
# 4. 确保目录存在
mkdir -p /var/run
# 5. 清理可能存在的旧 pid 文件
# 这一步可以防止因为上次非正常退出(如崩溃)导致的 pid 文件残留,
# 从而避免服务认为"进程已经在运行"而拒绝启动
if [ -f "$pidfile" ]; then
rm -f "$pidfile"
fi
}
关键修复点解析
-
pidfile="/var/run/${RC_SVCNAME}.pid":
这是最重要的一行。它告诉 OpenRC 把进程 ID 写入/var/run/phj.pid。当执行 stop 时,OpenRC 会读取这个文件,找到对应的 PID 并发送 TERM 信号。 -
supervisor=supervise-daemon:
确保使用 OpenRC 自带的守护进程管理器,它会自动将你的程序放入后台运行,无需你在命令后面手动加&或使用nohup(这反而会导致双重 fork 或 PID 丢失)。 -
start_pre()清理逻辑:
在启动前检查并删除旧的 pid 文件。这是一种健壮性做法,可以避免 "PID file exists, is service already running?" 类型的错误。
4. 验证修复结果
应用新脚本后,执行以下步骤进行验证:
# 1. 确保旧进程已清理 (如果有残留进程)
pkill -9 phj_stock_check
# 2. 重启服务
v6:~# rc-service phj restart
* Starting PHJ Stock Checker ... [ ok ]
# 3. 检查 PID 文件是否生成
v6:~# cat /var/run/phj.pid
1000
# (这里的数字应该与下面查到的 PID 一致)
# 4. 检查进程和端口
v6:~# ss -tunlp | grep 37376
tcp LISTEN 0 4096 *:37376 *:* users:(("phj_stock_check",pid=1000,fd=3))
# 注意这里的 PID 是 1000
# 5. 【终极测试】停止服务
v6:~# rc-service phj stop
* Stopping PHJ Stock Checker ... [ ok ]
# 6. 确认进程已消失,端口已释放
v6:~# ss -tunlp | grep 37376
v6:~# (没有输出,表示成功停止)
5. 总结
在 Alpine Linux 上编写 OpenRC 服务脚本时,如果遇到“服务停止但进程仍在”的问题,请检查以下几点:
- 是否显式指定了
pidfile? pidfile路径是否有写入权限?- 是否使用了
supervise-daemon而不是简单的后台启动? - 是否在
start_pre中处理了残留的 pid 文件?
正确的 pidfile 配置是连接 OpenRC 管理器与实际进程的桥梁,缺少它,服务管理器就是个“瞎子”。
希望这篇文章能帮到你!