服务器和windows客户端重启后,发现agent连不上lightmem和hive mcp了
服务器和windows客户端重启后,发现agent连不上lightmem和hive mcp了
后来按了重联后,hive连上了,但是lightmem还是不行,日志信息: fetch failed (connect ECONNREFUSED 192.168.0.88:18444)
手工开lightmem任务:
skywalk@fb98:~/dswork $ sudo service lightmem start
lightmem: PostgreSQL 10.0.10.10:5432 在 60s 内不可达
看看谁没有起来?
skywalk@fb98:~/dswork $ jls
JID IP Address Hostname Path
1 lims-pg /usr/jails/lims-pg
2 lims-redis /usr/jails/lims-redis
3 lims-rabbit /usr/jails/lims-rabbit
4 lims-minio /usr/jails/lims-minio
5 lims-router /usr/jails/lims-router
手工开这个jail ,竟然说已经exists....
skywalk@fb98:~/dswork $ sudo service jail start lims-pg
Starting jails: cannot start jail "lims-pg":
jail: "lims-pg" already exists
.
怒了,还是让AI来做吧,Workbuddy,开工!
FreeBSD vnet jail 二层「静默断链」:bridge0 成员都在,包就是不转发
一台 FreeBSD 15 上的服务起不来,报了「PostgreSQL 60 秒内不可达」。
顺着这个报错查下去,PG 是活的、jail 是跑着的、bridge0 的成员接口一个不少——
但宿主和 jail 之间就是 ping 不通。
这篇文章记录这次排错:三个看似合理的假设全错,真正的判据藏在接口计数器里。
后面还有第二个坑——它在开机时根本没有尝试启动过。
一、现场:一句「60s 内不可达」
lightmem(一个带 pgvector 后端的记忆服务)的 rc 脚本长这样:
# 启动前等待 lims-pg jail 的 5432 就绪
_i=0
while [ "${_i}" -lt "${lightmem_pg_wait}" ]; do
if nc -z -w 2 "${lightmem_pg_host}" "${lightmem_pg_port}" 2>/dev/null; then
return 0
fi
_i=$((_i + 1))
sleep 1
done
echo "lightmem: PostgreSQL ${lightmem_pg_host}:${lightmem_pg_port} 在 ${lightmem_pg_wait}s 内不可达" >&2
return 1
服务没起来,宿主 nc -z 10.0.10.10 5432 失败。拓扑是标准的 FreeBSD vnet jail:
宿主 em0 192.168.0.88
└─ bridge0 10.0.10.1/24
├─ epair10a ── epair10b lims-pg 10.0.10.10 (PostgreSQL)
├─ epair20a ── epair20b lims-redis 10.0.10.20
├─ epair30a ── epair30b lims-rabbit 10.0.10.30
├─ epair40a ── epair40b lims-minio 10.0.10.40
└─ epair50a ── epair50b lims-router 10.0.10.50
二、被数据推翻的三个假设
假设 1:PG 连接打满(因为「有 271 个进程」)
一个只跑 PG 的 jail 有 271 个进程,确实可疑——正常 PostgreSQL 也就十来个。但进去一数:
$ sudo jexec 1 ps -ax -o comm= | sort | uniq -c | sort -rn | head -6
9 postgres
6 syslogd
2 cron
9 个。那个 271 是历史观测,早就不是现场了。用过期数据做推理,是排错里最贵的错误。
假设 2:空壳 jail
这个假设对了一半,但对错的不是那个 jail。查了一圈 /usr/local/etc/rc.d/ 才发现:lims-redis、lims-rabbit、lims-minio、lims-router 四个 jail 里一个服务都没装,只有 syslogd 在跑——它们本来就是占位壳。真正有业务的 lims-pg 是好的。
假设 3:PG 没启动
$ sudo jexec 1 pg_isready -h 10.0.10.10 -p 5432 -t 3
10.0.10.10:5432 - accepting connections
活着,而且 sockstat 显示它老老实实监听在 10.0.10.10:5432。所以问题不在 PG,在「宿主能不能走到 jail」这一层。
顺带两个假警报,别被它们带偏
警报 A:pg_isready -h 127.0.0.1 在 jail 里返回 no response。
第一反应是「PG 没监听回环」。但 sockstat 里那个 127.0.0.1:5432 其实属于宿主自己另装的一个 postgres(jid 0)。jail 有独立 vnet,它的 127.0.0.1 是另一套回环——jail 的 PG 只绑了 10.0.10.10,在 jail 内查 127.0.0.1 本来就不该通。
警报 B:jail 里 ps -o lstart 显示进程启动时间比宿主记录早 8 小时,看着像「进程跨重启存活」这种灵异事件。实际是 jail 里 /etc/localtime 是 UTC、宿主是 CST。时区差,不是残留。
三、真判据:别信「状态」,去数「包」
怀疑链路的第一个动作通常是 ifconfig。但这次 ifconfig 是最会骗人的那个:
$ ifconfig epair10a
epair10a: flags=1008942<BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST,LOWER_UP>
status: active # ← 看着完全正常
$ ifconfig bridge0 | grep member
member: epair10a flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP>
member: epair20a ... # ← 五个成员一个不少
UP、RUNNING、member 齐全。但真正说明问题的是这两条:
$ netstat -I epair10a -b
Name Mtu Network Address Ipkts Ierrs Idrop Ibytes Opkts Oerrs Obytes Coll
epair10a* 1500 <Link#4> 58:9c:fc:10:55:c6 0 0 0 0 0 383 0 0
# ↑收 0 ↑发 0 ↑错误 383 且在涨
$ ifconfig bridge0 addr
# 空。MAC 转发表里一条记录都没有。
$ ping -c 3 10.0.10.10
100.0% packet loss
$ arp -an | grep 10.0.10.10
? (10.0.10.10) at (incomplete) on bridge0 expired [bridge]
Ipkts=0 / Opkts=0 / Oerrs 持续递增,这个组合基本只有一种解释:桥把帧交给 epair10a 输出,epair_output() 每次都返回错误——对端的 softc 已经不在了。
再用 tcpdump 确认一次:
$ sudo tcpdump -i bridge0 -n -c 20 arp # 另一窗口同时 ping
08:08:12.673564 ARP, Request who-has 10.0.10.10 tell 10.0.10.1, length 28
08:08:13.673922 ARP, Request who-has 10.0.10.10 tell 10.0.10.1, length 28
08:08:14.674933 ARP, Request who-has 10.0.10.10 tell 10.0.10.1, length 28
# 只有 request 出去,没有一个 reply
jail 里抓不了包怎么办
想在 jail 侧对照,tcpdump 直接拒绝:
tcpdump: epair10b: Packet capture is not supported on that device
((there are no BPF devices))
jail 的 devfs 里没给 bpf。这时候不用折腾 devfs 规则,用两端计数器对照就够了:
$ jexec lims-pg netstat -I epair10b -b
epair10b ... 0 0 0 0 5 0 168 0
# ↑jail 确实发出去了 5 个包
$ netstat -I epair10a -b # 宿主侧 a 口
epair10a* ... 0 ... 0 0 0 0
# ↑ 一个都没收到
单向丢包可能是路由问题,双向都丢、且发端计数在涨、收端计数是 0,就只能是这对 epair 本身不通。
四、根因:vnet jail 的 epair 不能「二次入狱」
epair 是一对背靠背的虚拟网卡(a 端留宿主、b 端塞进 jail 的 vnet)。正常生命周期是:建对 → b 端 move 进 vnet 一次。
问题出在「再来一次」。只要 jail 走过 restart(或 stop + start),jail(8) 会把已经存在的那对 epair 再次 move 进 vnet——这一次之后,跨 vnet 的转发通道就废了:
- 两端
ifconfig都是UP/RUNNING bridge0的 member 列表照旧jls里 jail 好好地跑着- 但帧到了
epairXa就报错,出不去
这也是为什么它特别难查:所有「看状态」的命令都在告诉你没事。
有个反直觉的现象可以佐证配对关系没乱:
$ sudo service jail stop lims-pg
$ ifconfig -l | tr ' ' '\n' | grep epair10
epair10a
epair10b # ← 停 jail 后 b 端“回到”宿主 vnet
$ sudo ifconfig epair10a destroy
$ ifconfig -l | tr ' ' '\n' | grep epair10
# epair10b 跟着一起消失了 → 证明 a/b 确实是同一对,不是配对错乱
同一对、没配错、就是不转发。 所以修法只有一个:整对销毁重建。
五、修法:四步重建
NAME=lims-pg; N=10
sudo service jail stop $NAME
sudo ifconfig epair${N}a destroy # a 端一销毁,b 端连带消失
sudo ifconfig epair${N} create # 建出全新的 a/b 对
sudo ifconfig epair${N}a up # 少这一步会因为缺 IFF_UP 完全不通
sudo ifconfig bridge0 addm epair${N}a
sudo service jail start $NAME
验证:
$ ping -c 2 10.0.10.10
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.071/0.071/0.071/0.000 ms
$ nc -z -w 2 10.0.10.10 5432 && echo PG-REACHABLE
PG-REACHABLE
两个坑
坑一:bridge0 deletem / addm 没用。 这是网上最常见的建议(我自己的旧笔记里也这么写过),但成员关系本来就是对的,重置成员治不了「对端已死」。必须销毁重建。
坑二:别忘了 ifconfig epairXa up。 新建出来的 a 端默认不是 UP,只 addm 到 bridge 而不 up,现象和没修一样——很容易误判成「重建也没用」。
六、把一次性修复变成机制
手工修五个 jail 只是止血。下次谁再 service jail restart,同样的坑还在。所以把「重建」挂进 jail 的生命周期:
#!/bin/sh
# /usr/local/sbin/jail_epair.sh up|down <base> 例:jail_epair.sh up epair10
set -u
ACTION="$1"; BASE="$2"
case "${ACTION}" in
up)
if ifconfig "${BASE}a" >/dev/null 2>&1; then
exit 0 # 开机时 rc.conf 已建好,直接用
fi
ifconfig "${BASE}" create || exit 1
ifconfig "${BASE}a" up || exit 1
ifconfig bridge0 addm "${BASE}a"
;;
down)
# 停 jail 后 b 端会回到宿主 vnet,销毁 a 端即连带销毁 b 端,
# 保证下次 start 一定走 up 分支建出「全新的对」
ifconfig "${BASE}a" destroy >/dev/null 2>&1 || true
;;
esac
/etc/jail.conf 里每个成员加两行:
lims-pg {
vnet;
vnet.interface = epair10b;
exec.prestart = "/usr/local/sbin/jail_epair.sh up epair10";
exec.poststop = "/usr/local/sbin/jail_epair.sh down epair10";
...
}
为什么是 poststop 销毁,而不是 prestart 里判断「旧的是不是坏了」? 因为坏掉的那对在用户态根本看不出异常(这正是这次排错的全部难点)。既然判断不可靠,就不要判断——每次停机都销毁,每次启动都新建,让「复用旧对」这条路根本不存在。
改完实测:
$ sudo service jail restart lims-redis
$ netstat -I epair20a -b | tail -1
epair20a 1500 <Link#6> 58:9c:fc:10:22:0e 6 0 0 364 3 0 238 0
# ↑收 6 ↑发 3 ↑Oerrs=0
$ ping -c 2 10.0.10.20
2 packets transmitted, 2 packets received, 0.0% packet loss
顺便把这个「开机路径」单独验了一遍(不动生产 jail):新建一对 epair99、挂 bridge、再起一个临时 jail 把 b 端塞进去——Oerrs=0、ping 通。这说明首次建链没问题,问题只在复用,和上面的结论对得上。
七、第二个坑:它开机时压根没尝试启动
链路修好后 service lightmem start 两秒就起来了(不再等 60 秒)。但问题还没完:下次重启机器,它还会自动起来吗?
翻日志:
$ grep '\[boot\]' /var/log/lightmem.log | tail -3
2026/10/27 10:14:41 [boot] profile=family backend=pgvector tier=family
2026/10/27 10:14:41 [boot] listening on :18443
2026/11/06 08:12:12 [boot] ... # ← 这是我手工起的
11-05 那次开机(kern.boottime = 07:43:18)一条 [boot] 都没有。也就是说:二进制从未被拉起,它是卡在「等 PG」那一步就退出了,连日志都来不及写(超时消息走的是 stderr/console)。而且超时后不重试。
再看启动顺序,问题的全貌就出来了:
$ rcorder /etc/rc.d/* /usr/local/etc/rc.d/* | grep -nE '/(jail|lightmem|NETWORKING)$'
85:/etc/rc.d/NETWORKING
86:/usr/local/etc/rc.d/lightmem # ← 第 86 位
216:/etc/rc.d/jail # ← 第 216 位
lightmem 比 jail 早启动 130 个服务。 而它的等待窗口只有 60 秒——11-05 那天从开机(07:43:18)到 jail 起来(07:49:36)用了 6 分 18 秒。窗口永远等不到,这就是「必然失败」而不是「偶发失败」。
冷知识:.bak 脚本会被开机执行
顺手发现 rc.d 里躺着几个历史备份:lightmem.bak.r28、lightmem.bak.r29、lightmem_mcp.bak.r29——全部可执行,且都带 # PROVIDE: lightmem。
FreeBSD 的 /etc/rc 是把 /usr/local/etc/rc.d/* 整个 glob 喂给 rcorder 的,.bak 不会被跳过。也就是说以前每次开机,可能有多个「lightmem 启动脚本」一起跑。
结论:在
rc.d里留备份,等于留了个会在开机时执行的幽灵。备份请挪到rc.d目录之外。
八、三层兜底
修完之后是这样一套组合,而不是单点:
第一层:改依赖顺序。 让 lightmem 排在 jail 之后。
# REQUIRE: NETWORKING jail
改后 rcorder:jail 212 → lightmem 217 → lightmem_mcp 219。这里有个关键前提——jail 的 exec.start 是同步执行 /bin/sh /etc/rc 的,所以 service jail start 返回时,PG 已经在监听,不需要靠 sleep 猜。
第二层:放宽等待窗口。 lightmem_pg_wait 从默认 60 提到 180,纯粹当安全垫(比如 PG 要做 WAL 回放时)。
第三层:watchdog。 每 2 分钟一次,逻辑刻意做得「保守」:
# PG 不可达 → 只记日志,绝不动手
if ! nc -z -w 2 "${PG_HOST}" "${PG_PORT}" 2>/dev/null; then
if fetch -q -T 2 -o - "${KERNEL_URL}" >/dev/null 2>&1; then
log "WARN PG 不可达,但 kernel 仍在服务(PG 可能挂了)"
fi
exit 0
fi
# PG 可达但 18443 不健康 → 才拉起
fetch -q -T 3 -o - "${KERNEL_URL}" >/dev/null 2>&1 || service lightmem start
为什么 PG 挂了反而不动手? 两个原因:
- 强行拉起只会让进程反复启动失败、把日志刷满,掩盖真正的故障点;
- 「PG 不可达」和「lightmem 挂了」是两个故障,watchdog 不该把前者伪装成后者。
这个设计的价值在硬关机场景:断电后 PG 要走 WAL 崩溃恢复,可能远超 180 秒。这时 rc 那一轮会放弃,但 watchdog 会在 PG 就绪后的 2 分钟内把服务拉起来——把「开机一次性机会」变成了「持续收敛」。
实测(不是看着日志猜):
$ /usr/local/sbin/lightmem-watchdog.sh # 健康时:零动作,日志都没生成
$ sudo service lightmem stop && sudo service lightmem_mcp stop
$ /usr/local/sbin/lightmem-watchdog.sh
$ cat /var/log/lightmem-watchdog.log
09:14:48 [watchdog] kernel 18443 不健康,执行 service lightmem start
09:14:52 [watchdog] kernel 已恢复(pid 38046)
09:14:52 [watchdog] mcp 18444 不健康,执行 service lightmem_mcp start
09:14:55 [watchdog] mcp 已恢复
九、可以抄走的七条
- 状态类命令(
ifconfig/jls/service status)会骗人,计数器不会。 链路怀疑时先netstat -I <if> -b,看Ipkts/Opkts/Oerrs,别只看status: active。 bridge0 addr的 MAC 表为空,是个极强的负向信号。 桥连自己的成员都没学到东西,说明帧根本没进出。- 抓不了包就比计数。 两端
netstat -I的收发差,等价于一次单向 tcpdump,还不用碰 devfs/BPF 权限。 - vnet jail 的 epair 是一次性用品。 只要走过
restart,就重建;判断「坏没坏」不如直接重建便宜。 - 验证服务别只看端口。
healthz说status: ok可能只读了配置;跑一次真实的业务请求(这里是POST /tools/memory/recall拿到命中)才算数。 - 「起不来」和「没启动」是两件事。 先看日志里有没有启动记录(
[boot]之类),没有就说明它卡在启动前的检查里,方向完全相反。 - 排错笔记要写「怎么判断」,不要只写「怎么修」。 这次能一次定位,靠的是旧记忆里那条「
epairXa计数异常」的线索;而旧记忆里「bridge0 deletem/addm可修」那条是错的——错的结论会原样传给自己下一次,所以修完要回去改掉它。
整件事最值得记的不是那四步命令,而是一个教训:
当所有「状态」都正常、只有「行为」不正常时,说明你在看错的东西。
ifconfig 看的是配置,ping 看的是行为。这次配置全对、行为全错,差的就是那两行不该为 0 的计数器。
更多推荐


所有评论(0)