建立连接后 TCP 请求卡住

article/2025/7/23 1:28:43

大家读完觉得有意义记得关注和点赞!!!

这篇文章描述了一个内核和BPF网络问题 以及故障排除步骤,这是一个值得深入研究的有趣案例 Linux 内核网络复杂性。

目录

1 故障报告

1.1 现象:概率健康检查失败

1.2 范围:特定节点上的特定 Pod

2 网络基础知识

2.1 节点网络拓扑:Cilium(带 BPF)

2.2 内核:node2localPod 流量的 sockmap BPF 加速5.10+

2.2.1 BPF:本地流量绕过内核堆栈sockops

2.2.2:只能捕获 TCP 握手数据包tcpdump3-way/4-way

2.3 总结

3 快速缩小范围

3.1 快速复制

3.2 缩小问题范围

3.2.1 : 正常,排除 L2/L3 问题ping

3.2.2 连接测试:OK,排除 TCP 连接问题telnet

3.2.3 远程到本地Pod:好的,排除pod问题和vanilla内核堆栈问题curl

3.2.4 本地 pod to-pod:好的,排除一些 node 内部问题

3.3 总结:只有 node-to-localPod TCP 请求概率卡住

4 深入挖掘

4.1 Linux 与 AliOS 内核

4.1.1 比较 BPF 功能

4.1.2 AliOS 云内核特定变更

4.2 查看详细的 TCP 连接统计信息

4.2.1 正常情况:显示正确sssegs_out/segs_in

4.2.2 异常情况:显示不正确sssegs_out/segs_in

4.3 Trace related call stack

4.3.1 : 跟踪内核调用堆栈trace-cmd

4.3.2 找到代码:inet_recvmsg -> {tcp_bpf_recvmsg, tcp_recvmsg}

4.3.3 使用 bpftrace 进行仔细检查

4.3.4 总结

4.4 内核堆栈中的处理程序初始化recvmsg

4.5 确认 sockmap 中的过时条目

4.5.1: 套接字处理程序 (sk_prot) 不正确bpftracetcp_bpf_get_prot()

4.5.2bpftrace sk_psock_drop

4.5.3 bpftool:在 sockops 映射中确认过时的连接信息

5 技术总结

5.1 普通的 sockops/sockmap BPF 工作流程

5.2 直接原因

5.3 根本原因

5.4 快速恢复/修复

5.5 具有类似现象的另一个问题

附录

引用


 

1 故障报告

1.1 现象:概率健康检查失败

用户报告了他们的 Pod 的间歇性故障, 尽管如此,它们还是照常运行,没有例外。

运行状况检查是一个非常简单的 TCP 上的 HTTP 探测: kubelet 定期(例如每 5 秒)向本地 Pod 发送请求, 为每个请求启动新的 TCP 连接。GET

 

无花果。Pod 间歇性健康检查失败。

用户怀疑这是网络问题。

1.2 范围:特定节点上的特定 Pod

这个报告的问题仅限于一个新的 k8s 集群,最近引入了操作系统和内核

  • 操作系统: AliOS (AlibabaCloud OS)
  • 内核:5.10.134-16.al8.x86_64(Linux 的一个分支,gitee.com/anolis/cloud-kernel),其中包括他们的 上游功能向后移植和自我维护的更改,例如cloud-kernel

    1. 适用于 AI 工作负载的 Intel AMX (Advanced Matrix Extensions), 在某些情况下提供 GPU 的硬件加速替代方案, 例如对小于 13B 的 LLM 进行推理。 AMX 支持首次在内核 5.16 中引入,并将该功能向后移植到其当前版本 5.10cloud-kernel;
    2. cloud-kernel包括未上游的修改,如新的内核结构字段和新的枚举/类型。

其他环境信息:

  • Cilium:自维护 v1.11.10
  • CNCF 案例研究:Trip.com Group 如何改用 Cilium 实现可扩展和云原生网络,2023 年

2 网络基础知识

在开始探索之前,让我们概述一下这个集群中的网络基础设施。

2.1 节点网络拓扑:Cilium(带 BPF)

我们的 k8s 节点的内部网络拓扑图描述如下:

 

无花果。k8s 节点的内部网络拓扑。

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>k8s node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>route <span style="color:#000080">-n</span>
Destination  Gateway   Genmask           Use Iface
0.0.0.0      <GW-IP>   0.0.0.0           eth0
<Node-IP>    0.0.0.0   <Node-IP-Mask>    eth0
<Pod1-IP>    0.0.0.0   255.255.255.255   lxc-1
<Pod2-IP>    0.0.0.0   255.255.255.255   lxc-2
<Pod3-IP>    0.0.0.0   255.255.255.255   lxc-3
</code></span></span></span>

如图和内核路由表输出所示,每个 Pod 都有一个专用的路由条目。 因此,所有运行状况检查流量都直接定向到 lxc 设备 (Pod 的 veth 对的主机端设备),随后进入 Pod。 换句话说,所有运行状况检查流量都在本地处理

Cilium 在 AlibabaCloud 上的网络拓扑与 AWS 上的网络拓扑类似。 有关更多信息,请参阅 AWS 上的 Cilium 网络拓扑和流量路径 (2019)。 其中可能包含一些过时的信息,但大多数内容仍应验证。

2.2 内核:node2localPod 流量的 sockmap BPF 加速5.10+

2.2.1 BPF:本地流量绕过内核堆栈sockops

 

如何使用 eBPF 加速云原生 applications 提供了一个实际示例,说明了 sockops/sockmap BPF 程序是如何工作的。

中文读者也可以参考以下内容了解更多信息,

  1. (译)利用 ebpf sockmap/redirection 提升 socket 性能(2020)
  2. BPF 进阶笔记(五):几种 TCP 相关的 BPF(sockops、struct_ops、header options)

2.2.2:只能捕获 TCP 握手数据包tcpdump3-way/4-way

sockops加速在 kernel 5.10 + Cilium v1.11.10 中自动开启:

 

无花果。Cilium 中的 socket 级加速。 请注意,该图描述了通过环回进行通信的本地进程,这与此处讨论的场景不同。 只是懒得画一张新图。

一个重大的概念变化是,启用 sockops BPF 后,您无法 在TCPDUMP输出中查看请求和响应数据包,因为在此设置中,只有TCP 3次握手和4次关闭程序仍然通过内核网络。 stack 中,所有有效负载都将直接通过 socket 级别的(例如在 tcp/udp 发送/接收消息)方法。

一个快速测试来说明这个想法:从节点访问 Pod 中的服务器:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>curl <pod ip>:<port>
</code></span></span></span>

tcpdump 的输出:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>pod<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>tcpdump <span style="color:#000080">-nn</span> <span style="color:#000080">-i</span> eth0 host <node ip> and <port>
<span style="color:#999988"><em># TCP 3-way handshake</em></span>
IP NODE_IP.36942 <span style="color:#000000"><strong>></strong></span> POD_IP.8080: Flags <span style="color:#000000"><strong>[</strong></span>S]
IP POD_IP.8080   <span style="color:#000000"><strong>></strong></span> NODE_IP.36942: Flags <span style="color:#000000"><strong>[</strong></span>S.]
IP NODE_IP.36942 <span style="color:#000000"><strong>></strong></span> POD_IP.8080: Flags <span style="color:#000000"><strong>[</strong></span>.]<span style="color:#999988"><em># requests & responses, no packets go through there, they are bypassed,</em></span>
<span style="color:#999988"><em># payloads are transferred directly in socket-level TCP methods</em></span><span style="color:#999988"><em># TCP 4-way close</em></span>
IP POD_IP.8080   <span style="color:#000000"><strong>></strong></span> NODE_IP.36942: Flags <span style="color:#000000"><strong>[</strong></span>F.]
IP NODE_IP.36942 <span style="color:#000000"><strong>></strong></span> POD_IP.8080: Flags <span style="color:#000000"><strong>[</strong></span>.]
IP NODE_IP.36942 <span style="color:#000000"><strong>></strong></span> POD_IP.8080: Flags <span style="color:#000000"><strong>[</strong></span>F.]
IP POD_IP.8080   <span style="color:#000000"><strong>></strong></span> NODE_IP.36942: Flags <span style="color:#000000"><strong>[</strong></span>.]
</code></span></span></span>

2.3 总结

现在我们已经对问题和环境有了基本的了解。 是时候深入研究实际调查了。

3 快速缩小范围

3.1 快速复制

首先,检查 kubelet 日志,

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span><span style="color:#0086b3">grep</span> <span style="color:#dd1144">"Timeout exceeded while awaiting headers"</span> /var/log/kubernetes/kubelet.INFO
prober.go] Readiness probe <span style="color:#000000"><strong>for </strong></span>POD_XXX failed <span style="color:#000000"><strong>(</strong></span>failure<span style="color:#000000"><strong>)</strong></span>:Get <span style="color:#dd1144">"http://POD_IP:PORT/health"</span>: context deadline exceeded <span style="color:#000000"><strong>(</strong></span>Client.Timeout exceeded <span style="color:#000000"><strong>while </strong></span>awaiting headers<span style="color:#000000"><strong>)</strong></span>
...
</code></span></span></span>

事实上,存在许多就绪度探测失败。

由于探针是非常简单的 HTTP 请求,我们可以在节点上手动进行, 这应该等同于 kubelet 探针

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>curl <POD_IP>:<PORT>/v1/health
OK
<span style="color:#008080">$ </span>curl <POD_IP>:<PORT>/v1/health
OK
<span style="color:#008080">$ </span>curl <POD_IP>:<PORT>/v1/health <span style="color:#999988"><em># stuck</em></span>
^C
</code></span></span></span>

好的,我们可以轻松复现它,而无需依赖 k8s 工具。

3.2 缩小问题范围

现在让我们执行一些快速测试来缩小问题范围。

3.2.1 : 正常,排除 L2/L3 问题ping

ping来自节点的 PodIP 始终成功

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>ping <POD_IP>
</code></span></span></span>

这表明L2和L3(ARP表、路由表等)的连接运行良好。

3.2.2 连接测试:OK,排除 TCP 连接问题telnet

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>telnet POD_IP PORT
Trying POD_IP...
Connected to POD_IP.
Escape character is <span style="color:#dd1144">'^]'</span><span style="color:#0086b3">.</span>
</code></span></span></span>

同样,始终成功,输出确认连接始终进入 ESTABLISHED 状态:ss

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>netstat <span style="color:#000080">-antp</span> | <span style="color:#0086b3">grep </span>telnet
tcp        0      0 NODE_IP:34316    POD_IP:PORT     ESTABLISHED 2360593/telnet
</code></span></span></span>

3.2.3 远程到本地Pod:好的,排除pod问题和vanilla内核堆栈问题curl

从远程节点执行相同的运行状况检查,始终正常:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node2<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>curl <POD_IP>:<PORT>/v1/health
OK
...
<span style="color:#000000"><strong>(</strong></span>node2<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>curl <POD_IP>:<PORT>/v1/health
OK
</code></span></span></span>

这排除了 Pod 本身和原版内核堆栈的问题。

3.2.4 本地 pod to-pod:好的,排除一些 node 内部问题

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>pod3<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>curl <POD2_IP>:<PORT>/v1/health
OK
...
<span style="color:#000000"><strong>(</strong></span>pod3<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>curl <POD2_IP>:<PORT>/v1/health
OK
</code></span></span></span>

始终正常。排除 Pod 本身的问题。

3.3 总结:只有 node-to-localPod TCP 请求概率卡住

 

无花果。测试用例和结果。

三种情况的区别:

  1. Node-to-localPod:payload 流量通过 sockops BPF 处理;
  2. 本地 Pod 到 PodBPF 重定向(或内核堆栈,具体取决于您的内核版本)
    • 区分三种类型的 eBPF 重定向 (2022)
  3. RemoteNode-to-localPod:标准内核网络堆栈

结合这些信息,我们确信问题已经 与 sockops BPF 和 kernel 的关系(因为 kernel 在 sockops BPF 场景中完成了大部分工作)。

从这些观察中,可以合理地推断出该问题很可能 与 sockops BPF 和内核相关,给定 kernel 在 sockops BPF 场景中的核心作用。

4 深入挖掘

现在让我们更深入地探讨这个问题。

4.1 Linux 与 AliOS 内核

由于我们一直在使用内核 5.10.56 和 cilium v1.11.10 多年未遇到过这个问题,第一个合理的 假设 AliOS 云内核 5.10.134 可能会引入一些不兼容的更改或 bug。

因此,我们花了一些时间将 AliOS 云内核与上游 Linux 进行比较。

注意:cloud-kernel 维护在 gitee.com,它限制了大多数读取权限(例如提交、责备)而无需登录, 因此,在本文的其余部分,我们引用了 github.com 上的 Linux 存储库进行讨论。

4.1.1 比较 BPF 功能

首先,比较节点上 cilium-agent 自动检测到的 BPF 特征。 结果将写入节点上的本地文件:/var/run/cilium/state/globals/bpf_features.h

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>diff <bpf_features.h from our 5.10.56 node> <bpf_features.h from AliOS node>
</code></span></span></span>
<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code>59c59
<span style="color:#000000"><span style="background-color:#ffdddd">< #define NO_HAVE_XSKMAP_MAP_TYPE
</span></span>---
<span style="color:#000000"><span style="background-color:#ddffdd">> #define HAVE_XSKMAP_MAP_TYPE
</span></span>71c71
<span style="color:#000000"><span style="background-color:#ffdddd">< #define NO_HAVE_TASK_STORAGE_MAP_TYPE
</span></span>---
<span style="color:#000000"><span style="background-color:#ddffdd">> #define HAVE_TASK_STORAGE_MAP_TYPE
</span></span>243c243
<span style="color:#000000"><span style="background-color:#ffdddd">< #define BPF__PROG_TYPE_socket_filter__HELPER_bpf_ktime_get_coarse_ns 0
</span></span>---
<span style="color:#000000"><span style="background-color:#ddffdd">> #define BPF__PROG_TYPE_socket_filter__HELPER_bpf_ktime_get_coarse_ns 1
</span></span><span style="color:#a61717"><span style="background-color:#e3d2d2">...</span></span>
</code></span></span></span>

确实存在一些差异,但通过进一步调查,我们没有 查找与观察到的问题的任何关联。

4.1.2 AliOS 云内核特定变更

然后我们花了一些时间检查了 AliOS 云内核自维护的 BPF 和网络修改。 如

  1. b578e4b8ed6e1c7608e07e03a061357fd79ac2ddCK: NET: 跟踪创建 sock 的 PID

    在此提交中,他们向 struct sock 数据结构添加了一个字段。pid_t pid

  2. ea0307caaf29700ff71467726b9617dcb7c0d084tcp:确保初始化 accept_queue 的 spinlocks 一次

但同样,我们没有发现与问题有任何关联。

4.2 查看详细的 TCP 连接统计信息

由于没有来自代码比较的有价值的信息,我们将重点转移到了环境 收集一些更详细的连接信息。

ss(socket stats) 是一个强大而方便的 socket/connection 自省工具:

  • -i/--info:显示内部 TCP 信息,包括几个 TCP 连接统计信息;
  • -e/--extended:显示详细的 socket 信息,包括 inode、uid、cookie。

4.2.1 正常情况:显示正确sssegs_out/segs_in

使用 (netcat) 启动连接,nc

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>nc POD_IP PORT
</code></span></span></span>

我们在这里故意不使用 telnet,因为会关闭连接 在成功提供请求后立即,我们没有时间检查 SS 输出中的连接统计信息。 将使连接保持 CLOSE-WAIT 状态,这足以让我们检查连接发送/接收统计信息。telnetnc

现在是这个连接的统计数据:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>ss <span style="color:#000080">-i</span> | <span style="color:#0086b3">grep</span> <span style="color:#000080">-A</span> 1 50504
tcp    ESTAB      0         0         NODE_IP:50504          POD_IP:PORTcubic wscale:7,7 rto:200 rtt:0.059/0.029 mss:1448 pmtu:1500 rcvmss:536 advmss:1448 cwnd:10 bytes_acked:1 segs_out:2 segs_in:1 send 1963.4Mbps lastsnd:14641 lastrcv:14641 lastack:14641 pacing_rate 3926.8Mbps delivered:1 rcv_space:14480 rcv_ssthresh:64088 minrtt:0.059
</code></span></span></span>

发送和接收统计数据:segs_out=2,segs_in=1

现在让我们向服务器发送请求:键入然后按 ,GET /v1/health HTTP/1.1\r\nEnter

实际上,您可以键入任何内容,只需 ,服务器很可能会 向您发送 400 (Bad Request) 响应,但对于 在我们的例子中,这个 400 表示 TCP 发送/接收路径完全正常!Enter

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>nc POD_IP PORT
GET /v1/health HTTP/1.1<span style="color:#dd1144">\r\n</span>
<Response Here>
</code></span></span></span>

我们将得到响应,连接将进入状态, 在它消失之前,我们有一些时间来检查它:CLOSE-WAIT

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>ss <span style="color:#000080">-i</span> | <span style="color:#0086b3">grep</span> <span style="color:#000080">-A</span> 1 50504
tcp     CLOSE-WAIT   0      0        NODE_IP:50504     POD_IP:httpcubic wscale:7,7 rto:200 rtt:0.059/0.029 ato:40 mss:1448 pmtu:1500 rcvmss:536 advmss:1448 cwnd:10 bytes_acked:1 bytes_received:1 segs_out:3 segs_in:2 send 1963.4Mbps lastsnd:24277 lastrcv:24277 lastack:4399 pacing_rate 3926.8Mbps delivered:1 rcv_space:14480 rcv_ssthresh:64088 minrtt:0.059
</code></span></span></span>

正如预期的那样,segs_out=3,segs_in=2

4.2.2 异常情况:显示不正确sssegs_out/segs_in

重复上述测试以捕获失败的 1 个。

建立连接后,

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>ss <span style="color:#000080">-i</span> | <span style="color:#0086b3">grep</span> <span style="color:#000080">-A</span> 1 57424
tcp      ESTAB      0       0         NODE_IP:57424    POD_IP:webcachecubic wscale:7,7 rto:200 rtt:0.056/0.028 mss:1448 pmtu:1500 rcvmss:536 advmss:1448 cwnd:10 bytes_acked:1 segs_out:2 segs_in:1 send 2068.6Mbps lastsnd:10686 lastrcv:10686 lastack:10686 pacing_rate 4137.1Mbps delivered:1 rcv_space:14480 rcv_ssthresh:64088 minrtt:0.056
</code></span></span></span>

键入请求内容并抚摸 后:Enter

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>ss <span style="color:#000080">-i</span> | <span style="color:#0086b3">grep</span> <span style="color:#000080">-A</span> 1 57424
tcp      ESTAB      0       0         NODE_IP:57424    POD_IP:webcachecubic wscale:7,7 rto:200 rtt:0.056/0.028 mss:1448 pmtu:1500 rcvmss:536 advmss:1448 cwnd:10 bytes_acked:1 segs_out:2 segs_in:1 send 2068.6Mbps lastsnd:21994 lastrcv:21994 lastack:21994 pacing_rate 4137.1Mbps delivered:1 rcv_space:14480 rcv_ssthresh:64088 minrtt:0.056
</code></span></span></span>

That segments sent/received stats remain unchanged (), suggesting that the problem may reside on tcp {send,receive} message level.segs_out=2,segs_in=1

4.3 Trace related call stack

Based on the above hypothesis, we captured kernel call stacks to compare failed and successful requests.

4.3.1 : 跟踪内核调用堆栈trace-cmd

跟踪 10 秒,按服务器进程 ID 过滤,保存调用堆栈图,

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em># filter by process ID (PID of the server in the pod)</em></span>
<span style="color:#008080">$ </span>trace-cmd record <span style="color:#000080">-P</span> 178501 <span style="color:#000080">-p</span> function_graph <span style="color:#0086b3">sleep </span>10
</code></span></span></span>

注意:避免在生产环境中进行跟踪,以防止生成大文件和过多的磁盘 IO。

在此期间,发送请求,

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>curl POD_IP PORT
</code></span></span></span>

默认情况下,它会将数据保存到当前目录下的本地文件中,内容如下所示:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>trace-cmd report <span style="color:#000000"><strong>></strong></span> report-1.graph
CPU  1 is empty
CPU  2 is empty
...
CPU 63 is empty
<span style="color:#008080">cpus</span><span style="color:#000000"><strong>=</strong></span>64<idle>-0     <span style="color:#000000"><strong>[</strong></span>022] 5376816.422992: funcgraph_entry:    2.441 us   |  update_acpu.constprop.0<span style="color:#000000"><strong>()</strong></span>;<idle>-0     <span style="color:#000000"><strong>[</strong></span>022] 5376816.422994: funcgraph_entry:               |  switch_mm_irqs_off<span style="color:#000000"><strong>()</strong></span> <span style="color:#000000"><strong>{</strong></span><idle>-0     <span style="color:#000000"><strong>[</strong></span>022] 5376816.422994: funcgraph_entry:    0.195 us   |    choose_new_asid<span style="color:#000000"><strong>()</strong></span>;<idle>-0     <span style="color:#000000"><strong>[</strong></span>022] 5376816.422994: funcgraph_entry:    0.257 us   |    load_new_mm_cr3<span style="color:#000000"><strong>()</strong></span>;<idle>-0     <span style="color:#000000"><strong>[</strong></span>022] 5376816.422995: funcgraph_entry:    0.128 us   |    switch_ldt<span style="color:#000000"><strong>()</strong></span>;<idle>-0     <span style="color:#000000"><strong>[</strong></span>022] 5376816.422995: funcgraph_exit:     1.378 us   |  <span style="color:#000000"><strong>}</strong></span>
...
</code></span></span></span>

用作分隔符(这将保留调用堆栈和适当的前导空格)并将最后的字段保存到专用文件中:|

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span><span style="color:#0086b3">awk</span> <span style="color:#000080">-F</span><span style="color:#dd1144">'|'</span> <span style="color:#dd1144">'{print $NF}'</span> report-1.graph <span style="color:#000000"><strong>></strong></span> stack-1.txt
</code></span></span></span>

将它们与 或 vimdiff 进行比较:diff

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>vimdiff stack-1.txt stack-2.txt
</code></span></span></span>

这里有两个跟踪,左边是正常请求的跟踪,右边是有问题的:

 

无花果。正常请求(左侧)和有问题的请求(右侧)的跟踪(调用堆栈)。

可以看出,对于一个失败的请求,内核做了一个错误的函数调用: 它应该调用 ,但实际上调用了 。tcp_bpf_recvmsg()tcp_recvmsg()

4.3.2 找到代码:inet_recvmsg -> {tcp_bpf_recvmsg, tcp_recvmsg}

调用 into 或 from 是一段简洁的代码, 如下图所示,tcp_bpf_recvmsgtcp_recvmsginet_recvmsg

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/torvalds/linux/blob/v5.10/net/ipv4/af_inet.c#L838</em></span>
<span style="color:#445588"><strong>int</strong></span> <span style="color:#990000"><strong>inet_recvmsg</strong></span>(<span style="color:#000000"><strong>struct</strong></span> socket <span style="color:#000000"><strong>*</strong></span>sock, <span style="color:#000000"><strong>struct</strong></span> msghdr <span style="color:#000000"><strong>*</strong></span>msg, <span style="color:#445588"><strong>size_t</strong></span> size, <span style="color:#445588"><strong>int</strong></span> flags) {<span style="color:#000000"><strong>struct</strong></span> sock <span style="color:#000000"><strong>*</strong></span>sk <span style="color:#000000"><strong>=</strong></span> sock<span style="color:#000000"><strong>-></strong></span>sk;<span style="color:#445588"><strong>int</strong></span> addr_len <span style="color:#000000"><strong>=</strong></span> <span style="color:#009999">0</span>;<span style="color:#445588"><strong>int</strong></span> err;<span style="color:#000000"><strong>if</strong></span> (likely(<span style="color:#000000"><strong>!</strong></span>(flags <span style="color:#000000"><strong>&</strong></span> MSG_ERRQUEUE)))sock_rps_record_flow(sk);err <span style="color:#000000"><strong>=</strong></span> INDIRECT_CALL_2(sk<span style="color:#000000"><strong>-></strong></span>sk_prot<span style="color:#000000"><strong>-></strong></span>recvmsg, tcp_recvmsg, udp_recvmsg,sk, msg, size, flags <span style="color:#000000"><strong>&</strong></span> MSG_DONTWAIT,flags <span style="color:#000000"><strong>&</strong></span> <span style="color:#000000"><strong>~</strong></span>MSG_DONTWAIT, <span style="color:#000000"><strong>&</strong></span>addr_len);<span style="color:#000000"><strong>if</strong></span> (err <span style="color:#000000"><strong>>=</strong></span> <span style="color:#009999">0</span>)msg<span style="color:#000000"><strong>-></strong></span>msg_namelen <span style="color:#000000"><strong>=</strong></span> addr_len;<span style="color:#000000"><strong>return</strong></span> err;
}
</code></span></span></span>

sk_prot (“套接字协议”)包含此套接字的处理程序。INDIRECT_CALL_2行可以简化为以下伪代码:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>if</strong></span> sk<span style="color:#000000"><strong>-></strong></span>sk_prot<span style="color:#000000"><strong>-></strong></span>recvmsg <span style="color:#000000"><strong>==</strong></span> tcp_recvmsg<span style="color:#000000"><strong>:</strong></span> <span style="color:#999988"><em>// if socket protocol handler is tcp_recvmsg</em></span>tcp_recvmsg()
<span style="color:#000000"><strong>else</strong></span><span style="color:#000000"><strong>:</strong></span>tcp_bpf_recvmsg()
</code></span></span></span>

这表明,当请求失败时,套接字的 sk_prot->recvmsg 指针可能不正确。

4.3.3 使用 bpftrace 进行仔细检查

虽然是一个强大的工具,但它可能包含太多细节会分散我们的注意力,并且 如果设置不正确的过滤器参数,可能会耗尽您的磁盘空间。trace-cmd

bpftrace是另一个跟踪工具,默认情况下不会将数据写入本地文件。 现在让我们用它再次确认上述结果。

同样,运行多次 ,仅捕获 tcp_recvmsg 和 tcp_bpf_recvmsg 个调用, print kernel 调用堆栈:curl POD_IP:PORT

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>bpftrace <span style="color:#000080">-e</span> <span style="color:#dd1144">'k:tcp_recvmsg /pid==178501/ { printf("%s\n", kstack);} k:tcp_bpf_recvmsg /pid==178501/ { printf("%s\n", kstack);} '</span>tcp_bpf_recvmsg+1                   <span style="color:#999988"><em># <-- correspond to a successful request</em></span>inet_recvmsg+233__sys_recvfrom+362__x64_sys_recvfrom+37do_syscall_64+48entry_SYSCALL_64_after_hwframe+97tcp_bpf_recvmsg+1                   <span style="color:#999988"><em># <-- correspond to a successful request</em></span>inet_recvmsg+233__sys_recvfrom+362__x64_sys_recvfrom+37do_syscall_64+48entry_SYSCALL_64_after_hwframe+97tcp_recvmsg+1                       <span style="color:#999988"><em># <-- correspond to a failed request</em></span>inet_recvmsg+78__sys_recvfrom+362__x64_sys_recvfrom+37do_syscall_64+48entry_SYSCALL_64_after_hwframe+97
</code></span></span></span>

您还可以按客户端程序名称(内核数据结构中的 comm 字段)进行过滤,例如,

<span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span>bpftrace <span style="color:#000080">-e</span> <span style="color:#dd1144">'k:tcp_bpf_recvmsg /comm=="curl"/ { printf("%s", kstack); }'</span>
</code></span></span>

如上所示,成功的请求被定向到 ,而失败的请求被路由到 。tcp_bpf_recvmsgtcp_recvmsg

4.3.4 总结

tcp_recvmsg 等待来自内核网络堆栈的消息, 在 sockops BPF 的情况下,消息会绕过内核堆栈,这解释了为什么有些请求会失败(超时),但 TCP 连接始终正常

我们向团队报告了上述发现,他们与我们一起进行了一些进一步的调查。cloud-kernel

4.4 内核堆栈中的处理程序初始化recvmsg

简而言之,

 

无花果。sockops BPF:连接建立和套接字处理程序初始化。

根据上图,如果 to-to-inserted entry 已经存在 sockmap(步骤 3.1 结束),handler 将被错误地初始化。recvmsg

在 BPF 映射中,连接的两个条目是什么样子的:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>cilium-agent<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>bpftool map dump <span style="color:#0086b3">id </span>122 | <span style="color:#0086b3">grep</span> <span style="color:#dd1144">"0a 0a 86 30"</span> <span style="color:#000080">-C</span> 2 | <span style="color:#0086b3">grep</span> <span style="color:#dd1144">"0a 0a 65 f9"</span> <span style="color:#000080">-C</span> 2 | <span style="color:#0086b3">grep</span> <span style="color:#000080">-C</span> 2 <span style="color:#dd1144">"db 78"</span>
0a 0a 86 30 00 00 00 00  00 00 00 00 00 00 00 00
0a 0a 65 f9 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 1f 90 00 00  db 78 00 00
<span style="color:#000080">--</span>
key:
<span style="color:#000080">--</span>
0a 0a 65 f9 00 00 00 00  00 00 00 00 00 00 00 00
0a 0a 86 30 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 db 78 00 00  1f 90 00 00
</code></span></span></span>

我们稍后将解释这些二进制数据。 现在让我们首先确认我们上面的假设。

4.5 确认 sockmap 中的过时条目

4.5.1: 套接字处理程序 (sk_prot) 不正确bpftracetcp_bpf_get_prot()

两个后续函数调用,即 hold :sk_port

  • tcp_bpf_get_prot()其中初始化sk_prot;
  • tcp_bpf_recvmsg()或 : 调用其中接收消息tcp_recvmsg()sk_prot;

跟踪这两个方法并打印变量 (pointer)。sk_prot

成功案例:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code>tcp_bpf_get_proto: src POD_IP <span style="color:#000000"><strong>(</strong></span>8080<span style="color:#000000"><strong>)</strong></span>, dst NODE_IP<span style="color:#000000"><strong>(</strong></span>59500<span style="color:#000000"><strong>)</strong></span>, 2232440
tcp_bpf_get_proto: 0xffffffffacc65800                                     <span style="color:#999988"><em># <-- sk_prot pointer</em></span>
tcp_bpf_recvmsg: src POD_IP <span style="color:#000000"><strong>(</strong></span>8080<span style="color:#000000"><strong>)</strong></span>, dst NODE_IP<span style="color:#000000"><strong>(</strong></span>59500<span style="color:#000000"><strong>)</strong></span> 0xffffffffacc65800 <span style="color:#999988"><em># <-- same pointer</em></span>
</code></span></span></span>

坏情况:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>./tcp_bpf_get_proto.bt 178501
Attaching 6 probes...
tcp_bpf_get_proto: src POD_IP <span style="color:#000000"><strong>(</strong></span>8080<span style="color:#000000"><strong>)</strong></span>, dst NODE_IP<span style="color:#000000"><strong>(</strong></span>53904<span style="color:#000000"><strong>)</strong></span>, 2231203
tcp_bpf_get_proto: 0xffffffffacc65800                                    <span style="color:#999988"><em># <-- sk_prot pointer</em></span>
tcp_recvmsg: src POD_IP <span style="color:#000000"><strong>(</strong></span>8080<span style="color:#000000"><strong>)</strong></span>, dst NODE_IP<span style="color:#000000"><strong>(</strong></span>53904<span style="color:#000000"><strong>)</strong></span> 0xffffffffac257300    <span style="color:#999988"><em># <-- sk_prot is modified!!!</em></span>
</code></span></span></span>

4.5.2bpftrace sk_psock_drop

一个成功的情况,当请求完成并且连接通常关闭时调用 in:sk_psock_drop

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>./sk_psock_drop.bt 178501
tcp_bpf_get_proto: src POD_IP <span style="color:#000000"><strong>(</strong></span>8080<span style="color:#000000"><strong>)</strong></span>, dst NODE_IP<span style="color:#000000"><strong>(</strong></span>59500<span style="color:#000000"><strong>)</strong></span>, 2232440
tcp_bpf_get_proto: 0xffffffffacc65800                                    <span style="color:#999988"><em># <-- sk_prot pointer</em></span>
sk_psock_drop: src POD_IP <span style="color:#000000"><strong>(</strong></span>8080<span style="color:#000000"><strong>)</strong></span>, dst NODE_IP<span style="color:#000000"><strong>(</strong></span>44566<span style="color:#000000"><strong>)</strong></span>sk_psock_drop+1sock_map_remove_links+161sock_map_close+50inet_release+63sock_release+58sock_close+17fput+147task_work_run+89exit_to_user_mode_loop+285exit_to_user_mode_prepare+110syscall_exit_to_user_mode+18entry_SYSCALL_64_after_hwframe+97
tcp_bpf_recvmsg: src POD_IP <span style="color:#000000"><strong>(</strong></span>8080<span style="color:#000000"><strong>)</strong></span>, dst NODE_IP<span style="color:#000000"><strong>(</strong></span>59500<span style="color:#000000"><strong>)</strong></span> 0xffffffffacc65800 <span style="color:#999988"><em># <-- same pointer</em></span>
</code></span></span></span>

当服务器端调用并且要插入的条目已存在时调用 in,情况失败:sk_psock_dropsock_map_update()

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>./sk_psock_drop.bt 178501
tcp_bpf_get_proto: src POD_IP <span style="color:#000000"><strong>(</strong></span>8080<span style="color:#000000"><strong>)</strong></span>, dst NODE_IP<span style="color:#000000"><strong>(</strong></span>53904<span style="color:#000000"><strong>)</strong></span>, 2231203
tcp_bpf_get_proto: 0xffffffffacc65800                                    <span style="color:#999988"><em># <-- sk_prot pointer</em></span>
sk_psock_drop: src POD_IP <span style="color:#000000"><strong>(</strong></span>8080<span style="color:#000000"><strong>)</strong></span>, dst NODE_IP<span style="color:#000000"><strong>(</strong></span>44566<span style="color:#000000"><strong>)</strong></span>sk_psock_drop+1sock_hash_update_common+789bpf_sock_hash_update+98bpf_prog_7aa9a870410635af_bpf_sockmap+831_cgroup_bpf_run_filter_sock_ops+189tcp_init_transfer+333                       // -> bpf_skops_established -> BPF_CGROUP_RUN_PROG_SOCK_OPS -> cilium sock_ops codetcp_rcv_state_process+1430tcp_child_process+148tcp_v4_rcv+2491...
tcp_recvmsg: src POD_IP <span style="color:#000000"><strong>(</strong></span>8080<span style="color:#000000"><strong>)</strong></span>, dst NODE_IP<span style="color:#000000"><strong>(</strong></span>53904<span style="color:#000000"><strong>)</strong></span> 0xffffffffac257300    <span style="color:#999988"><em># <-- sk_prot is modified!!!</em></span>
</code></span></span></span>
<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/torvalds/linux/blob/v6.5/net/core/sock_map.c#L464</em></span><span style="color:#000000"><strong>static</strong></span> <span style="color:#445588"><strong>int</strong></span> <span style="color:#990000"><strong>sock_map_update_common</strong></span>(<span style="color:#000000"><strong>struct</strong></span> bpf_map <span style="color:#000000"><strong>*</strong></span>map, u32 idx, <span style="color:#000000"><strong>struct</strong></span> sock <span style="color:#000000"><strong>*</strong></span>sk, u64 flags) {<span style="color:#000000"><strong>struct</strong></span> bpf_stab <span style="color:#000000"><strong>*</strong></span>stab <span style="color:#000000"><strong>=</strong></span> container_of(map, <span style="color:#000000"><strong>struct</strong></span> bpf_stab, map);...link <span style="color:#000000"><strong>=</strong></span> sk_psock_init_link();sock_map_link(map, sk);psock <span style="color:#000000"><strong>=</strong></span> sk_psock(sk);osk <span style="color:#000000"><strong>=</strong></span> stab<span style="color:#000000"><strong>-></strong></span>sks[idx];<span style="color:#000000"><strong>if</strong></span> (osk <span style="color:#000000"><strong>&&</strong></span> flags <span style="color:#000000"><strong>==</strong></span> BPF_NOEXIST) {     <span style="color:#999988"><em>// sockmap entries already exists</em></span>ret <span style="color:#000000"><strong>=</strong></span> <span style="color:#000000"><strong>-</strong></span>EEXIST;<span style="color:#000000"><strong>goto</strong></span> out_unlock;                   <span style="color:#999988"><em>// goto out_unlock, which will release psock</em></span>} <span style="color:#000000"><strong>else</strong></span> <span style="color:#000000"><strong>if</strong></span> (<span style="color:#000000"><strong>!</strong></span>osk <span style="color:#000000"><strong>&&</strong></span> flags <span style="color:#000000"><strong>==</strong></span> BPF_EXIST) {ret <span style="color:#000000"><strong>=</strong></span> <span style="color:#000000"><strong>-</strong></span>ENOENT;<span style="color:#000000"><strong>goto</strong></span> out_unlock;}sock_map_add_link(psock, link, map, <span style="color:#000000"><strong>&</strong></span>stab<span style="color:#000000"><strong>-></strong></span>sks[idx]);stab<span style="color:#000000"><strong>-></strong></span>sks[idx] <span style="color:#000000"><strong>=</strong></span> sk;<span style="color:#000000"><strong>if</strong></span> (osk)sock_map_unref(osk, <span style="color:#000000"><strong>&</strong></span>stab<span style="color:#000000"><strong>-></strong></span>sks[idx]);<span style="color:#000000"><strong>return</strong></span> <span style="color:#009999">0</span>;                              <span style="color:#999988"><em>// <-- should return from here</em></span>
<span style="color:#990000"><strong>out_unlock:</strong></span>                                <span style="color:#999988"><em>// <-- actually hit here</em></span><span style="color:#000000"><strong>if</strong></span> (psock)sk_psock_put(sk, psock);           <span style="color:#999988"><em>// --> further call sk_psock_drop</em></span>
<span style="color:#990000"><strong>out_free:</strong></span>sk_psock_free_link(link);<span style="color:#000000"><strong>return</strong></span> ret;
}
</code></span></span></span>

早期版本导致 初始化为 。psocksk->sk_prot->recvmsgtcp_recvmsg

4.5.3 bpftool:在 sockops 映射中确认过时的连接信息

BPF 映射中的键和值格式:

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#999988"><em>// https://github.com/cilium/cilium/blob/v1.11.10/pkg/maps/sockmap/sockmap.go#L20</em></span><span style="color:#999988"><em>// SockmapKey is the 5-tuple used to lookup a socket</em></span>
<span style="color:#999988"><em>// +k8s:deepcopy-gen=true</em></span>
<span style="color:#999988"><em>// +k8s:deepcopy-gen:interfaces=github.com/cilium/cilium/pkg/bpf.MapKey</em></span>
<span style="color:#000000"><strong>type</strong></span> SockmapKey <span style="color:#000000"><strong>struct</strong></span> {DIP    types<span style="color:#000000"><strong>.</strong></span>IPv6 <span style="color:#dd1144">`align:"$union0"`</span>SIP    types<span style="color:#000000"><strong>.</strong></span>IPv6 <span style="color:#dd1144">`align:"$union1"`</span>Family <span style="color:#445588"><strong>uint8</strong></span>      <span style="color:#dd1144">`align:"family"`</span>Pad7   <span style="color:#445588"><strong>uint8</strong></span>      <span style="color:#dd1144">`align:"pad7"`</span>Pad8   <span style="color:#445588"><strong>uint16</strong></span>     <span style="color:#dd1144">`align:"pad8"`</span>SPort  <span style="color:#445588"><strong>uint32</strong></span>     <span style="color:#dd1144">`align:"sport"`</span>DPort  <span style="color:#445588"><strong>uint32</strong></span>     <span style="color:#dd1144">`align:"dport"`</span>
}<span style="color:#999988"><em>// SockmapValue is the fd of a socket</em></span>
<span style="color:#999988"><em>// +k8s:deepcopy-gen=true</em></span>
<span style="color:#999988"><em>// +k8s:deepcopy-gen:interfaces=github.com/cilium/cilium/pkg/bpf.MapValue</em></span>
<span style="color:#000000"><strong>type</strong></span> SockmapValue <span style="color:#000000"><strong>struct</strong></span> {fd <span style="color:#445588"><strong>uint32</strong></span>
}
</code></span></span></span>

Trip.com:使用Cilium/eBPF的大规模云原生网络和安全,2022展示了如何解码Cilium BPF映射的编码条目。

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#008080">$ </span><span style="color:#0086b3">cat </span>ip2hex.sh
<span style="color:#0086b3">echo</span> <span style="color:#008080">$1</span> | <span style="color:#0086b3">awk</span> <span style="color:#000080">-F</span><span style="color:#0086b3">.</span> <span style="color:#dd1144">'{printf("%02x %02x %02x %02x\n",$1,$2,$3,$4);}'</span>
<span style="color:#008080">$ </span><span style="color:#0086b3">cat </span>hex2port.sh
<span style="color:#0086b3">echo</span> <span style="color:#008080">$1</span> | <span style="color:#0086b3">awk</span> <span style="color:#dd1144">'{printf("0x%s%s 0x%s%s\n", $1, $2, $5, $6) }'</span> | <span style="color:#0086b3">sed</span> <span style="color:#dd1144">'s/ /\n/g'</span> | xargs <span style="color:#000080">-n1</span> <span style="color:#0086b3">printf</span> <span style="color:#dd1144">'%d\n'</span><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>./ip2hex.sh <span style="color:#dd1144">"10.10.134.48"</span>
0a 0a 86 30
<span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>./ip2hex.sh <span style="color:#dd1144">"10.10.101.249"</span>
0a 0a 65 f9
</code></span></span></span>
<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>cilium-agent<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>bpftool map dump <span style="color:#0086b3">id </span>122 | <span style="color:#0086b3">grep</span> <span style="color:#dd1144">"0a 0a 86 30"</span> <span style="color:#000080">-C</span> 2 | <span style="color:#0086b3">grep</span> <span style="color:#dd1144">"0a 0a 65 f9"</span> <span style="color:#000080">-C</span> 2 | <span style="color:#0086b3">grep</span> <span style="color:#000080">-C</span> 2 <span style="color:#dd1144">"db 78"</span>
0a 0a 86 30 00 00 00 00  00 00 00 00 00 00 00 00
0a 0a 65 f9 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 1f 90 00 00  db 78 00 00
<span style="color:#000080">--</span>
key:
<span style="color:#000080">--</span>
0a 0a 65 f9 00 00 00 00  00 00 00 00 00 00 00 00
0a 0a 86 30 00 00 00 00  00 00 00 00 00 00 00 00
01 00 00 00 db 78 00 00  1f 90 00 00
</code></span></span></span>
<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>node<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>./hex2port.sh <span style="color:#dd1144">"1f 90 00 00  b6 8a 00 00"</span>
8080
46730 <span style="color:#999988"><em># you can verify this connection in `ss` output</em></span>
</code></span></span></span>

几乎所有以下条目都是过时的(因为这是一个空的,没有节点到 Pod 的流量,除非我们手动执行此操作):

<span style="color:#333333"><span style="background-color:#f8f8f8"><span style="background-color:#f6f8fa"><code><span style="color:#000000"><strong>(</strong></span>cilium-agent<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>bpftool map dump /sys/fs/bpf/cilium_sock_ops | <span style="color:#0086b3">grep</span> <span style="color:#dd1144">"0a 0a 86 30"</span> | <span style="color:#0086b3">wc</span> <span style="color:#000080">-l</span>
7325
<span style="color:#000000"><strong>(</strong></span>cilium-agent<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>bpftool map dump /sys/fs/bpf/cilium_sock_ops | <span style="color:#0086b3">grep</span> <span style="color:#dd1144">"0a 0a 8c ca"</span> | <span style="color:#0086b3">wc</span> <span style="color:#000080">-l</span>
1288
<span style="color:#000000"><strong>(</strong></span>cilium-agent<span style="color:#000000"><strong>)</strong></span> <span style="color:#008080">$ </span>bpftool map dump /sys/fs/bpf/cilium_sock_ops | <span style="color:#0086b3">grep</span> <span style="color:#dd1144">"0a 0a 8e 40"</span> | <span style="color:#0086b3">wc</span> <span style="color:#000080">-l</span>
191
</code></span></span></span>

5 技术总结

5.1 普通的 sockops/sockmap BPF 工作流程

 

无花果。sockops BPF:连接建立和套接字处理程序初始化。

  1. Node 客户端(例如 kubelet)-> server:启动与服务器的 TCP 连接
  2. 内核(以及内核中的 BPF 代码):在侦听已建立的连接时
    1. write two entries to sockmap
    2. link entries to bpf handlers (tcp_bpf_{sendmsg, recvmsg})
  3. 节点客户端(例如kubelet)>服务器:发送和接收有效负载:BPF处理程序已执行
  4. 节点客户端(例如 kubelet)-> server: close connection: kernel 从 sockmap 中删除条目

5.2 直接原因

问题出现在第 4 步,由于未知原因,连接关闭时某些条目未被删除。这 导致步骤 2(或图片中的 Section 3.1)中新连接中的处理程序初始化不正确。 当点击过时的条目时,

  • 发送方使用 BPF 消息处理程序进行传输;
  • 服务器端将 socket 视为 standard,并通过 default message handler 等待消息,然后卡在那里,因为没有 payload 进入 default handler。

5.3 根本原因

阿里巴巴团队进一步深入研究了这个问题,感谢他们的努力,他们最终发现 bpf, sockmap: Remove unhash handler for BPF sockmap usage 是根本原因,这是在 Linux 5.10.58 中引入的。我们 正在使用基于 5.10.134 的版本,因此它受到了影响。cloud-kernel

上游补丁 bpf, sockmap: Fix sk->sk_forward_alloc warn_on in sk_stream_kill_queues 已经修复了它,但只向后移植到 6.x 系列。

5.4 快速恢复/修复

如果问题已经发生,您可以使用以下方法之一进行恢复:

  1. 内核重启:耗尽节点然后重启节点,这将刷新内核状态;
  2. 手动清理方式 :请谨慎避免删除有效条目。bpftool

5.5 具有类似现象的另一个问题

启用 sockops 时,类似现象还有另一个问题:

  1. 本地 Pod 运行 nginx(最新版本,例如>= 1.18);
  2. 将 http 请求从节点发送到本地 pod,具有足够大的 cookie 长度(例如> 1024 Byte);

TCP 连接将正常,但请求将始终卡在那里

纤毛问题:

ioctl FIONREAD 在启用 sockops 时返回不正确的值

nginx 正在从 Traefik 请求中读取标头,默认值为 1024 (client_header_buffer_size 1k;) 字节,然后(似乎)通过 ioctl 剩余多少数据。由于返回值为 0,因此请求永远不会完全 读取,并且不会继续。

社区解决方案:

  • 在 v1.13 中废弃了 –sockops-enable,并在 v1.14 中删除了该功能

附录

  • 本文中使用的 bpftrace 脚本

引用

  1. AliOS 内核(一个 Linux 分支),gitee.com/anolis/cloud-kernel
  2. AWS 上的 Cilium 网络拓扑和流量路径 (2019)
  3. 纤度 v1.11.10, bpf_sockops.c
  4. Cilium v1.11.10, BPF sockops键和值定义
  5. 区分三种类型的 eBPF 重定向
  6. Trip.com:使用 Cilium/eBPF 的大规模云原生网络和安全,2022 年

​编辑

 

 


http://www.hkcw.cn/article/wGIEHUNbGA.shtml

相关文章

C++核心编程_赋值运算符重载

4.5.4 赋值运算符重载 c编译器至少给一个类添加4个函数 默认构造函数(无参&#xff0c;函数体为空) 默认析构函数(无参&#xff0c;函数体为空) 默认拷贝构造函数&#xff0c;对属性进行值拷贝 赋值运算符 operator, 对属性进行值拷贝 如果类中有属性指向堆区&#xff0c;做…

深度学习笔记25-RNN心脏病预测(Pytorch)

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、前期准备 1.数据处理 import torch.nn.functional as F import numpy as np import pandas as pd import torch from torch import nn dfpd.read_csv(r&…

基于 HT for Web 轻量化 3D 数字孪生数据中心解决方案

一、技术架构&#xff1a;HT for Web 的核心能力 图扑软件自主研发的 HT for Web 是基于 HTML5 的 2D/3D 可视化引擎&#xff0c;核心技术特性包括&#xff1a; 跨平台渲染&#xff1a;采用 WebGL 技术&#xff0c;支持 PC、移动端浏览器直接访问&#xff0c;兼容主流操作系统…

CIO大会, AI课笔记手稿分享

能认真听课的CIO不多了 能认真听课的CIO还能记笔记的不多了 能认真听课的CIO还能记笔记的字写得好的不多了

stl三角面元文件转颗粒VTK文件

效果展示&#xff1a; import os import sys import json import argparse import numpy as np import pandas as pd import open3d as o3d from glob import globPARTICLE_RADIUS 0.025def stl_to_particles(objpath, radiusNone):if radius is None:radius PARTICLE_RADIU…

vue为什么点击两遍才把参数传递过去

先说一下场景&#xff0c;就是我把云服务器这个下拉选择框分别初始化之后&#xff0c;然后点击新建权限然后就打开了右侧的抽屉式的对话框&#xff0c;页面上那个文字信息是传递过来了。那个是正确的&#xff0c;但是我请求接口的时候&#xff0c;发现请求的接口的参数总是要慢…

NodeMediaEdge通道管理

NodeMediaEdge任务管理 简介 NodeMediaEdge是一款部署在监控摄像机网络前端中&#xff0c;拉取Onvif或者rtsp/rtmp/http视频流并使用rtmp/kmp推送到公网流媒体服务器的工具。 在未使用NodeMediaServer的情况下&#xff0c;或者对部分视频流需要单独推送的需求&#xff0c;也可…

2025年- H59-Lc167--207.课程表(拓扑排序、BFS)-需二刷--Java版

1.题目描述 2.思路 记录每门课程的前置课程数量&#xff0c;记录每门课程是哪些课程的前置课程。 &#xff08;1&#xff09;如果有向图中的拓扑图中存在环&#xff0c;则说明所有的课程是无法完成的。 &#xff08;2&#xff09;使用拓扑排序&#xff0c;在图中每个节点的入度…

MQTT的Thingsboards的使用

访问云服务 https://thingsboard.cloud/ 新建一个设备 弹出 默认是mosquittor的客户端。 curl -v -X POST http://thingsboard.cloud/api/v1/tnPrO76AxF3TAyOblf9x/telemetry --header Content-Type:application/json --data "{temperature:25}" 换成MQTTX的客户…

代码随想录算法训练营第60期第五十二天打卡

大家好&#xff0c;昨天我们重点讲解了单调栈的问题&#xff0c;我们今天的题目还是继续围绕单调栈展开&#xff0c;我们上节课其实对单调栈已经有了大致的了解&#xff0c;今天的第一道题目大家务必要注意很重要&#xff0c;接雨水问题我们会涉及到单调栈与双指针&#xff0c;…

新能源集群划分+电压调节!基于分布式能源集群划分的电压调节策略!

适用平台&#xff1a;MatlabYalmip Cplex (具体操作已在程序文件中说明) 参考文献&#xff1a;基于分布式能源集群化分的电压调节策略[D]. 一、文献解读 1. 主要内容/创新点 提出了一种基于分布式能源集群化的电压调节策略&#xff0c;计及分布式能源的有功、无功调节能力&a…

【C++】22. 红黑树封装实现Mymap和Myset

上一章节我们实现了红黑树&#xff0c;这一章节我们就用红黑树封装来实现一个我们自己的map和set 1. 源码及框架分析 SGI-STL 3.0版本的源代码中&#xff0c;map和set的实现主要分布在若干头文件中&#xff0c;这些头文件构成了这两个容器的完整实现架构&#xff1a; 核心头文…

论文速读《UAV-Flow Colosseo: 自然语言控制无人机系统》

论文链接&#xff1a;https://arxiv.org/abs/2505.15725项目主页&#xff1a;https://prince687028.github.io/UAV-Flow/ 0. 简介 近年来&#xff0c;无人机技术蓬勃发展&#xff0c;但如何让无人机像智能助手一样理解并执行人类语言指令&#xff0c;仍是一个前沿挑战。现有研…

关于表连接

目录 1.左连接 2.右连接 3.内连接 4.全外连接 5.笛卡尔积 -- 创建表A CREATE TABLE A(PNO VARCHAR2(10) PRIMARY KEY, PAMT NUMBER, A_DATE DATE);-- 向表A插入数据 INSERT INTO A VALUES (01001, 100, TO_DATE(2005-01-01, YYYY-MM-DD)); INSERT INTO A VALUES (010…

【面试 - 遇到的问题 - 优化 - 地图】腾讯地图轨迹回放 - 回放的轨迹时间要和现实时间对应(非匀速)

目录 背景轨迹回放 - 匀速效果图TrackPlaybackDialog.vue 代码TMapNew.vue 代码 轨迹回放 - 非匀速效果图TrackPlaybackDialog.vue 代码TMapNew.vue 代码 背景 腾讯地图轨迹回放是匀速回放的&#xff0c;但是客户要求根据现实时间&#xff0c;什么时间点在某个点位 【腾讯地图轨…

Python Day37 学习

&#xff08;补充学习几个知识点&#xff09; 1. 异常处理机制 摘自讲义 常见异常报错 2. debug 理解一下几种错误 SyntaxError 语法错误 代码不符合Python的语法规则 错误代码示例 NameError 名称错误 尝试使用一个未被定义的变量、函数或对象的名称。 TypeError 类型错…

打破建筑管理壁垒,IBMS智能系统赋能现代建筑协同增效

在建筑行业快速发展与智能化转型的进程中&#xff0c;传统建筑管理模式正面临前所未有的挑战。各子系统独立运行形成的“信息孤岛”、部门间沟通不畅导致的协作低效&#xff0c;以及管理决策缺乏数据支撑等问题&#xff0c;严重制约了建筑的运营效率与服务质量。而IBMS&#xf…

十四: 导数,数值微分,偏导数,梯度

在前一章说明损失函数的用途时,引入了梯度,导数等名词,现在我们详细了解一下这些名词 1. 导数 假如你是全程马拉松选手&#xff0c;在开始的 10 分钟内跑了 2 千米。如果要计算此时的奔跑速度&#xff0c;则为 2/10 0.2&#xff3b;千米 / 分&#xff3d;。也就是说&#xf…

深度刨析树结构(从入门到入土讲解AVL树及红黑树的奥秘)

树的概念及结构: 树是一种非线性的数据结构&#xff0c;它是由n>0个有限结点组成的一个有层次关系的集合&#xff0c;把它叫做树是因为像一个倒挂的树&#xff0c;根在上&#xff0c;叶子在下 对于树&#xff0c;每颗树都可以看成根节点和子树&#xff0c;所有的子树又可以…

Replacing iptables with eBPF in Kubernetes with Cilium

source: https://archive.fosdem.org/2020/schedule/event/replacing_iptables_with_ebpf/attachments/slides/3622/export/events/attachments/replacing_iptables_with_ebpf/slides/3622/Cilium_FOSDEM_2020.pdf 使用Cilium&#xff0c;结合eBPF、Envoy、Istio和Hubble等技术…