ftrace calico netns问题1

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 A
eth0
lo
cni0
容器:
namespace B
eth0
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 Namespace
vethxxxx
|
|
kernel
|
|
eth0
Pod 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/tracing
echo > kprobe_events
echo \
'p:netmany unregister_netdevice_many' \
> kprobe_events
开启stack:
echo 1 > options/stacktrace
开启:
echo 1 > events/kprobes/netmany/enable
echo 1 > tracing_on
查看:
cat trace_pipe
10. 关键trace分析
得到:
unregister_netdevice_many
notifier_call_chain
netdev_run_todo
rtnetlink_rcv
netlink_sendmsg
SYSC_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生命周期变化
|
v
kubelet
|
v
调用 CNI DEL
|
v
/opt/cni/bin/calico
|
v
NETLINK_ROUTE
|
v
RTM_DELLINK
|
v
unregister_netdevice_many()
|
v
发现eth0 refcount=1
|
v
等待释放
|
v
kernel报警
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_on
echo 0 > events/kprobes/netmany/enable
echo > kprobe_events
echo 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网络泄露问题。
浙公网安备 33010602011771号