作者:张文博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
