k8s centos7 内核漏洞二
Bug #2:诊断修复网络设备引用计数泄漏问题
关键词:kernel:unregister_netdevice: waiting for eth0 to become free. Usage count = 1
社区相关 Issue:
- https://github.com/kubernetes/kubernetes/issues/64743
- https://github.com/projectcalico/calico/issues/1109
- https://github.com/moby/moby/issues/5618
问题起源
我们的薛定谔分布式测试集群运行一段时间后,经常会持续出现“kernel:unregister_netdevice: waiting for eth0 to become free. Usage count = 1” 问题,并会导致多个进程进入不可中断状态,只能通过重启服务器来解决。
问题分析
通过使用 crash 工具对 vmcore 进行分析,我们发现内核线程阻塞在 netdev_wait_allrefs
函数,无限循环等待 dev->refcnt
降为 0。由于 pod 已经释放了,因此怀疑是引用计数泄漏问题。我们查找 K8s issue 后发现问题出在内核上,但这个问题没有简单的稳定可靠复现方法,且在社区高版本内核上依然会出现这个问题。
为避免每次出现问题都需要重启服务器,我们开发一个内核模块,当发现 net_device
引用计数已泄漏时,将引用计数清 0 后移除此内核模块(避免误删除其他非引用计数泄漏的网卡)。为了避免每次手动清理,我们写了一个监控脚本,周期性自动执行这个操作。但此方案仍然存在缺陷:
- 引用计数的泄漏和监控发现之间存在一定的延迟,在这段延迟中 K8s 系统可能会出现其他问题;
- 在内核模块中很难判断是否是引用计数泄漏,
netdev_wait_allrefs
会通过 Notification Chains 向所有的消息订阅者不断重试发布NETDEV_UNREGISTER
和NETDEV_UNREGISTER_FINAL
消息,而经过 trace 发现消息的订阅者多达 22 个,而去弄清这 22 个订阅者注册的每个回调函数的处理逻辑来判断是否有办法避免误判也不是一件简单的事。
解决方案
在我们准备深入到每个订阅者注册的回调函数逻辑的同时,我们也在持续关注 kernel patch 和 RHEL 的进展,发现 RHEL 的 solutions:3659011 有了一个更新,提到 upstream 提交的一个 patch:
route: set the deleted fnhe fnhe_daddr to 0 in ip_del_fnhe to fix a race
在尝试以 hotfix 的方式为内核打上此补丁后,我们持续测试了 1 周,问题没有再复现。我们向 RHEL 反馈测试信息,得知他们已经开始对此 patch 进行 backport。
操作步骤
推荐内核版本 Centos 7.6 kernel-3.10.0-957 及以上。
1.安装 kpatch 及 kpatch-build 依赖:
UNAME=$(uname -r)
sudo yum install gcc kernel-devel-${UNAME%.*} elfutils elfutils-devel
sudo yum install pesign yum-utils zlib-devel \
binutils-devel newt-devel python-devel perl-ExtUtils-Embed \
audit-libs audit-libs-devel numactl-devel pciutils-devel bison
# enable CentOS 7 debug repo
sudo yum-config-manager --enable debug
sudo yum-builddep kernel-${UNAME%.*}
sudo debuginfo-install kernel-${UNAME%.*}
# optional, but highly recommended - enable EPEL 7
sudo yum install ccache
ccache --max-size=5G
2.安装 kpatch 及 kpatch-build:
git clone https://github.com/dynup/kpatch && cd kpatch
make
sudo make install
systemctl enable kpatch
3.下载并构建热补丁内核模块:
curl -SOL https://raw.githubusercontent.com/pingcap/kdt/master/kpatchs/route.patch
kpatch-build -t vmlinux route.patch (编译生成内核模块)
mkdir -p /var/lib/kpatch/${UNAME}
cp -a livepatch-route.ko /var/lib/kpatch/${UNAME}
systemctl restart kpatch (Loads the kernel module)
kpatch list (Checks the loaded module)
总结
虽然我们修复了这些内核错误,但是未来应该会有更好的解决方案。对于 Bug#1,我们希望 K8s 社区可以为 kubelet 提供一个参数,以允许用户禁用或启用 kmem account 功能。对于 Bug#2,最佳解决方案是由 RHEL 和 CentOS 修复内核错误,希望 TiDB 用户将 CentOS 升级到新版后,不必再担心这个问题。