企业如何通过vue小程序开发满足高效运营与合规性需求
2591
2022-11-30
Kubernetes CentOS7.4 系统内核升级 修复 k8s 内存泄露问题
为什么要升级内核
本文中所使用到的OS为Ubuntu 16.04,用户均为root用户。升级内核为必须条件。在低版本的内核中会出现一下很让人恼火的Bug,时不时来一下,发作时候会导致整个OS Hang住无法执行任何命令。 现象如下:
kernel:unregister_netdevice: waiting for lo to become free. Usage count = 1
关于这个Bug,你可以从以下地方追踪到:
而根据我实际的实验(采坑)下来,这个问题我花费了差不多1个多月的时间先后尝试了内核版本3.10、4.4、4.9、4.12、4.14、4.15版本,均会不同程度的复现上述Bug,而一旦触发并无他法,只能重启(然后祈祷不要再次触发)。 实在是让人寝食难安,睡不踏实,直到我遇到了内核4.17,升级完毕之后,从6月到现在。重来没有复现过。似乎可以认为该Bug已经修复了。故而强烈建议升级内核到4.17+。
前言
这篇文章的全称应该叫:[在某些内核版本上,cgroup 的 kmem account 特性有内存泄露问题],如果你遇到过 pod 的 "cannot allocated memory"报错,node 内核日志的“SLUB: Unable to allocate memory on node -1”报错,那么恭喜你中招了。
这个问题在 pingcap 的文章和腾讯云的官方修复都发过,原因也讲的很清楚,不过因为版本差异,文章里的方法有所变动,这里做下总结
3.10的kernel 问题太多了。也是k8s 支持的最低版本。
为什么要升级内核?
由于Docker 在CentOS系统中需要安装在 CentOS 7 64 位的平台,并且内核版本不低于 3.10;CentOS 7.× 满足要求的最低内核版本要求,但由于 CentOS 7默认内核版本比较低,部分功能(如 overlay2 存储层驱动)无法使用,并且部分功能可能不太稳定。所以建议大家升级到最新的稳定内核版本。
今天k8s集群服务器突然出现这个警告错误,网上查了一下,建议升级内核版本
kernel:unregister_netdevice: waiting for lo to become free. Usage count = 1
原因
一句话总结:
cgroup 的 kmem account 特性在 3.x 内核上有内存泄露问题,如果开启了 kmem account 特性会导致可分配内存越来越少,直到无法创建新 pod 或节点异常。
几点解释:
kmem account 是cgroup 的一个扩展,全称CONFIG_MEMCG_KMEM,属于机器默认配置,本身没啥问题,只是该特性在 3.10 的内核上存在漏洞有内存泄露问题,4.x的内核修复了这个问题。因为 kmem account 是 cgroup 的扩展能力,因此runc、docker、k8s 层面也进行了该功能的支持,即默认都打开了kmem 属性因为3.10 的内核已经明确提示 kmem 是实验性质,我们仍然使用该特性,所以这其实不算内核的问题,是 k8s 兼容问题。
其他细节原因下面会解释
解决方案
既然是 3.x 的问题,直接升级内核到 4.x 及以上即可,内核问题解释:
pod 肯定要漂移,如果节点规模很大,这个升级操作会很繁琐,业务部门也会有意见,要事先沟通。这个问题归根结底是软件兼容问题,3.x 自己都说了不成熟,不建议你使用该特性,k8s、docker却 还要开启这个属性,那就不是内核的责任,因为我们是云上机器,想替换4.x 内核需要虚机团队做足够的测试和评审,因此这是个长期方案,不能立刻解决问题。已有业务在 3.x 运行正常,不代表可以在 4.x 也运行正常,即全量升级内核之前需要做足够的测试,尤其是有些业务需求对os做过定制。
因为 2 和 3 的原因,我们没有选择升级内核,决定使用其他方案
[root@jenkins-master ~]# cat /etc/redhat-release CentOS Linux release 7.4.1708 (Core) [root@jenkins-master ~]# uname -r3.10.0-693.el7.x86_647.4版本操作系统升级 rpms.tar# 升级Master节点和Node节点内核至 4.19.20-1.el7.x86_64 版本,步骤:# 上传rpm安装包,执行rpm安装yum localinstall -y kernel-ml-* --skip-broken# 修改内核启动参数sed -i 's/saved/0/g' /etc/default/grub# 重建grubgrub2-mkconfig -o /boot/grub2/grub.cfg# 检查grub启动参数 grep "^menuentry" /boot/grub2/grub.cfgmenuentry 'CentOS Linux (4.19.20-1.el7.x86_64) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-4.19.20-1.el7.x86_64-advanced-918bc4e0-3f40-4ee7-89f3-0acbd5e60266'# awk -F\' '$1=="menuentry " {print i++ " : " $2}' /etc/grub2.cfg0 : CentOS Linux (4.19.20-1.el7.x86_64) 7 (Core) 1 : CentOS Linux (3.10.0-1127.el7.x86_64) 7 (Core) 2 : CentOS Linux (0-rescue-ce96d80aad324672909914d327a2d91c) 7 (Core)# 重启服务器# sudo reboot
Why Kernel 4?
Kernel requirement for Containerd
requirement for Kubernetes
Kernel 3.10 is just the minimum requirement, you can see 4+ or even 5+ is also supported by Kubernetes. The latest CentOS7.6 releases on kernel 3.10. No any plan for kernel 4. While some of the kernel feature like ‘kernel memory accounting (kmem)’ that Kubernetes enables by default is only experimental in kernel 3, but stable on kernel 4. Docker provides the kernel version check and disables kmem if kernel is 3.x. But I can’t find any flags to disable kmem from Kubernetes. One possible way to fix it is to rebuild Kubernetes from source code with build flag to disable the kmem accounting from runc. 3.10只是最低要求,你可以看到Kubernetes也支持4+甚至5+。内核 3.10 上最新的 CentOS7.6 版本。没有针对内核 4 的任何计划。虽然 Kubernetes 默认启用的一些内核功能,例如“内核内存记帐 (kmem)”,仅在内核 3 中处于试验阶段,但在内核 4 上是稳定的。
Docker 提供内核版本检查并在以下情况下禁用 kmem内核是 3.x。但是我找不到从 Kubernetes 禁用 kmem 的任何标志。修复它的一种可能方法是使用构建标志从源代码重建 Kubernetes,以禁用来自 runc 的 kmem 记帐。
Other situations
The kernel memory leak bug in 3.x that has been happening for quite a few weeks for us: SLUB: Unable to allocate memory on node -1, which crashes the Docker node. The kernel memory accounting is only experimental on kernel 3, but stable on kernel 4.No fix for CentOS now. See issue,kernel socket leak in 3.x I have met: unregister_netdevice: waiting for eth0 to become free. Usage count = 1. It is fixed in Ubuntu kernel 4.4.114.net: tcp: close sock if net namespace is exiting. Seechanglog. You can also find the fix by linus torvalds innet: tcp: close sock if net namespace is exiting. But no CentOS fix was ever found so far. I have rebuiltan enhanced CentOS on kernel 3.10.0.957.10.1.Kernel 3.10 is only tech preview on overlayfs. kernel: [ 24.062493] TECH PREVIEW: Overlay filesystem may not be fully supported.As you can see from the above requirement examples, seems the container ecosystem mainly develops on kernel 4.x now. It should be so in the future as well. Vendors will not prefer to provide fixes for kernel 3, take the kernel socket leak bug for example, it has been existing for several years on CentOS.Even if we fix those 2 kernel bugs, how can we be sure that the future unknown potential bugs can be officially fixed in time? But the kernel 4.x can be timely patched once such critical bugs are found, as the whole ecosystem mainly plays on it.Docker has the fixes for kernel memory leak, while k8s doesn’t have, seems it mainly considers & develops on kernel 4.x. Also Kubernetes is going to work without Docker, it has its own crictl tools which can replace docker client. Moreover it includes the containerd/runc as its vendor code directly. It is now possible to totally discard Docker when setting up a Kubernetes cluster, seeKubernetes: Using containerd 1.1 without Docker - aboutsimon.comKubernetes only patches the last 3 minor versions. And Minor releases occur approximately every 3 months. So we are moving to the next minor version in 9 months, as Kubernetes will depend more and more heavily on kernel 4.x features. We will be in high risk on kernel 3.x when k8s version goes up. If we won’t upgrade following the pace, we can’t get the latest security patches from Kubernetes.Docker now only patches 17.06+. The latest version is 1809.3. Redhat still uses Docker 1.13.1, because their kernel is too old to run newer Docker. The 1.13.1 is even older than 17.03, which is no longer in maintenance scope.CoreOS adopted kernel 4 very early. In 2015RHEL 8 defaults on kernel 4.x, but it is beta. No timeline for GA yet.Red Hat Enterprise Linux Overview | Red Hat DeveloperCentOS officially doesn’t support kernel 4.x by for now.Analysis of Kernel & OS for running Kubernetes - Gary's Understandings
问题一:修复 K8S 内存泄露问题
问题描述
当 k8s 集群运行日久以后,有的 node 无法再新建 pod,并且出现如下错误,当重启服务器之后,才可以恢复正常使用。查看 pod 状态的时候会出现以下报错。
applying cgroup … caused: mkdir …no space left on device
或者在 describe pod 的时候出现 cannot allocate memory。
这时候你的 k8s 集群可能就存在内存泄露的问题了,当创建的 pod 越多的时候内存会泄露的越多,越快。
具体查看是否存在内存泄露
$ cat /sys/fs/cgroup/memory/kubepods/memory.kmem.slabinfo
当出现 cat: /sys/fs/cgroup/memory/kubepods/memory.kmem.slabinfo: Input/output error 则说明不存在内存泄露的情况 如果存在内存泄露会出现
slabinfo - version: 2.1# name
解决方案
解决方法思路:关闭 runc 和 kubelet 的 kmem,因为升级内核的方案改动较大,此处不采用。kmem 导致内存泄露的原因:
内核对于每个 cgroup 子系统的的条目数是有限制的,限制的大小定义在 kernel/cgroup.c #L139,当正常在 cgroup 创建一个 group 的目录时,条目数就加 1。我们遇到的情况就是因为开启了 kmem accounting 功能,虽然 cgroup 的目录删除了,但是条目没有回收。这样后面就无法创建 65535 个 cgroup 了。也就是说,在当前内核版本下,开启了 kmem accounting 功能,会导致 memory cgroup 的条目泄漏无法回收。
2.1 编译 runc
配置 go 语言环境
$ wget tar xf go1.12.9.linux-amd64.tar.gz -C /usr/local/# 写入bashrc$ vim ~/.bashrc$ export GOPATH="/data/Documents"$ export GOROOT="/usr/local/go"$ export PATH="$GOROOT/bin:$GOPATH/bin:$PATH"$ export GO111MODULE=off# 验证$ source ~/.bashrc$ go env
- runc 源码
$ mkdir -p /data/Documents/src/github.com/opencontainers/$ cd /data/Documents/src/github.com/opencontainers/$ git clone cd runc/$ git checkout v1.0.0-rc9 # 切到v1.0.0-rc9 tag
编译
# 安装编译组件$ sudo yum install libseccomp-devel$ make BUILDTAGS='seccomp nokmem'# 编译完成之后会在当前目录下看到一个runc的可执行文件,等kubelet编译完成之后会将其替换
2.2 编译 kubelet
- kubernetes 源码
$ mkdir -p /root/k8s/$ cd /root/k8s/$ git clone cd kubernetes/$ git checkout v1.15.3
制作编译环境的镜像(Dockerfile 如下)
FROM centos:centos7.3.1611ENV GOROOT /usr/local/goENV GOPATH /usr/local/gopathENV PATH /usr/local/go/bin:$PATHRUN yum install rpm-build which where rsync gcc gcc-c++ automake autoconf libtool make -y \ && curl -L | tar zxvf - -C /usr/local
在制作好的 go 环境镜像中来进行编译 kubelet
$ docker run -it --rm -v /root/k8s/kubernetes:/usr/local/gopath/src/k8s.io/kubernetes build-k8s:centos-7.3-go-1.12.9-k8s-1.15.3 bash$ cd /usr/local/gopath/src/k8s.io/kubernetes#编译$ GO111MODULE=off KUBE_GIT_TREE_STATE=clean KUBE_GIT_VERSION=v1.15.3 make kubelet GOFLAGS="-tags=nokmem"
替换原有的 runc 和 kubelet
将原有 runc 和 kubelet 备份
$ mv /usr/bin/kubelet /home/kubelet$ mv /usr/bin/docker-runc /home/docker-runc
停止 docker 和 kubelet
$ systemctl stop docker$ systemctl stop kubelet
将编译好的 runc 和 kubelet 进行替换
$ cp kubelet /usr/bin/kubelet$ cp kubelet /usr/local/bin/kubelet$ cp runc /usr/bin/docker-runc
检查 kmem 是否关闭前需要将此节点的 pod 杀掉重启或者重启服务器,当结果为 0 时成功
$ cat /sys/fs/cgroup/memory/kubepods/burstable/memory.kmem.usage_in_bytes
检查是否还存在内存泄露的情况
$ cat /sys/fs/cgroup/memory/kubepods/memory.kmem.slabinfo
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~