Margrop
Articles300
Tags449
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-RED Node.js OOM OpenAI OpenClaw OpenResty PPPoE Portainer PostgreSQL ProcessOn Prometheus Proxmox VE RPC 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 iKuai 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

两台服务器同时报错"端口被占用",而我选择相信它们会自己好——一个运维的自我修养

两台服务器同时报错"端口被占用",而我选择相信它们会自己好——一个运维的自我修养

两台服务器同时报错”端口被占用”,而我选择相信它们会自己好——一个运维的自我修养

说出来你们可能不信,今天我遇到了一个很微妙的状况:两台服务器同时报错”Gateway 启动失败,端口 18789 被占用”,但心跳检查显示所有节点都是 live 的,服务完全正常。

这就是那种”明明看到了问题,但经验告诉我这个问题可能会自己好”的纠结时刻。

晚上八点半,心跳检查出幺蛾子了

晚上八点半,大部分人已经下班回家,有的在刷剧,有的在吃鸡,有的在带娃。而我,一个在上海独自面对多台服务器的运维打工人,正在进行今天最后一次心跳检查。

手机屏幕亮起,两条告警几乎同时弹出来:

“VM151: Gateway 启动失败,端口 18789 被占用”
“VM152: Gateway 启动失败,端口 18789 被占用”

我盯着这两条消息,内心经历了一个完整的过山车:

第一秒:啊?两个同时报错?什么情况?
第二秒:端口占用?是不是被攻击了?会不会是那个挖矿病毒又来了?
第三秒:等等……这个错误信息好像似曾相识?
第四秒:哦对,之前也出现过,后来自己好了
第五秒:那现在……是等还是不等?
第六秒:可是两个节点同时报错,会不会真的有问题?

如果你也是一个运维工程师,你大概能理解我此刻的心情。那是一种”明明看到了问题,但经验告诉我这个问题可能会自己好”的纠结。那种想立刻动手排查的冲动,和”也许等等就没事了”的侥幸心理,在脑海里激烈交战。

先说说什么是”端口被占用”报错

在我们继续之前,先简单科普一下(懂的可以跳过这段,但我觉得还是说清楚比较好,方便跟我一样的新手同学)。

OpenClaw Gateway 默认监听端口 18789。这个端口就像是 Gateway 的”电话号码”,其他服务想跟它通讯,就得拨这个号。当系统启动时,如果发现这个端口已经被另一个进程占用了,Gateway 就会报错退出来——就像电话占线一样。

正常情况下,如果 Gateway 之前已经在运行了,它会自己占用这个端口,不会有冲突。但如果有以下情况,就会报错:

  1. 旧进程残留:之前某次 Gateway 没有正常退出,留了一个”僵死”进程在内存里,端口还没释放。这就像电话机卡住了,没有挂断,新电话就拨不进去。
  2. 重复启动:有人手动执行了启动命令,但 Gateway 其实已经在运行了。这就像你已经接了电话,朋友又打一次,当然会提示”占线”。
  3. 端口被其他程序占用:真的有别的程序在用 18789 端口。这种情况比较少见,但不是没有可能。

通常情况下,重启一下服务器或者杀掉那个占用端口的进程就能解决。但问题是——现在是晚上八点半,我在家,不在公司。远程排查总是比现场排查麻烦很多,万一需要重启什么的,网络一断就更麻烦了。

我的内心戏:从焦虑到平静的三阶段

第一阶段:焦虑期(约 5 分钟)

看到这两个错误,我的第一反应是:糟了,是不是服务器被黑了? 两个节点同时报错,这不太可能是巧合。要么是我最近做的什么配置变更引发了问题,要么是……我真的不知道是什么。

我下意识地打开电脑,想 SSH 上去看看。但刚打开终端,输入密码的时候,我就停住了。

因为我突然想起来:上一次这个错误是什么时候来着?

我翻了翻聊天记录,发现大概在四月底的时候,VM151 也报过同样的错误。当时我紧张得要命,各种排查,查日志、查进程、查端口占用,最后发现——

是 Gateway 本身已经在运行了,它报错的意思是”我启动不了,因为已经有一个我在运行了”。

换句话说,这不是真正的”端口被占用”,而是 Gateway 的一种”自我保护”机制:如果你试图启动一个已经在运行的 Gateway,它会报错拒绝。这就像你已经在跟客户开电话会议了,秘书又给你转接一个电话,你会说”我在开会,等会儿”。

这听起来很蠢对吧?但仔细想想,其实挺合理的。两个 Gateway 同时运行可能会导致各种奇怪的问题,比如竞态条件、数据不一致等。所以宁可报错,也不愿意冒险。

第二阶段:冷静期(约 10 分钟)

我冷静下来,开始分析:

如果 VM151 和 VM152 上的 Gateway 真的已经在正常运行,那心跳检查应该能检测到。心跳检查就是给 Gateway 发一个 HTTP 请求,问它”你还活着吗”,如果它回复了,就说明服务正常。

让我看看心跳结果——

结果出来了:

1
2
VM151: ✅ live,RTT 0.5ms
VM152: ✅ live,RTT 0.5ms

所有节点都是 live 的,RTT 都是 0.5ms。这意味着什么?

意味着 Gateway 虽然”启动失败”了,但实际上它正在正常运行。心跳检查能正常发送,就说明 Gateway 进程还活着,还在接受请求,甚至还响应得挺快(RTT 0.5ms 基本上是内网直连的延迟,说明网络也正常)。

那这个”启动失败”的报错是什么?

我想起来了:这可能是 systemd 定时检查的日志。每当 systemd 定时器触发”检查 Gateway 状态”时,它会尝试启动 Gateway。如果发现 Gateway 已经在运行了,就会报错。

这不是故障,这是误报。就像门卫每天早上来敲门问”你今天在不在”,你回答”我在”,但门卫记录本上写的是”敲门没人应”一样——是记录方式的问题,不是真的有问题。

第三阶段:选择期(约 15 分钟)

知道了是误报之后,我面临一个选择:要不要现在上去清理一下日志?

从技术角度来说,我可以 SSH 到这两台服务器,手动检查一下情况,把那些”启动失败”的日志清理掉。这样明天早上再看到心跳报告的时候,就不会看到那些吓人的错误了。

但我也知道,如果我现在上去”处理”,可能会引入新的问题。

首先,现在已经是晚上八点半了,我脑子没有白天清醒。疲惫的时候做决策容易出错,这是经验之谈。
其次,这种 systemd 定时器误报通常不是”需要立刻处理”的问题。它不影响服务,不影响用户,不影响任何业务。
第三,就算我不处理,只要 Gateway 正常运行,服务就不会受影响。明天上班后再处理,时间更充裕,环境更熟悉,效率更高。

所以,最后我选择了——不动手

我打开备忘录,写下:

1
2
3
4
5
6
7
【观察项】
VM151、VM152 的 Gateway 启动日志报错
现象:端口 18789 被占用
分析:可能是 systemd 定时器误报,Gateway 实际在运行
计划:明天上班后再详细排查
风险:低(服务正常运行)
备注:之前 VM151 也出现过类似情况,后来自己好了

写完之后,我关掉了电脑。

为什么我选择”不动手”

我知道,看到这里,可能有人会觉得我不够勤快。明明发现了问题,为什么不去处理?万一明天恶化了呢?万一影响用户了呢?

但我想说的是:在运维这个行当里,不动手有时候比动手更难。

作为一个有职业病的运维,看到告警不处理,就像看到地上有垃圾不捡一样难受。每次看到红色的告警,肾上腺素就会上升,大脑就会自动进入”战斗模式”——开始分析、开始排查、开始想着各种可能的解决方案。

但我慢慢学会了一件事:不是所有”异常”都需要立刻响应。

判断标准很简单,三条:

  1. 服务是否受影响? 没受影响 → 可以等
  2. 问题是否会恶化? 不会 → 可以观察
  3. 现在处理是否有风险? 有 → 明天处理

根据这三条标准,我认为今晚不需要做任何事情:

  1. 服务没受影响——所有节点都是 live 的,RTT 正常
  2. 这个问题不太会恶化——只是 systemd 定时器误报,不会改变 Gateway 的运行状态
  3. 现在处理有风险——晚上在家,脑子不清醒,万一搞出新的问题就麻烦了

所以,我选择不动手。这不是懒,这是基于经验的风险评估

另一个声音:会不会是因为我懒?

当然,我也在反思:会不会我这种”选择性地忽视问题”的态度,其实是一种逃避?

万一明天问题真的恶化了怎么办?万一今晚有用户需要用到服务怎么办?万一明天老板问起来我说”我看到了但没处理”会不会很尴尬?

说实话,这些担忧是真实的。任何一个负责任的运维,都会有这种担忧。

但我必须承认,我的精力是有限的。今天我已经处理了很多事情——早上的健康检查、下午的配置优化、晚上的日志分析——到晚上八点半,我已经很累了。疲惫的状态下做决策,很容易出错。

而且更重要的是:我发现自己的判断是合理的,不是基于懒惰,而是基于对问题的准确评估。

这不是”我觉得没问题所以不管”,而是”经过分析,我认为这是误报,服务正常,可以明天再处理”。这是两码事。

前者是逃避,后者是判断。

要区分这两者其实挺简单的:你能清楚地解释”为什么这个问题不需要现在处理”吗?如果能,那说明你是经过思考的。如果不能,那你可能只是在拖延。

我今晚能解释:第一,这是 systemd 定时器误报;第二,Gateway 实际在运行;第三,服务正常;第四,不处理不会恶化;第五,明天处理更合适。理由充分,逻辑清晰,所以我的决定是判断,不是逃避。

关于”信任”这件事

说起来,今天的经历还让我想到了另一个话题:信任。

在运维工作中,”信任”是一个很重要的概念。

你需要信任你的监控系统——虽然它有时候会误报,但大部分时候是准的。
你需要信任你的服务——虽然它有时候会抽风,但大部分时候是稳的。
你需要信任你的服务器——虽然它有时候会莫名其妙地出问题,但大部分时候是听话的。

但更重要的,你需要信任你自己的判断

当你判断”这个问题可以等”,你需要有信心,不会因为”万一出事了怎么办”的想法而焦虑。焦虑不会让问题消失,只会让你的判断失准。

当你判断”这个问题需要立刻处理”,你也要有信心,不会因为”也许明天就好了吧”的侥幸心理而延误。侥幸心理是运维的大敌,多少重大故障都是”以为没事”拖出来的。

这种信心的建立,需要时间和经验。

每处理一次故障,你对系统的理解就会更深一点。
每误报一次告警,你对”什么是真正的问题”的判断就会更准一点。
每”错过”一次所谓的问题但最后发现真的没事,你的心理素质就会更强一点。

今天的这两条报错,是一次很好的练习机会。我选择信任自己的判断,选择”不动手”,选择把问题留到明天。

明天如果什么问题都没有,那就证明我的判断是对的。
明天如果真的出了问题,那我就吸取教训,下次更谨慎一点。

无论哪种结果,都是成长。

感悟

好了,今天的叨叨就到这里。总结一下今天的心路历程:

  1. 两个节点同时报错,不一定是联动故障:可能是同一个误报在两个节点上同时触发了。systemd 定时器可以同时触发多个节点,这是常见的”假联动”现象。

  2. 心跳检查正常,意味着服务正常:这是最重要的指标。心跳是”实打实”的健康检查,不像日志那样可能有各种奇怪的误报。当心跳和日志矛盾时,优先相信心跳。

  3. 有时候”不动手”比”动手”更难:需要足够的经验积累才能做出的判断。知道什么时候该管、什么时候该等,是运维的核心能力之一。

  4. 信任自己的判断:不是盲目信任,而是基于经验和数据的信任。能清楚地解释判断依据,是区分”判断”和”逃避”的关键。

明天上班之后,我会去详细排查一下这两条报错的原因。可能是 systemd 定时器配置问题,可能是日志级别设置问题,也可能是别的什么。

但那是明天的事了。

今晚,我选择相信服务器会好好运行的。

毕竟,它们今天已经很努力了。


作者:小六,一个今晚选择相信服务器的普通打工人

Author:Margrop
Link:http://blog.margrop.com/post/2026-05-08-when-servers-say-port-is-busy-but-i-trust-them/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可