ftrace calico netns问题

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
定位依据:
-
排除sandbox/netns泄露;
-
kprobe确认进入
unregister_netdevice_many; -
调用栈确认来源:
rtnetlink_rcv-> netlink_sendmsg-> syscall
-
PID定位:
/opt/cni/bin/calico
-
cgroup确认:
system.slice/kubelet.service
最终确定:
kubelet -> calico CNI -> rtnetlink -> kernel net_device释放异常
该分析方法适用于所有:
-
Kubernetes CNI网络异常;
-
veth删除失败;
-
netns清理异常;
-
unregister_netdevice卡死; -
Docker网络泄露问题。
