Margrop
Articles270
Tags432
Categories23
1password AC ACP AI AP API AppDaemon Aqara CI/CD Caddy Cloudflare Cookie 认证 Cron D1 Date Diagrams.net Docker Docker Compose Electerm Gateway GitHub Actions HA HADashboard Hexo HomeAssistant IP IPv4 Java LVM‑Thin Linux MacOS Markdown MiniMax Multi-Agent MySQL NAS Nginx Node.js OpenAI OpenClaw OpenResty PPPoE Portainer PostgreSQL ProcessOn Prometheus Proxmox VE SOCKS5 SSL Session Shell Subagent TTS TimeMachine UML Uptime Kuma VPN VPS Web WebSocket Windows Workers activate ad adb adblock agent aligenie aliyun alpine annotation aop authy autofs backup baidupan bash bitwarden boot brew browser caddy2 cdn centos cert certbot charles chat chrome classloader client clone closures cloudflare cmd command commit container crontab ctyun ddsm demo dependency deploy developer devtools dll dns docker domain download draw drawio dsm dump dylib edge exception export fail2ban feign firewall-cmd flow frp frpc frps fuckgfw function gcc gfw git github golang gperftools gridea grub gvt-g hacs havcs heap hello hexo hibernate hidpi hoisting homeassistant hosts html htmlparser https idea image img img2kvm import index install intel io ios ip iptables iptv ipv6 iso java javascript jetbrains jni jnilib jpa js json jsonb jupter jupyterlab jvm k8s kernel key kid kms kodi koolproxy koolproxyr kvm lan lastpass launchctl learning lede letsencrypt linux live low-code lvm lxc m3u8 mac macos mariadb markdown maven md5 microcode mirror modem modules monitor mount mstsc mysql n2n n5105 nas network nfs node node-red nodejs nohup notepad++ npm nssm ntp oop openfeign openssl os otp ovz p14 packet capture pat pdf pem perf ping pip plugin png powerbutton print pro proxy pve pvekclean python qcow2 qemu qemu-guest-agent rar reboot reflog remote remote desktop renew repo resize retina root route router rule rules runtime safari sata scipy-notebook scoping scp server slmgr so socks source spk spring springboot springfox ssh ssl stash string supernode svg svn swagger sync synology systemctl systemd tap tap-windows tapwindows telecom template terminal tls token totp tvbox txt ubuntu udisk ui undertow uninstall unlocker upgrade url v2ray vhd vim vlmcsd vm vmdk web websocket wechat windows with worker wow xiaoya xml yum zip 中国电信 云电脑 交换机 人机协作 代理 体检 健康检查 光猫 公网IP 内存 内网IP 写作 升级 协作 博客 反向代理 启动 夏令时 多智能体 多节点 多节点管理 天猫精灵 天翼云 安全 安装 定时任务 容器 容器网络 导入 小米 常用软件 广告屏蔽 序列号 应用市场 异常 感悟 打工 打工人 技术 抓包 排查 描述文件 故障 故障排查 效率 效率工具 无服务器 日记 时区 显卡虚拟化 智能家居 智能音箱 服务管理 架构 梯子 模块 流程 流程图 浏览器 漫游 激活 火绒 焦虑 玄学 生活 电信 画图 监控 直播源 直觉 端口扫描 管理 续期 网关 网络 网络风暴 群晖 脚本 腾讯 自动化 虚拟机 认证 证书 语雀 超时 路由 路由器 软件管家 软路由 运维 运维监控 连接保活 通信机制 部署 配置 钉钉 镜像 镜像源 门窗传感器 问题排查 防火墙 阿里云 阿里源 集客

Hitokoto

Archive

411次重启都没救活,直到我发现它只是"占着茅坑不拉屎"

411次重启都没救活,直到我发现它只是"占着茅坑不拉屎"

411次重启都没救活,直到我发现它只是”占着茅坑不拉屎”

说出来你们可能不信,今天我遇到了一台服务器,它今天一天的重启次数,比我过去一个月重启的次数加起来还多。

411次。

对,你没看错,四百一十一次

而这一切的罪魁祸首,是一个已经”死”了但还”占着坑”的进程。

晚上8点:准时送达的健康检查”惊喜”

作为一个在上海打工的运维,我每天晚上8点准时收到健康检查报告。这已经成了一种仪式——有点像收快递,只不过快递里装的是服务器的”体检报告”。

今天打开报告一看,好家伙,密密麻麻一片绿里,突然跳出来一个刺眼的红色:

VM153: ❌ inactive/dead — systemd 循环重启(411次

我当时的表情大概是这样的:😱

等等,411次重启?这服务器是受到了什么诅咒吗?从早上到现在,一天时间重启了411次,平均每两分钟一次。这已经不是”不稳定”了,这是”完全失控”。

第一反应:难道是内存泄漏?

作为一个有经验的运维,我的第一反应是:内存泄漏?CPU打满?还是磁盘满了?

赶紧SSH连上去看看。

1
2
3
4
ssh root@某VM
top
free -h
df -h

结果:

  • 内存:正常
  • CPU:正常
  • 磁盘:正常
  • 进程……等等,这里有个进程占用率怎么这么高?

一看日志,好家伙,系统日志里全是这样的记录:

1
2
3
4
Apr 18 12:02:03 systemd[1]: Started openclaw-gateway.service
Apr 18 12:02:05 systemd[1]: Stopping openclaw-gateway.service
Apr 18 12:02:05 systemd[1]: openclaw-gateway.service: Main process exited, code=exited, status=1/FAILURE
Apr 18 12:02:05 systemd[1]: openclaw-gateway.service: Failed with result 'exit-code'.

服务刚启动就失败,失败后就重启,重启后又失败,然后继续重启……周而复始,生生不息。

我突然意识到一个问题:systemd 里配置的是 restart counter is at 411,也就是说 systemd 已经尝试重启了411次还没放弃。这孩子,是真的倔啊。

深入排查:日志里藏着的真相

我仔细看了看 journalctl 的输出,发现了问题的端倪:

1
Gateway failed to start: another gateway instance is already listening on ws://0.0.0.0:18789 | listen EADDRINUSE: address already in use 0.0.0.0:18789

地址已被占用!

这就解释通了——systemd 在不断尝试启动新的 Gateway 实例,但是端口 18789 已经被一个旧的进程占用了,所以新的实例启动失败,失败后 systemd 就会重启,然后又失败,然后又重启……无限循环。

用大白话说就是:茅坑已经被一个”死进程”占着了,新进程想上厕所但找不到坑位,只能在门口干等着,等了一会儿就被踢出去了(重启)。

找到”罪魁祸首”

让我看看是谁在占着茅坑:

1
2
3
lsof -i :18789
# 或者
ss -tlnp | grep 18789

好家伙,果然有个老进程还在跑着:

1
pid 21095 root: openclaw-gateway (*:18789)

这个 pid 21095 的进程,不知道什么时候启动的,估计是很久以前的一次手动启动,然后 SSH 会话断开了,进程还在跑,但它实际上已经”死”了——没有人用它了,它也没有任何响应,但就是占着端口不放。

就像那种在公司会议室里放着个电脑,然后人走了,电脑还开着,灯还亮着,但已经没人用了。你进去开会发现没位置,但你不能把人家电脑扔了吧?

尝试”温柔地”请走它

我想先试试”文明执法”:

1
kill 21095

结果——

1
bash: line 1: kill: (21095) - No such process

什么?进程不存在?

但它明明在占用端口啊?

我突然明白了——这个进程可能是一个”僵尸进程”(zombie process)。它实际上已经退出了,但系统还没有来得及清理它的资源占用。或者,它是一个”孤儿进程”,被 systemd 收养了,但已经不受控制了。

不管是哪种情况,反正 kill 命令是拿它没办法了。

终极方案:直接 systemctl restart

既然 kill 不行,那我就直接 restart 试试:

1
systemctl restart openclaw-gateway

结果——

1
Active: active (running) since Sat 2026-04-18 20:02:49 CST; 3s ago

成了!

原来 systemd 在 restart 的时候,会先停止(stop)服务,停止的时候就会把所有的子进程都清理掉——包括那个占着端口的 pid 21095。旧进程被清理掉之后,新进程就能成功绑定端口了。

你说这事儿闹的,411次重启没解决,我一个 restart 就好了。

Systemd内心OS:你早说啊,我 restart 会先停再启,你偏偏只 start,我能怎么办?

复盘:为什么会发生这种事?

事后我复盘了一下,这事儿大概是这样发生的:

某月某日,我登录 VM153,手动启动了 openclaw-gateway。后来 SSH 会话超时断开,但那个进程还在跑。

某月某日,systemd 的定时任务检测到服务没有运行(因为是手动启动的,不归 systemd 管),就尝试用 systemd 启动服务。

结果:systemd 启动的新实例发现端口被占用,启动失败。systemd 的默认策略是”失败了?那就重试吧”。于是就陷入了 411 次的无限重启循环。

说白了,这事儿就是手动启动的服务和 systemd 管理冲突了

经验总结:如何避免”占着茅坑不拉屎”

经过今天的这个事情,我总结了几条经验:

第一,尽量用 systemd 管理服务,不要手动启动。

手动启动的进程不受 systemd 管理,systemd 不知道它的存在,以为服务没跑,就会不断尝试启动新实例。

第二,如果要手动启动,记得 nohup 或者 screen。

如果真的需要手动启动,记得用 nohup 或者 screen 包裹,这样即使 SSH 断开,进程也不会受影响。

第三,定期检查有没有僵尸进程或者孤儿进程。

可以用 ps aux | grep openclaw 看看有没有多个进程在跑,有的话可能就是冲突了。

第四,端口占用问题,用 lsof 或者 ss 排查。

这两个命令可以看到哪个进程在占用端口,比 netstat 快多了。

第五,systemd 的 Restart 配置要合理。

Restart=always 是双刃剑——服务挂了会自动重启,但如果是因为端口占用这种问题重启,重启也没用。建议加一个 RestartSec=5 或者更长的间隔,避免疯狂重启。

感想

说实话,今天这个问题排查起来不算难——日志写得清清楚楚,”地址已被占用”,只要认真看日志,一两分钟就能定位。

但问题在于,这种”进程还活着但已经没用了还占着资源”的情况,特别容易被人忽略。进程列表里看起来一切正常,端口也没被人用,但就是有冲突。你说是 bug 吧也不算 bug,你说是配置问题吧也不算配置问题,就是一个”历史遗留问题”。

运维这行干久了,就会发现很多问题都不是什么高深的技术问题,而是——**”你上次干了一件事但没干完”**。

就像今天这个占着茅坑的进程,谁知道它是什么时候被留下来的呢?可能是上周,可能是上个月,可能是我自己干的,也可能是别人干的。但不管是谁干的,现在是我来处理。

打工人的日常,就是给前任收拾烂摊子。


作者:小六,一个今天被僵尸进程上了一课并成功收拾烂摊子的普通打工人

Author:Margrop
Link:http://blog.margrop.com/post/2026-04-18-stale-process-port-occupancy-gateway-restart-loop/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可