![CoreOS:tcpdump 神秘地解决了网络问题(使用的套接字数量过多)](https://linux22.com/image/735496/CoreOS%EF%BC%9Atcpdump%20%E7%A5%9E%E7%A7%98%E5%9C%B0%E8%A7%A3%E5%86%B3%E4%BA%86%E7%BD%91%E7%BB%9C%E9%97%AE%E9%A2%98%EF%BC%88%E4%BD%BF%E7%94%A8%E7%9A%84%E5%A5%97%E6%8E%A5%E5%AD%97%E6%95%B0%E9%87%8F%E8%BF%87%E5%A4%9A%EF%BC%89.png)
今天我要告诉你一个谜题。我们在 Azure 上运行一个基于 CoreOS(2023.5.0 / Linux 4.19.25-coreos)的小型三节点 Elasticsearch 集群。Elasticsearch 在主机网络模式下的 docker 容器内运行。在几乎完全免维护运行了一年多之后,我们看到机器进入了一个非常有趣的状态。
更新
这个问题已通过修复驱动程序得到解决Linux 内核. 请参阅下面的答案。
症状
基本上,受影响的机器与其他两个节点之间的网络连接中断。所有节点都位于同一个虚拟网络和同一个子网中,通常可以相互通信。仍然可以从其他子网(我可以通过 ssh 进入)和不同的对等虚拟网络访问受影响的节点。该机器还具有(非常不稳定的)互联网连接,但大多数请求都超时了。
我们观察到,在受影响的节点上,报告的“已使用套接字”数量/proc/net/sockstat
非常高(约 4.5k,而健康节点上约为 300)。监控显示,从节点不可用的那一刻起,该数字迅速上升。
有趣的是,我们似乎无法识别这些使用过的插座的来源:
# cat /proc/net/sockstat
sockets: used 4566
TCP: inuse 2 orphan 0 tw 2 alloc 98 mem 4
UDP: inuse 1 mem 0
UDPLITE: inuse 0
RAW: inuse 0
FRAG: inuse 0 memory 0
# cat /proc/net/sockstat6
TCP6: inuse 98
UDP6: inuse 1
UDPLITE6: inuse 0
RAW6: inuse 1
FRAG6: inuse 0 memory 0
除此之外,机器看起来还不错。没有可疑进程在运行,CPU 使用率极低,并且有足够的可用内存。
对同一子网中的“无法访问”虚拟机执行 ping 操作,将导致一些EAGAIN
响应recvmsg
,然后ENOBUFS
从sendmsg
。strace ping 输出在这里
我收集了一些额外的输出(在对系统进行任何修改之前)并将其发布在此要点中: https://gist.github.com/privatwolke/e7e2e7eb0272787765f5d3726f37107c
分析
我们尝试关闭服务器上所有我们能想到的东西,其中 elasticsearch 是第一个怀疑的对象。但是关闭 elasticsearch 容器并不能释放使用的套接字。所有与 CoreOS 相关的进程(update-engine、locksmithd 等)或甚至整个 Docker 运行时或 Azure 特定的东西都是如此。似乎没有什么帮助。
但现在情况变得更加奇怪了:我们试图跑tcpdump
在机器上运行以查看发生了什么。结果:问题自行解决,连接恢复。我们的理论是 tcpdump 执行了某种系统调用来解决这个问题。我们用 gdb 运行 tcpdump 并在所有系统调用上设置断点。在遍历大量断点后,我们最终发现在捕获套接字上设置混杂模式(具体来说libpcap 中的这一行) 是重置套接字使用计数器并使我们恢复正常状态的东西。
其他发现
- 我们已经验证,
tcpdump
使用该-p/--no-promiscuous-mode
标志运行不会清除套接字使用计数器并使机器返回到可用状态。 - 跑步
ifconfig eth0 txqueuelen 1001
会重置使用的插座但连接性不是恢復。 - 手动设置 promisc 模式
ip link set eth0 promisc on
也不能恢复连接。net.ipv4.xfrm4_gc_thresh
设置为 32768,稍微增加它并不能解决问题。
我们联系了 Azure,他们和我们一样对此感到困惑。我知道这可能不是问题,而只是一种症状。但这是我迄今为止发现的唯一有形的东西。我希望通过了解症状,我可以更接近根本原因。Azure 上的网络接口运行这个网络驱动程序。
也许是 CoreOS/Kernel 的错?
从时间线来看,问题始于 2019-03-11,即 CoreOS 自动更新到最新版本的那一天。根据发布说明,此更新包含内核更新自4.15.23 至 4.19.25。我仍在查看变更日志,看看是否有任何问题。到目前为止,我只发现 hyperv 网络驱动程序最近几个月收到了不少更新,其中似乎并非全部都是 4.19.25 的一部分。CoreOS 4.19.25 补丁集并不令人印象深刻,但引入假的 nf_conntrack_ipv4 模块的补丁是新的。
帮助!
到目前为止,我们的问题如下:
什么原因会导致“已使用插座”指标飙升?我已阅读内核源代码对于这个指标来说,似乎只是一个柜台没有提及这些插座实际上是什么类型的插座或是什么创建了它们。
为什么这个数字在 4.5k 左右就持平了?哪个限制会导致这种情况?
内核 4.14.96 和 4.19.25 之间有什么重大变化吗?
为什么
setsockopt()
libpcap 中的调用会重置状态?
相关 CoreOS 错误:https://github.com/coreos/bugs/issues/2572
答案1
首先,感谢您提出如此出色的问题!
由于您描述的详细程度非常高,并且您已经处于 gdb 级别,因此我认为我的答案对您没有多大用处。无论如何,请尝试一下:
- 想必您已经尝试过类似的事情
ss -ae
?lsof -n
- 当发生这种情况时,是否会
dmesg
返回一些有趣的东西? - 你在服务器上使用 iptables 吗?
- 如果您使用 tcpdump 以外的其他方式(例如
ip link set [interface] promisc on
)设置混杂模式,这也能解决问题吗? - 您是否检查过任何可疑进程、文件或其他奇怪活动?您是否在想,也许一些不速之客进程潜伏在阴影中,隐藏自身,并且只要设置了混杂模式就会保持沉默?
- 如果让 tcpdump 在后台运行,这个问题还会再次出现吗?
我希望这有帮助。
答案2
这是由 Linux 内核中的 hv_netsvc 驱动程序的一个错误引起的。我们与 Microsoft 开发人员一起解决了这个问题,并设法将修复程序应用到上游。
我将在这里引用提交消息,因为它很好地总结了这个问题:
当环形缓冲区由于 RX 完成消息而几乎已满时,TX 数据包可能会达到“低水位线”并导致队列停止。如果 TX 完成比队列停止更早到达,则可能会错过唤醒。
此补丁将对最后一个待处理数据包的检查移至涵盖 EAGAIN 和成功情况,因此队列将在必要时可靠地被唤醒。
供将来参考,修复此问题的提交是https://github.com/torvalds/linux/commit/6d9cfab853ca60b2f77b5e4c40443216988cba1f。