虚拟机通过 nbd 秒级启动,但 nbd-server 连接总被误清理。根因是新版本 nbd-server 默认用 tcp6 双栈监听,而清理代码用 netstat -4 只过滤 tcp4,把 tcp6 上的连接误判为"无连接"而误清理。

现象

虚拟机通过 nbd 可以实现秒级启动,之前跑的好好的。 最近一个测试环境,总是会出现 nbd 连接丢失的情况。

排查是 base img 的 nbd-server 进程被 kill。 且通过日志看,就是我们的程序给 kill 的。

但是我们的程序是正常的清理,会判断这个 nbd-server 没有连接,才会去 kill。 到底是哪里的问题?是真没有连接了,还是哪里判断有问题。

排查

代码会先检查已经有连接的,这些是肯定不会去 kill:

exec_cmd("netstat -4 --tcp -p -n | grep ESTABLISHED | grep nbd-server | awk '{print $4}' | awk -F':' '{print $2}' | sort -u > %s" % tmp_file)

代码已经十年了,看着也很完善,没出过类似问题。

后发现是 tcp6 和 tcp4 的原因,和 nbd-server 的版本有关系:

  • 以前部署的是 This is nbd-server version 3.9.1
  • 现在部署的是 This is nbd-server version 3.23

看起来新版本默认用的是 tcp6 双栈,老版本默认是 tcp4。

新版本 nbd-server 的监听是这样:

root@:~# netstat -tpln |grep nbd-server
tcp6        0      0 :::43403           :::*               LISTEN      2468990/nbd-server

老版本 nbd-server 的监听是这样:

root@host:~# netstat -tpln |grep nbd-server
tcp        0      0 0.0.0.0:43403           0.0.0.0:*               LISTEN      2468990/nbd-server

关键区别在于,开头的 tcp6 和 tcp,还有 ::: 和 0.0.0.0。

根因

查看内核参数 net.ipv6.bindv6only 是 0,也就是说默认是开启双栈的。内核参数就不改了。

  • 双栈兼容处理:当一个程序在 IPv6 套接字(AF_INET6)上监听时,如果未显式启用 IPV6_V6ONLY 选项,这个监听套接字就可以同时接收 IPv4 和 IPv6 的传入连接。
  • 协议栈隐式转换:当纯 IPv4 客户端连接进来时,Linux 内核会自动将该 IPv4 地址转换为 IPv6 格式(例如将 10.0.0.10 在内部表示为 ::ffff:10.0.0.10),并通过 IPv6 的套接字来处理这个连接。
  • netstat 展示机制:因为这个连接在系统内核层面被归类在 tcp6 套接字下处理,所以 netstat 读取 /proc/net/tcp6 时就会把它显示为 tcp6,即便输出界面上渲染出的远端 IP 依然是 IPv4 的格式。

解决办法

现在有两种方式:

  1. 修改代码,netstat 不加 -4,这样 tcp 和 tcp6 都能过滤到;
  2. 应该有配置,约束 nbd-server 的行为,让其和老版本一样,只启动 tcp4 的连接。

调整配置:各种 AI 都是这个结论:

/etc/nbd-server/config
[generic]
bindaddr = 0.0.0.0

一顿折腾猛如虎,回头一看原地杵。后查看手册发现正解:

man 5 nbd-server
listenaddr = 0.0.0.0

一点思考

虽然 2026 年了,虽然 AI 很强了,但是这个问题依然需要人去解决。 前面各种 AI 给的解法都是:一直不行,AI 说各种格式问题,疯狂尝试。 结果可想而知,从源头参数就是错的,围绕错误的开头,AI 做再多都是徒劳,浪费算力和时间。 从这个角度看,AI 是个很强的智能工具,依然需要人去驾驭。

有些时候,这取舍啊,真是理解不了。 这里为啥要用 netstat -4 呢?是怕匹配到非我们平台的 nbd,导致我们漏清理了吗? 但是漏清理就是多占用个进程,而误清理,直接影响虚拟机运行,这是要直接中断业务的呀。 这两个结果,怎么能选择规避漏清理,而埋下误清理的选择呢,不能理解!

小结

  • 新版本 nbd-server 默认用 tcp6 双栈监听,老版本默认 tcp4;
  • 清理代码用 netstat -4 只过滤 tcp4,导致 tcp6 上的连接被误判为"无连接"而误清理;
  • 双栈下 IPv4 连接也会被归类到 tcp6 套接字,netstat -4 会漏掉;
  • 正解是 listenaddr = 0.0.0.0(不是 bindaddr);
  • 排查问题要从源头参数入手,AI 再强也需要人去驾驭。