文档章节

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

TiDB
 TiDB
发布于 05/27 13:38
字数 2392
阅读 23
收藏 0

作者:张文博

Kubernetes(K8s)是一个开源容器编排系统,可自动执行应用程序部署、扩展和管理。它是云原生世界的操作系统。 K8s 或操作系统中的任何缺陷都可能使用户进程存在风险。作为 PingCAP EE(效率工程)团队,我们在 K8s 中测试 TiDB Operator(一个创建和管理 TiDB 集群的工具)时,发现了两个 Linux 内核错误。这些错误已经困扰我们很长一段时间,并没有在整个 K8s 社区中彻底修复。

经过广泛的调查和诊断,我们已经确定了处理这些问题的方法。在这篇文章中,我们将与大家分享这些解决方法。不过,尽管这些方法很有用,但我们认为这只是权宜之策,相信未来会有更优雅的解决方案,也期望 K8s 社区、RHEL 和 CentOS 可以在不久的将来彻底修复这些问题。

Bug #1: 诊断修复不稳定的 Kmem Accounting

关键词:SLUB: Unable to allocate memory on node -1

社区相关 Issue:

问题起源

薛定谔平台是我司开发的基于 K8s 建立的一套自动化测试框架,提供各种 Chaos 能力,同时也提供自动化的 Bench 测试,各类异常监控、告警以及自动输出测试报告等功能。我们发现 TiKV 在薛定谔平台上做 OLTP 测试时偶尔会发生 I/O 性能抖动,但从下面几项来看未发现异常:

  • TiKV 和 RocksDB 的日志
  • CPU 使用率
  • 内存和磁盘等负载信息

只能偶尔看到 dmesg 命令执行的结果中包含一些 “SLUB: Unable to allocate memory on node -1” 信息。

问题分析

我们使用 perf-tools 中的 funcslower trace 来执行较慢的内核函数并调整内核参数 hung_task_timeout_secs 阈值,抓取到了一些 TiKV 执行写操作时的内核路径信息:

从上图的信息中可以看到 I/O 抖动和文件系统执行 writepage 有关。同时捕获到性能抖动的前后,在 node 内存资源充足的情况下,dmesg 返回的结果也会出现大量 “SLUB: Unable to allocate memory on node -1” 的信息。

hung_task 输出的 call stack 信息结合内核代码发现,内核在执行 bvec_alloc 函数分配 bio_vec 对象时,会先尝试通过 kmem_cache_alloc 进行分配,kmem_cache_alloc 失败后,再进行 fallback 尝试从 mempool 中进行分配,而在 mempool 内部会先尝试执行 pool->alloc 回调进行分配,pool->alloc 分配失败后,内核会将进程设置为不可中断状态并放入等待队列中进行等待,当其他进程向 mempool 归还内存或定时器超时(5s) 后,进程调度器会唤醒该进程进行重试 ,这个等待时间和我们业务监控的抖动延迟相符。

但是我们在创建 Docker 容器时,并没有设置 kmem limit,为什么还会有 kmem 不足的问题呢?为了确定 kmem limit 是否被设置,我们进入 cgroup memory controller 对容器的 kmem 信息进行查看,发现 kmem 的统计信息被开启了, 但 limit 值设置的非常大。

我们已知 kmem accounting 在 RHEL 3.10 版本内核上是不稳定的,因此怀疑 SLUB 分配失败是由内核 bug 引起的,搜索 kernel patch 信息我们发现确实是内核 bug, 在社区高版本内核中已修复:

slub: make dead caches discard free slabs immediately

同时还有一个 namespace 泄漏问题也和 kmem accounting 有关:

mm: memcontrol: fix cgroup creation failure after many small jobs

那么是谁开启了 kmem accounting 功能呢?我们使用 bcc 中的 opensnoop 工具对 kmem 配置文件进行监控,捕获到修改者 runc 。从 K8s 代码上可以确认是 K8s 依赖的 runc 项目默认开启了 kmem accounting。

解决方案

通过上述分析,我们要么升级到高版本内核,要么在启动容器的时候禁用 kmem accounting 功能,目前 runc 已提供条件编译选项,可以通过 Build Tags 来禁用 kmem accounting,关闭后我们测试发现抖动情况消失了,namespace 泄漏问题和 SLUB 分配失败的问题也消失了。

操作步骤

我们需要在 kubelet 和 docker 上都将 kmem account 功能关闭。

  1. kubelet 需要重新编译,不同的版本有不同的方式。

    如果 kubelet 版本是 v1.14 及以上,则可以通过在编译 kubelet 的时候加上 Build Tags 来关闭 kmem account:

    	$ git clone --branch v1.14.1 --single-branch --depth 1 [https://github.com/kubernetes/kubernetes](https://github.com/kubernetes/kubernetes) 
    	$ cd kubernetes
    
    	$ KUBE_GIT_VERSION=v1.14.1 ./build/run.sh make kubelet GOFLAGS="-tags=nokmem"
    

    但如果 kubelet 版本是 v1.13 及以下,则无法通过在编译 kubelet 的时候加 Build Tags 来关闭,需要重新编译 kubelet,步骤如下。

    首先下载 Kubernetes 代码:

    	$ git clone --branch v1.12.8 --single-branch --depth 1 https://github.com/kubernetes/kubernetes
    	$ cd kubernetes
    

    然后手动将开启 kmem account 功能的 两个函数 替换成 下面这样

    	func EnableKernelMemoryAccounting(path string) error {
    		return nil
    	}
    
    	func setKernelMemory(path string, kernelMemoryLimit int64) error {
    		return nil
    	}
    
    

    之后重新编译 kubelet:

    	$ KUBE_GIT_VERSION=v1.12.8 ./build/run.sh make kubelet
    

    编译好的 kubelet 在 ./_output/dockerized/bin/$GOOS/$GOARCH/kubelet 中。

  2. 同时需要升级 docker-ce 到 18.09.1 以上,此版本 docker 已经将 runc 的 kmem account 功能关闭。

  3. 最后需要重启机器。

验证方法是查看新创建的 pod 的所有 container 已关闭 kmem,如果为下面结果则已关闭:

$ cat /sys/fs/cgroup/memory/kubepods/burstable/pod<pod-uid>/<container-id>/memory.kmem.slabinfo
cat: memory.kmem.slabinfo: Input/output error

Bug #2:诊断修复网络设备引用计数泄漏问题

关键词:kernel:unregister_netdevice: waiting for eth0 to become free. Usage count = 1

社区相关 Issue:

问题起源

我们的薛定谔分布式测试集群运行一段时间后,经常会持续出现“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_UNREGISTERNETDEV_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 升级到新版后,不必再担心这个问题。

© 著作权归作者所有

TiDB
粉丝 163
博文 202
码字总数 539133
作品 4
海淀
私信 提问
Cloud 团队:让 TiDB 在云上跳舞 | PingCAP 招聘季

TiDB 是 Cloud Native 的数据库,对于 TiDB 来说,如何用 Cloud 的思想和技术让 TiDB 在云上跳舞,是 Cloud 团队研究的重要课题,本期我司商业产品副总裁刘寅老师将为大家介绍 Cloud 团队,E...

TiDB
03/25
0
0
自动化部署运维工具 - TiDB Operator

TiDB Operator 是 TiDB 在 Kubernetes 平台上的自动化部署运维工具,借助 TiDB Operator,TiDB 可以无缝运行在公有云厂商提供的 Kubernetes 平台上,让 TiDB 成为真正的 Cloud-Native 数据库...

TiDB
2018/08/23
0
0
tidb使用过程中遇到问题汇总

本文内容是马上消费金融DBA团队使用tidb遇到的问题汇总.李银龙主负责整理. 问题一: tidb与mk-table-checksum不兼容 描述: 在gh-ost与syncer兼容性测试时,做一致性校验时,发现tidb与mk-tabl...

梧桐0928
2018/08/14
0
0
PingCAP 开源 TiDB 自动化部署运维工具 TiDB Operator

TiDB Operator 是 TiDB 在 Kubernetes 平台上的自动化部署运维工具,目前,TiDB Operator 已经正式开源。 借助 TiDB Operator,TiDB 可以无缝运行在公有云厂商提供的 Kubernetes 平台上,让 ...

TiDB
2018/08/23
1K
2
开源 TiDB Operator 让 TiDB 成为真正的 Cloud-Native 数据库

TiDB Operator 是 TiDB 在 Kubernetes 平台上的自动化部署运维工具,借助 TiDB Operator,TiDB 可以无缝运行在公有云厂商提供的 Kubernetes 平台上,让 TiDB 成为真正的 Cloud-Native 数据库...

CSDN资讯
2018/08/23
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Quartz原理解析

Quartz原理解析 最近项目中好多地方都需要用到定时器,一开始用的是netty的hashWheel,后来发现删除任务的时候不是很好删除,于是就放弃了,然后选择了Quartz。 hashWheel定时器和Quartz的区...

石日天
30分钟前
0
0
explain详解

EXPLAIN列的解释 table 显示这一行的数据是关于哪张表的 type 这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为 const(读常量,最多只会有一条记录匹配,由于是常量,实际上...

周慕云
33分钟前
1
0
Oracle 修改或新增数据后查不到数据

修改或新增数据后数据库中SQL能查到但执行程序却查不到 因为AutoCommit is OFF 所以 每次新增或修改数据后都要commit 一下,不然只是post edit 的话,执行程序能查到的只是未更新的数据。...

南风末
今天
4
0
python学习整理(1)

#!/usr/bin/env python # -*- conding:utf-8 -*- 1、 python运算: + - * / % ** // In [21]: print(int(1.2)+3) 4 In [22]: print(float(1.2)+3) 4.2 In [15]: print(11//5) 2 In [16]: prin......

芬野de博客
今天
3
0
maven 在无法连接仓库的单机环境下打包程序

前提:依赖的jar已经在本机。 方法:以ojdbc6-11.2.0.4.jar 为例,进入.m2\repository\com\oracle\jdbc\ojdbc6\11.2.0.4 目录,编辑_remote.repositories文件,改写如下: ojdbc6-11.2.0.4....

jingshishengxu
今天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部