LXC 销毁后,hyper 内核报 unregister_netdevice 网卡错误,困扰了很久。根因是网卡 down 后 IPv6 协议栈仍持有 netdevice 引用,在 hyper 中加一个 disable_ipv6=1 的操作就好了。
现象
LXC 销毁后,hyper 内核报网卡错误 unregister_netdevice,这个问题困扰了很久没有解决。
按道理,应该是清理网卡之前,清理相关资源。但我们的程序是直接 down 网卡。
后来有人在网卡 down 后加了一个清理 ipv6 neigh 的操作,然后就好了。卧槽。
排查
先看接口没 down 的时候,比如 eth0 上有 neigh:
root@host:~# ip n s
10.0.0.252 dev eth1 lladdr 36:95:2f:f5:af:85 STALE
10.0.0.1 dev eth0 lladdr 02:54:d7:af:4b:cb STALE
10.0.0.253 dev eth1 lladdr 52:54:d7:c6:e8:47 STALE
fe80::5054:d7ff:fec6:e847 dev eth1 lladdr 52:54:d7:c6:e8:47 router STALE
fd00:2024:822:0:167:ffff:ffff:fffd dev eth1 lladdr 52:54:d7:c6:e8:47 router STALE
fe80::e01e:53ff:fe85:5ca0 dev eth0 lladdr 02:54:d7:af:4b:cb router STALE
把接口 eth0 down 掉:
root@host:~# ip link set dev eth0 down ; ip n s
10.0.0.252 dev eth1 lladdr 36:95:2f:f5:af:85 STALE
10.0.0.253 dev eth1 lladdr 52:54:d7:c6:e8:47 STALE
fe80::5054:d7ff:fec6:e847 dev eth1 lladdr 52:54:d7:c6:e8:47 router STALE
fd00:2024:822:0:167:ffff:ffff:fffd dev eth1 lladdr 52:54:d7:c6:e8:47 router STALE
已经看不到 eth0 的 neigh 了。
尝试的修复
一开始加的是清理 neigh 的操作:
def clear_ip_neigh(instance_id):
cmd = "ip neigh show | grep -E '%s'" % '|'.join(hwaddrs)
ret = exec_cmd(cmd)
ip_neighs = []
...
# 网卡 down 掉之后应该就执行不到下面,但是问题就是好了,这也太牛逼了。
for ip_neigh in ip_neighs:
exec_cmd("ip neigh del %s" % ip_neigh)
神奇呀。但最后发现,生效的可能不是上面这段,而是下面这段:
def let_iface_degraded(instance_id):
""" let interfaces degraded in the local host. """
ifaces_in_host = get_interfaces_in_host(instance_id)
# 1. set dev down
for iface in ifaces_in_host:
exec_cmd("ip link set dev %s down;" % iface)
# 2. disable ipv6
for iface in ifaces_in_host:
exec_cmd("sysctl -w net.ipv6.conf.%s.disable_ipv6=1;" % iface)
return
重点是:加了一个 disable_ipv6 的操作,是在 hyper 中执行的。
disable_ipv6=1 才是针对性的关键修复。
根因
问题出在 unregister_netdevice 的竞态窗口:
LXC stop 时,容器网络命名空间(netns)在销毁前,veth pair 的宿主侧接口上 IPv6 协议栈仍在运行,可能还在:
- 响应 Router Solicitation / Router Advertisement 消息
- 进行 Duplicate Address Detection (DAD) 检测
- 持有未清理的 IPv6 neighbor entries
此时如果内核正在执行 unregister_netdevice,而 IPv6 stack 仍然持有对该 netdevice 的引用,就会触发类似:
unregister_netdevice: waiting for eth0 to become free. Usage count = 1
小结
- 清理网卡前,要先清理相关资源(neigh、IPv6 协议栈等);
unregister_netdevice的竞态窗口是根因,IPv6 协议栈持有 netdevice 引用导致无法释放;- 关键修复是
disable_ipv6=1,在 hyper 中执行; - 底层操作往往"不讲道理",多尝试认知之外的事情,往往有意外收获。