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

ftrace calico netns问题 - 小镇

ftrace calico netns问题

image

 

Kubernetes/CNI 网络设备删除阻塞问题 RCA 文档

——基于 Linux Netlink、Network Namespace、net_device 引用计数的分析方法


1. 问题背景

1.1 故障现象

节点持续出现:

kernel: unregister_netdevice: waiting for eth0 to become free. Usage count = 1

同时:

  • Kubernetes Pod 网络异常;

  • Canal(Calico + Flannel)组件异常;

  • kubelet出现:

CrashLoopBackOff
failed to StartContainer calico-node

表象:

  • docker容器正常;

  • sandbox正常;

  • netns看起来正常;

  • 但是内核持续报警。


2. 核心分析思路

2.1 不从容器看问题,而从内核网络生命周期看问题

很多人看到:

calico异常
pod异常
sandbox异常

第一反应:

找异常容器。

但是这个问题不是容器层问题。

实际对象:

Container||
Network Namespace||
veth pair||
Linux net_device||
kernel reference count

最终报错对象:

struct net_device

不是:

  • container

  • pod

  • pause

  • namespace


3. 涉及的基础技术


3.1 Linux Network Namespace

定义

Network Namespace 是 Linux 内核提供的网络隔离机制。

隔离:

  • 网卡

  • IP地址

  • 路由表

  • iptables

  • socket

例如:

宿主机:

namespace Aeth0
lo
cni0

容器:

namespace Beth0
lo

Kubernetes中的关系

一个Pod:

Pod|+-- pause container|+-- Network Namespace|+-- eth0

业务容器:

container|
共享|
pause network namespace

所以:

docker inspect calico-node

看到:

NetworkMode:
container:<pause id>

是正常设计。


3.2 veth Pair技术

Kubernetes容器网络依靠:

veth pair

实现。

结构:

        Host Namespacevethxxxx||kernel||eth0Pod Namespace

一端在宿主:

vethxxxx

一端在Pod:

eth0

删除Pod:

流程:

kubelet|CNI DEL|calico|删除veth|kernel释放net_device

3.3 Linux net_device对象

Linux所有网络设备:

eth0
vethxxx
bond0
bridge

最终都是:

struct net_device

管理。

生命周期:

alloc_netdev()||
register_netdevice()||
使用||
unregister_netdevice()||
释放

3.4 引用计数(refcount)

Linux内核大量对象使用引用计数。

目的:

防止:

对象正在使用
但是被释放

网络设备:

net_device|refcount

增加:

dev_hold()

减少:

dev_put()

正常:

创建|
ref=1|
使用|
释放引用|
ref=0|
free

异常:

创建|
ref=1|
某模块引用|
删除设备|
ref=1|
永远等待

4. unregister_netdevice报错原理

代码逻辑:

unregister_netdevice()||v
判断设备是否还有引用||v
netdev_wait_allrefs()||v
等待refcount下降

如果:

refcount != 0

打印:

waiting for eth0 to become free
Usage count = 1

5. 为什么不是sandbox泄露

常见误判

认为:

eth0删除失败
=
残留sandbox

实际:

不一定。

sandbox泄露:

pause container存在|
netns存在|
eth0存在

当前检查:

lsns -t net

没有异常。

检查:

docker ps

没有孤儿。

说明:

问题发生在:

net_device释放阶段

而不是:

namespace阶段

6. 故障定位技术路线

整体思路:

现象||
kernel日志||
定位内核函数||
确定调用入口||
定位用户态调用者||
定位组件

7. 第一步:确认是不是网络设备删除

日志:

unregister_netdevice

关键词:

unregister

说明:

正在删除设备。


8. 第二步:使用ftrace/kprobe定位调用栈

基础技术

Linux Kernel Trace Framework:

ftrace

能力:

  • 跟踪函数调用;

  • 动态插桩;

  • 获取kernel stack。


kprobe

动态插入探针:

kernel function||+---- probe

无需重新编译内核。


9. 实际抓取过程

创建probe:

cd /sys/kernel/debug/tracingecho > kprobe_eventsecho \
'p:netmany unregister_netdevice_many' \
> kprobe_events

开启stack:

echo 1 > options/stacktrace

开启:

echo 1 > events/kprobes/netmany/enableecho 1 > tracing_on

查看:

cat trace_pipe

10. 关键trace分析

得到:

unregister_netdevice_manynotifier_call_chainnetdev_run_todortnetlink_rcvnetlink_sendmsgSYSC_sendto

11. 这个调用链说明什么?

重点:

rtnetlink_rcv

rtnetlink是什么?

Linux网络配置接口。

用户态:

iproute2

例如:

ip link del vethxxx

底层:

NETLINK_ROUTE socket

发送:

RTM_DELLINK

进入:

rtnetlink_rcv()

因此:

调用链:

用户态|
sendto()|
NETLINK_ROUTE|
kernel|
删除网卡

证明:

不是内核主动。

而是:

某个用户态网络组件请求删除设备

12. 第三步:定位用户态调用者

trace:

<...>-188907

PID:

188907

查看:

cat /proc/188907/cmdline

结果:

/opt/cni/bin/calico

cgroup:

system.slice/kubelet.service

说明:

关系:

kubelet||
CNI调用||
calico binary

13. 最终调用链

完整链路:

Kubernetes Pod生命周期变化|vkubelet|v调用 CNI DEL|v/opt/cni/bin/calico|vNETLINK_ROUTE|vRTM_DELLINK|vunregister_netdevice_many()|v发现eth0 refcount=1|v等待释放|vkernel报警

14. 为什么没有异常进程?

因为:

删除动作已经发生:

calico执行删除|v
kernel进入删除流程|v
calico退出

但是:

kernel对象还活着

所以:

ps
docker ps
lsns

可能全部正常。


15. 关闭和恢复方法


15.1 临时停止触发源

停止 kubelet:

systemctl stop kubelet

作用:

停止:

  • Pod调度

  • CNI ADD

  • CNI DEL


15.2 重启Calico

查看:

kubectl -n kube-system get pod | grep canal

删除:

kubectl -n kube-system delete pod canal-vlrk9

DaemonSet自动恢复。


15.3 重启Docker

systemctl restart docker

15.4 节点重启

如果:

Usage count=1

持续:

数分钟以上

说明:

kernel引用无法恢复。

执行:

reboot

16. 关闭trace调试

如果开启过:

进入:

cd /sys/kernel/debug/tracing

关闭:

echo 0 > tracing_onecho 0 > events/kprobes/netmany/enableecho > kprobe_eventsecho nop > current_tracer

17. 长期治理方案

17.1 升级Calico

原因:

历史版本可能存在:

  • CNI DEL异常;

  • veth删除竞态;

  • 网络资源释放问题。


17.2 升级Kernel

重点:

老版本:

3.10
4.14
4.19早期

存在大量:

  • netns cleanup race

  • veth teardown问题

推荐:

5.4+
5.10+

17.3 升级Docker

避免组合:

旧kernel
+
旧docker
+
旧CNI

18. 故障定位方法论总结

本次采用的方法:

日志现象|v
确定内核对象(net_device)|v
分析生命周期(unregister)|v
ftrace/kprobe动态追踪|v
找到调用栈|v
确认netlink入口|v
定位用户态PID|v
定位CNI组件

核心技术:

技术作用
Network Namespace 判断容器网络隔离
veth pair 理解Pod网络
net_device 定位内核对象
refcount 理解释放阻塞
Netlink 定位用户态网络操作
rtnetlink 确认删除请求来源
ftrace 获取内核调用路径
kprobe 动态追踪函数

19. 最终RCA结论

根因:

Calico CNI在Pod网络生命周期处理过程中,通过Netlink触发网络设备删除;Linux内核在注销 net_device 时发现仍存在一个引用计数未释放,导致 unregister_netdevice_many() 阻塞,持续打印:

unregister_netdevice: waiting for eth0 to become free. Usage count = 1

定位依据:

  1. 排除sandbox/netns泄露;

  2. kprobe确认进入 unregister_netdevice_many

  3. 调用栈确认来源:

rtnetlink_rcv-> netlink_sendmsg-> syscall
  1. PID定位:

/opt/cni/bin/calico
  1. cgroup确认:

system.slice/kubelet.service

最终确定:

kubelet -> calico CNI -> rtnetlink -> kernel net_device释放异常

该分析方法适用于所有:

  • Kubernetes CNI网络异常;

  • veth删除失败;

  • netns清理异常;

  • unregister_netdevice卡死;

  • Docker网络泄露问题。