建立连接后 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
- 适用于
AI 工作负载
的Intel AMX
(Advanced Matrix Extensions), 在某些情况下提供 GPU 的硬件加速替代方案, 例如对小于 13B 的 LLM 进行推理。 AMX 支持首次在内核 5.16 中引入,并将该功能向后移植到其当前版本5.10
cloud-kernel
; 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 程序是如何工作的。
中文读者也可以参考以下内容了解更多信息,
- (译)利用 ebpf sockmap/redirection 提升 socket 性能(2020)
- BPF 进阶笔记(五):几种 TCP 相关的 BPF(sockops、struct_ops、header options)
2.2.2:只能捕获 TCP 握手数据包tcpdump
3-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 请求概率卡住
无花果。测试用例和结果。
三种情况的区别:
Node-to-localPod
:payload 流量通过sockops BPF
处理;本地 Pod 到 Pod
:BPF 重定向
(或内核堆栈,具体取决于您的内核版本)- 区分三种类型的 eBPF 重定向 (2022)
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 和网络修改。 如
-
b578e4b8ed6e1c7608e07e03a061357fd79ac2dd
CK: NET: 跟踪创建 sock 的 PID在此提交中,他们向
struct sock
数据结构添加了一个字段。pid_t pid
-
ea0307caaf29700ff71467726b9617dcb7c0d084
tcp:确保初始化 accept_queue 的 spinlocks 一次
但同样,我们没有发现与问题有任何关联。
4.2 查看详细的 TCP 连接统计信息
由于没有来自代码比较的有价值的信息,我们将重点转移到了环境 收集一些更详细的连接信息。
ss
(socket stats) 是一个强大而方便的 socket/connection 自省工具:
-i/--info
:显示内部 TCP 信息,包括几个 TCP 连接统计信息;-e/--extended
:显示详细的 socket 信息,包括 inode、uid、cookie。
4.2.1 正常情况:显示正确ss
segs_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
状态,这足以让我们检查连接发送/接收统计信息。telnet
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>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\n
Enter
实际上,您可以键入任何内容,只需 ,服务器很可能会 向您发送
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 异常情况:显示不正确ss
segs_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_recvmsg
tcp_recvmsg
inet_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_recvmsg
tcp_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) 不正确bpftrace
tcp_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_drop
sock_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>
此早期版本
导致 初始化为 。psock
sk->sk_prot->recvmsg
tcp_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:连接建立和套接字处理程序初始化。
- Node 客户端(例如 kubelet)-> server:启动与服务器的
TCP 连接
- 内核(以及内核中的 BPF 代码):在侦听已建立的连接时
write two entries to sockmap
link entries to bpf handlers
(tcp_bpf_{sendmsg, recvmsg}
)
- 节点客户端(例如kubelet)>服务器:发送和接收有效负载:BPF处理程序已执行
- 节点客户端(例如 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 快速恢复/修复
如果问题已经发生,您可以使用以下方法之一进行恢复:
内核重启
:耗尽节点然后重启节点,这将刷新内核状态;手动清理
方式 :请谨慎避免删除有效条目。bpftool
5.5 具有类似现象的另一个问题
启用 sockops 时,类似现象还有另一个问题:
- 本地 Pod 运行 nginx(最新版本,例如
>= 1.18
); - 将 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 脚本
引用
- AliOS 内核(一个 Linux 分支),gitee.com/anolis/cloud-kernel
- AWS 上的 Cilium 网络拓扑和流量路径 (2019)
- 纤度 v1.11.10, bpf_sockops.c
- Cilium v1.11.10, BPF sockops键和值定义
- 区分三种类型的 eBPF 重定向
- Trip.com:使用 Cilium/eBPF 的大规模云原生网络和安全,2022 年
编辑
相关文章:

建立连接后 TCP 请求卡住
大家读完觉得有意义记得关注和点赞!!! 这篇文章描述了一个内核和BPF网络问题 以及故障排除步骤,这是一个值得深入研究的有趣案例 Linux 内核网络复杂性。 目录 1 故障报告 1.1 现象:概率健康检查失败 1.2 范围&am…...
尚硅谷redis7 99 springboot整合redis之连接集群
6381宕机,手动shutdown后在redis中,634自动上位变成master结点。 但是在springboot中却没有动态感知道redisCluster的最新集群消息,所以找不到我们要检索的数据。原因是: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 是运行在无线通信模块上的开发框架。对于首次接触物联网开发的用户而言,无线通信模块可能是一个相对陌生的概念。本文主要针对无线通信和蜂窝网络本身,以及模块的概念、特性和开发方式进行简要的介绍。 无线通信和蜂窝网络 物联网对无线通信…...
Go语言之空接口与类型断言
Go 语言中,接口是一种强大的抽象机制。其中,空接口(interface{})和类型断言为我们提供了处理任意类型与类型检查的能力。 一、空接口(interface{}) 空接口是 Go 中最特殊的接口:不包含任何方法…...

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

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

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

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

mysql慢sql的实际处理方案之一
复习mysql架构图 当大批量慢sql过来,显然就是占用了线程池的链接,然后长久不释放,所以会出现线程池满的问题,致使正常业务sql也全部阻塞,影响整个业务。 AI搜索如下: 可以考虑一种方案: 将线…...
GitLab 18.0 正式发布,15.0 将不再受技术支持,须升级【六】
GitLab 是一个全球知名的一体化 DevOps 平台,很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版,专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料: 极狐GitLab 官网极狐…...
c/c++的opencv车牌识别
OpenCV 安装: 你需要正确安装 OpenCV 库。Tesseract OCR 安装: 你需要安装 Tesseract OCR 引擎。在 Ubuntu/Debian 上,可以使用:sudo apt-get install tesseract-ocr sudo apt-get install libtesseract-dev sudo apt-get install…...

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

【论文解读】CVPR2023 PoseFormerV2:3D人体姿态估计(附论文地址)
论文链接:https://arxiv.org/pdf/2303.17472 源码链接:https://github.com/QitaoZhao/PoseFormerV2 Abstract 本文提出了 PoseFormerV2,通过探索频率域来提高 3D 人体姿态估计的效率和鲁棒性。PoseFormerV2 利用离散余弦变换(DC…...
WPF的交互核心:命令系统(ICommand)
命令系统(ICommand) 1 RelayCommand实现2 CanExecute控制按钮可用性3 参数传递(CommandParameter)3.1 静态参数绑定:3.2 动态参数绑定:3.3 复杂对象参数: 4 异步命令实现5 常见问题排查 WPF的命…...

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

uniapp分包配置,uniapp设置subPackages
在使用uniapp开发过程中,由于项目比较大,无法直接上传,需要分包后才可以上传。 步骤: 1、在pages同级目录下创建分包的目录(pages_second),把要分包的文件放到该目录下; 2、在pag…...
计算机网络 HTTP篇常见面试题总结
HTTP各版本区别 HTTP 1.0 无状态、无连接:每次请求都需要建立新的 TCP,处理完后立即关闭,导致开销较大。队头阻塞:每个请求必须按照顺序依次处理,前面的请求未完成,后面的请求只能等待,减低了…...

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

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

【笔记】Windows 成功部署 Suna 开源的通用人工智能代理项目部署日志
#工作记录 本地部署运行截图 kortix-ai/suna: Suna - 开源通用 AI 代理 项目概述 Suna 是一个完全开源的 AI 助手,通过自然对话帮助用户轻松完成研究、数据分析等日常任务。它结合了强大的功能和直观的界面,能够理解用户需求并提供结果。其强…...
关于ffplay在macos上运行奔溃的问题
这个问题大概是由于 MacOS 的问题引起的,奔溃的地方在 SDL2 的代码中,如果直接使用 brew 安装 SDL2就会遇到这个问题,所以需要修改 SDL2源码然后再编译安装。 我这里采用的是 origin/release-2.28.x 分支,修改部分如下࿱…...

Linux531rsync定时同步 再回忆
rsync定时同步 环境配置 关闭防火墙,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数据库:从时间索引到前沿技术 文章目录 探秘浪潮KWDB数据库:从时间索引到前沿技术引言1.浪潮KWDB数据库时间索引深度解析1.1时间索引工作原理1.2时间索引创建与管理实践 2.浪潮KWDB数据库前沿产品技术纵览2.1多模融合存储引擎2.2就地计算技术…...

安卓逆向篇LSP 模块HOOK 添加技术绕过检测算法解密逻辑验证
前置解释: 0 、 Magisk : 是当前 Android 社区用来获取 root 权限的主流方式开源工具 1 、 LSP 框架: XPosed 框架因只支持安卓 8 及以下,故高版本应使用 MagiskLSPosed 2 、 HOOK 技术: 钩子技术&…...
【SQL】关键字
ORDER BY ORDER BY(排序) 语句可以按照一个或多个列的值进行升序(ASC)或降序(DESC)排序。 MAX / MIN MAX() 函数返回一组值中的最大值。这个函数常用于数字字段,但也可以用于文本字段来找出按字典顺序最后的元素。 …...

第一节 51单片机概述
目录 一、单片机系统组成 (一)、单片机硬件系统 (二)单片机的软件系统 二、STC89C52单片机 (1)、基本信息 (2)、命名规则 (3)、单片机内部结构图 &am…...

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

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