当前位置: 首页 > Linux

TiDBOperator在K8s测试中遇到的Linux内核问题的诊断和修复

时间:2023-04-06 04:20:50 Linux

作者:张文博Kubernetes(K8s)是一个开源的容器编排系统,可以自动化应用部署、伸缩和管理。它是云原生世界的操作系统。K8s或操作系统中的任何缺陷都可能使用户进程面临风险。作为PingCAPEE(效率工程)团队,我们在K8s中测试TiDBOperator(创建和管理TiDB集群的工具)时发现了两个Linux内核错误。这些bug困扰了我们很长时间,并没有在整个K8s社区得到彻底修复。经过广泛的调查和诊断,我们已经确定了处理这些问题的方法。在这篇文章中,我们将与您分享这些解决方案。然而,尽管这些方法有用,但我们认为这只是权宜之计。我们相信未来会有更优雅的解决方案,也期待K8s社区、RHEL和CentOS能够在不久的将来彻底修复这些问题。Bug#1:诊断和修复不稳定的KmemAccounting关键字:SLUB:无法在节点-1上分配内存社区相关问题:https://github.com/kubernetes/kubernetes/issues/61937https://github.com/opencontainers/runc/issues/1725https://support.mesosphere.com/s/article/Critical-Issue-KMEM-MSPH-2018-0006问题的起源Schr?dinger平台是我们开发的一套基于K8s的自动化测试框架公司。提供各种Chaos能力,以及自动化台架测试,各种异常监控,告警,自动输出测试报告。我们发现TiKV在Schr?dinger平台上做OLTP测试时偶尔会出现I/O性能抖动,但是从以下几项没有发现异常:TiKV和RocksDBlogCPUusagememoryanddiskload信息只能偶尔看到dmesg的结果命令执行包含一些“SLUB:无法在节点-1上分配内存”信息。问题分析我们使用perf-tools中的funcslowertrace执行较慢的内核函数并调整内核参数hung_task_timeout_secs阈值,在TiKV进行写操作时捕获一些内核路径信息:从上图中的信息可以看出,我/OJitter与文件系统执行writepage有关。同时,在捕获性能抖动前后,如果节点内存资源充足,dmesg返回的结果中会出现很多“SLUB:Unabletoallocatememoryonnode-1”的信息。hung_task输出的调用栈信息结合内核代码发现,内核在执行bvec_alloc函数分配bio_vec对象时,会先尝试通过kmem_cache_alloc进行分配。kmem_cache_alloc失败后,它会执行回退并尝试从mempool分配。尝试执行pool->alloc回调进行分配。pool->alloc分配失败后,内核会将进程设置为不可中断状态,放入等待队列等待。当其他进程将内存归还给mempool或者定时器超时(5s)后,进程调度器会唤醒进程重试。这个等待时间和我们业务监控的抖动延迟是一致的。但是当我们创建Docker容器时,我们并没有设置kmem限制。为什么还会出现kmem不足的问题?为了判断是否设置了kmem限制,我们进入cgroup内存控制器查看容器的kmem信息,发现启用了kmem统计,但是限制值设置的很大。我们知道kmemaccounting在RHEL3.10内核上不稳定,所以我们怀疑SLUB分配失败是由内核bug引起的。查找内核补丁信息后发现确实是内核bug,社区内核高版本已经修复:slub:makedeadcachesdiscardfreeslabsimmediately还有一个与kmem相关的namespaceleak问题accounting:mm:memcontrol:fixcgroupcreationfailureaftermanysmalljobs谁打开了kmemaccounting功能?我们使用bcc中的opensnoop工具来监控kmem配置文件,抓取修饰符runc。从K8s代码可以确认,K8s依赖的runc项目默认开启了kmemaccounting。解决方案通过以上分析,我们要么升级到更高版本的内核,要么在启动容器时关闭kmemaccounting功能。目前runc已经提供了条件编译选项,可以通过BuildTags关闭kmemaccounting。关闭后,我们测试发现抖动消失了,命名空间泄漏问题和SLUB分配失败问题消失了。操作步骤我们需要关闭kubelet和docker上的kmem账号功能。kubelet需要重新编译,不同版本有不同的方法。如果kubelet版本为v1.14及以上,可以在编译kubelet时通过添加BuildTags来关闭kmem账号:$gitclone--branchv1.14.1--single-branch--depth1[https://github.com/kubernetes/kubernetes](https://github.com/kubernetes/kubernetes)$cdkubernetes$KUBE_GIT_VERSION=v1.14.1./build/run.shmakekubeletGOFLAGS="-tags=nokmem"但如果kubelet如果v1.13及以下版本,编译kubelet时不能通过添加BuildTags关闭。kubelet需要重新编译。步骤如下。首先下载Kubernetes代码:$gitclone--branchv1.12.8--single-branch--depth1https://github.com/kubernetes/kubernetes$cdkubernetes然后手动替换启用kmem账户功能的两个函数with如下:funcEnableKernelMemoryAccounting(pathstring)error{returnnil}funcsetKernelMemory(pathstring,kernelMemoryLimitint64)error{returnnil}然后重新编译kubelet:$KUBE_GIT_VERSION=v1.12.8./build/run.shmakekubeletcompiledkubelet在./_output/dockerized/bin/$GOOS/$GOARCH/kubelet中。同时需要将docker-ce升级到18.09.1或以上版本。该版本docker禁用了runc的kmem账号功能。最后,需要重启机器。验证方法是检查新创建的pod的所有容器是否都关闭了kmem,如果关闭了如下结果:$cat/sys/fs/cgroup/memory/kubepods/burstable/pod//memory.kmem.slabinfocat:memory.kmem.slabinfo:Input/outputerrorBug#2:诊断和修复网络设备引用计数泄漏关键字:kernel:unregister_netdevice:等待eth0空闲。Usagecount=1社区相关Issue:https://github.com/kubernetes/kubernetes/issues/64743https://github.com/projectcalico/calico/issues/1109https://github.com/moby/moby/issues/5618问题的根源是我们的Schr?dinger分布式测试集群在运行一段时间后,经常会不断出现“kernel:unregister_netdevice:waitingforeth0tobecomefree.usagecount=1”的问题,并且会导致多次进程进入不可中断状态,只能通过重启服务器解决。问题分析通过crash工具分析vmcore,发现内核线程阻塞在netdev_wait_allrefs函数中,死循环等待dev->refcnt降为0。由于pod已经释放,怀疑是引用计数泄漏。查找K8sissue后发现问题出在内核上,但是没有简单、稳定、可靠的复现方法,在社区的高版本内核上依然出现该问题。为了避免每次出现问题都需要重启服务器,我们开发了一个内核模块。当发现net_device的引用计数泄漏时,将引用计数清0后移除内核模块(避免误删其他没有引用计数泄漏的网卡)。为了避免每次都手动清理,我们写了一个监控脚本,定时自动执行这个操作。但是这个方案还是有缺陷的:引用计数泄露和监控发现之间存在一定的延迟,这段延迟期间K8s系统可能会出现其他问题;很难判断是否是内核模块引用计数泄露,netdev_wait_allrefs会通过NotificationChains不断重试向所有消息订阅者发布NETDEV_UNREGISTER和NETDEV_UNREGISTER_FINAL消息,通过trace发现订阅者多达22个,并且需要搞清楚这22个订阅者注册的每个回调函数的处理逻辑,判断是否有办法避免误判,这不是一件简单的事情。解决方案在我们准备深入研究各个订阅者注册的回调函数逻辑的同时,我们也在持续关注内核补丁和RHEL的进展,发现RHEL的解决方案:3659011有更新,提了一个补丁upstream提交:route:setthedeletedfnhefnhe_daddrto0inip_del_fnhetofixarace在尝试将此补丁作为hotfix打到内核后,我们继续测试了1周,问题没有再出现。我们将测试信息反馈给了RHEL,得知他们已经开始backporting这个patch了。操作步骤推荐内核版本Centos7.6kernel-3.10.0-957及以上。安装kpatch和kpatch-build依赖项:UNAME=$(uname-r)sudoyuminstallgcckernel-devel-${UNAME%.*}elfutilselfutils-develsudoyuminstallpesignyum-utilszlib-devel\binutils-develnewt-开发python-develperl-ExtUtils-Embed\audit-libsaudit-libs-develnumactl-develpciutils-develbison#enableCentOS7debugreposudoyum-config-manager--enabledebugsudoyum-builddepkernel-${UNAME%.*}sudodebuginfo-installkernel-${UNAME%.*}#可选,但强烈推荐-启用EPEL7sudoyuminstallccacheccache--max-size=5G安装kpatch和kpatch-build:gitclonehttps://github。com/dynup/kpatch&&cdkpatchmakesudomakeinstallsystemctlenablekpatch下载并构建热补丁内核模块:curl-SOLhttps://raw.githubusercontent.com/pingcap/kdt/master/kpatchs/route.patchkpatch-build-tvmlinuxroute.patch(编译生成内核模块)mkdir-p/var/lib/kpatch/${UNAME}cp-alivepatch-route.ko/var/lib/kpatch/${UNAME}systemctlrestartkpatch(加载内核模块)kpatchlist(检查加载的module)总结虽然我们已经修复了这些内核错误,但未来应该会有更好的解决方案。对于Bug#1,我们希望K8s社区能够为kubelet提供一个参数,允许用户关闭或开启kmem账号功能。对于Bug#2,最好的解决方案是通过RHEL和CentOS修复内核错误。希望TiDB用户在升级CentOS到新版本后不用再担心这个问题。