当前位置: 首页 > article >正文

建立连接后 TCP 请求卡住

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

这篇文章描述了一个内核和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 年

​编辑

 

 

相关文章:

建立连接后 TCP 请求卡住

大家读完觉得有意义记得关注和点赞&#xff01;&#xff01;&#xff01; 这篇文章描述了一个内核和BPF网络问题 以及故障排除步骤&#xff0c;这是一个值得深入研究的有趣案例 Linux 内核网络复杂性。 目录 1 故障报告 1.1 现象&#xff1a;概率健康检查失败 1.2 范围&am…...

尚硅谷redis7 99 springboot整合redis之连接集群

6381宕机&#xff0c;手动shutdown后在redis中&#xff0c;634自动上位变成master结点。 但是在springboot中却没有动态感知道redisCluster的最新集群消息&#xff0c;所以找不到我们要检索的数据。原因是&#xff1a;SpringBoot 2.X版本,Redis默认的连接池采用 Lettuce&#…...

hive 笔记

1. 查看hive表的文件情况 搭建ui界面机器上查看 show create table xxx;得到文件地址 hdfs查看文件情况 hdfs dfs -ls hdfs://HDFS4005133/usr/hive/warehouse/xxx/xxxx/app_idxxx...

无线通信模块简介

QuecPython 是运行在无线通信模块上的开发框架。对于首次接触物联网开发的用户而言&#xff0c;无线通信模块可能是一个相对陌生的概念。本文主要针对无线通信和蜂窝网络本身&#xff0c;以及模块的概念、特性和开发方式进行简要的介绍。 无线通信和蜂窝网络 物联网对无线通信…...

Go语言之空接口与类型断言

Go 语言中&#xff0c;接口是一种强大的抽象机制。其中&#xff0c;空接口&#xff08;interface{}&#xff09;和类型断言为我们提供了处理任意类型与类型检查的能力。 一、空接口&#xff08;interface{}&#xff09; 空接口是 Go 中最特殊的接口&#xff1a;不包含任何方法…...

把 CURSOR 的工具活动栏改成和 VSCODE 一样的左侧展示

目前使用cursor的时候发现工具栏与vscode的布局不一致&#xff0c;cursor在顶部导致操作起来不方便&#xff0c;如何改成与vscode相同的左侧布局展示。 解决方案 文件→首选项→设置&#xff0c;进入设置中&#xff0c;然后点击这个icon图标&#xff0c;可以打开配置文件 se…...

碰一碰系统源码搭建==saas系统

搭建“碰一碰”系统&#xff08;通常指基于NFC或蓝牙的短距离交互功能&#xff09;的源码实现&#xff0c;需结合具体技术栈和功能需求。以下是关键步骤和示例代码&#xff1a; 技术选型 NFC模式&#xff1a;适用于Android/iOS设备的近场通信&#xff0c;需处理NDEF协议。蓝牙…...

不加载PHP OpenTelemetry SDK实现Trace‌与Logs

目录 前言一、回到OpenTelemetry原理看问题1、数据接收&#xff08;Receivers&#xff09;2、数据处理&#xff08;Processors&#xff09;3、数据导出&#xff08;Exporters&#xff09; 二、不加载OpenTelemetry SDK实现Trace‌与Logs示例 前言 前面两篇我们分别介绍了OpenT…...

Three.js搭建小米SU7三维汽车实战(6)颜色切换

颜色切换 接下来我们来实现懂车帝的颜色切换效果 可以让ai帮我们生成页面结构以及样式&#xff0c;注意changeCarBodyColor这个函数需要我们自己来写 // 创建颜色选择器UI function createColorSelector() {const colors [{ name: "深海蓝", hex: "#1A9CB0&qu…...

mysql慢sql的实际处理方案之一

复习mysql架构图 当大批量慢sql过来&#xff0c;显然就是占用了线程池的链接&#xff0c;然后长久不释放&#xff0c;所以会出现线程池满的问题&#xff0c;致使正常业务sql也全部阻塞&#xff0c;影响整个业务。 AI搜索如下&#xff1a; 可以考虑一种方案&#xff1a; 将线…...

GitLab 18.0 正式发布,15.0 将不再受技术支持,须升级【六】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…...

c/c++的opencv车牌识别

OpenCV 安装&#xff1a; 你需要正确安装 OpenCV 库。Tesseract OCR 安装&#xff1a; 你需要安装 Tesseract OCR 引擎。在 Ubuntu/Debian 上&#xff0c;可以使用&#xff1a;sudo apt-get install tesseract-ocr sudo apt-get install libtesseract-dev sudo apt-get install…...

4.2.3 Spark SQL 手动指定数据源

在本节实战中&#xff0c;我们学习了如何在Spark SQL中手动指定数据源以及如何使用format()和option()方法。通过案例演示&#xff0c;我们读取了不同格式的数据文件&#xff0c;包括CSV、JSON&#xff0c;并从JDBC数据源读取数据&#xff0c;展示了如何将这些数据转换为DataFr…...

【论文解读】CVPR2023 PoseFormerV2:3D人体姿态估计(附论文地址)

论文链接&#xff1a;https://arxiv.org/pdf/2303.17472 源码链接&#xff1a;https://github.com/QitaoZhao/PoseFormerV2 Abstract 本文提出了 PoseFormerV2&#xff0c;通过探索频率域来提高 3D 人体姿态估计的效率和鲁棒性。PoseFormerV2 利用离散余弦变换&#xff08;DC…...

WPF的交互核心:命令系统(ICommand)

命令系统&#xff08;ICommand&#xff09; 1 RelayCommand实现2 CanExecute控制按钮可用性3 参数传递&#xff08;CommandParameter&#xff09;3.1 静态参数绑定&#xff1a;3.2 动态参数绑定&#xff1a;3.3 复杂对象参数&#xff1a; 4 异步命令实现5 常见问题排查 WPF的命…...

Maven工程演示

软件&#xff1a;idea 一、项目创建 操作截图file -> New -> Projectnextnext -> Name:工程名称&#xff1b;Location:项目路径&#xff1b;项目创建完成;文件夹基本样例&#xff1a;&#xff08;如果不完整自己创建即可&#xff09;MANIFEST.MF内容 二、导入依赖 …...

uniapp分包配置,uniapp设置subPackages

在使用uniapp开发过程中&#xff0c;由于项目比较大&#xff0c;无法直接上传&#xff0c;需要分包后才可以上传。 步骤&#xff1a; 1、在pages同级目录下创建分包的目录&#xff08;pages_second&#xff09;&#xff0c;把要分包的文件放到该目录下&#xff1b; 2、在pag…...

计算机网络 HTTP篇常见面试题总结

HTTP各版本区别 HTTP 1.0 无状态、无连接&#xff1a;每次请求都需要建立新的 TCP&#xff0c;处理完后立即关闭&#xff0c;导致开销较大。队头阻塞&#xff1a;每个请求必须按照顺序依次处理&#xff0c;前面的请求未完成&#xff0c;后面的请求只能等待&#xff0c;减低了…...

C++八股 —— 手撕线程池

文章目录 一、背景二、线程池实现1. 任务队列和工作线程2. 构造和析构函数3. 添加任务函数4. 完整代码 三、阻塞队列实现1. 基础队列2. 升级版队列 四、测试代码五、相关问题六、其他实现方式 来自&#xff1a;华为C一面&#xff1a;手撕线程池_哔哩哔哩_bilibili 华为海思&am…...

RPA如何支持跨平台和跨浏览器的自动化

RPA&#xff0c;即机器人流程自动化&#xff08;Robotic Process Automation&#xff09;&#xff0c;正日益成为企业实现业务流程高效自动化的关键技术。在复杂的数字化环境中&#xff0c;跨平台和跨浏览器的自动化需求极为迫切&#xff0c;RPA 通过多种技术手段和策略来满足这…...

【笔记】Windows 成功部署 Suna 开源的通用人工智能代理项目部署日志

#工作记录 本地部署运行截图 kortix-ai/suna&#xff1a; Suna - 开源通用 AI 代理 项目概述 Suna 是一个完全开源的 AI 助手&#xff0c;通过自然对话帮助用户轻松完成研究、数据分析等日常任务。它结合了强大的功能和直观的界面&#xff0c;能够理解用户需求并提供结果。其强…...

关于ffplay在macos上运行奔溃的问题

这个问题大概是由于 MacOS 的问题引起的&#xff0c;奔溃的地方在 SDL2 的代码中&#xff0c;如果直接使用 brew 安装 SDL2就会遇到这个问题&#xff0c;所以需要修改 SDL2源码然后再编译安装。 我这里采用的是 origin/release-2.28.x 分支&#xff0c;修改部分如下&#xff1…...

Linux531rsync定时同步 再回忆

rsync定时同步 环境配置 关闭防火墙&#xff0c;selinux systemctl stop firewalld systemctl disable firewall setenforce 0 cat /etc/selinux/configpei SELINUXdisable设置主机名 systemctl set-hostname code systemctl set-hostname backup设置静态IP rsync由于要设…...

Elasticsearch 分析器介绍

在 Elasticsearch 的世界里,构建高效搜索引擎的关键一环,便是透彻理解分析器(Analyzer)的工作机制。一个优秀的搜索引擎,能够精准地返回与用户查询紧密相关的文档,而这背后,正是分析器在默默发挥着核心作用。它不仅负责处理待索引的文档,还在用户发起查询时,智能评估哪…...

【KWDB 创作者计划】_探秘浪潮KWDB数据库:从时间索引到前沿技术

探秘浪潮KWDB数据库&#xff1a;从时间索引到前沿技术 文章目录 探秘浪潮KWDB数据库&#xff1a;从时间索引到前沿技术引言1.浪潮KWDB数据库时间索引深度解析1.1时间索引工作原理1.2时间索引创建与管理实践 2.浪潮KWDB数据库前沿产品技术纵览2.1多模融合存储引擎2.2就地计算技术…...

安卓逆向篇LSP 模块HOOK 添加技术绕过检测算法解密逻辑验证

前置解释&#xff1a; 0 、 Magisk &#xff1a; 是当前 Android 社区用来获取 root 权限的主流方式开源工具 1 、 LSP 框架&#xff1a; XPosed 框架因只支持安卓 8 及以下&#xff0c;故高版本应使用 MagiskLSPosed 2 、 HOOK 技术&#xff1a; 钩子技术&…...

【SQL】关键字

ORDER BY ORDER BY(排序) 语句可以按照一个或多个列的值进行升序&#xff08;ASC&#xff09;或降序&#xff08;DESC&#xff09;排序。 MAX / MIN MAX() 函数返回一组值中的最大值。这个函数常用于数字字段&#xff0c;但也可以用于文本字段来找出按字典顺序最后的元素。 …...

第一节 51单片机概述

目录 一、单片机系统组成 &#xff08;一&#xff09;、单片机硬件系统 &#xff08;二&#xff09;单片机的软件系统 二、STC89C52单片机 &#xff08;1&#xff09;、基本信息 &#xff08;2&#xff09;、命名规则 &#xff08;3&#xff09;、单片机内部结构图 &am…...

Google car key:安全、便捷的汽车解锁新选择

有了兼容的汽车和 Android 手机&#xff0c;Google car key可让您将Android 手机用作车钥匙。您可以通过兼容的 Android 手机锁定、解锁、启动汽车并执行更多功能。但是&#xff0c;Google car key安全吗&#xff1f;它是如何工作的&#xff1f;如果我的手机电池没电了怎么办&a…...

720全景展示:VR全景的技术原理及应用

VR720全景展示&#xff1a;技术原理及应用探索 720全景技术&#xff0c;作为当前全球范围内迅速崛起流行的视觉新技术&#xff0c;为用户带来了全新的真实现场感和交互式的体验。凭借全方位、无死角的视觉展示特性&#xff0c;在VR&#xff08;虚拟现实&#xff09;领域中得到…...