K8S内存泄漏问题处理

K8S内存泄漏问题处理

问题描述

我使用kubeadm 安装的K8S集群,随着pod增多,运行的时间久了,就会出现不能创建pod的情况。当kubectl describe pod,发现有 cannot allocate memory的错误信息。只有重启对应的服务器,才可以增加pod,异常提示才会消失。但继续随着时间的推移,pod的增多,该问题会继续出现。

问题分析

根据pod的异常信息,初步判断K8S可能造成了内存泄漏。
使用 cat /sys/fs/cgroup/memory/kubepods/memory.kmem.slabinfo 在出现问题的node查看时,如果显示如下图,则说明没有存在内存泄漏:



如果显示如下图,则说明存在内存泄漏:


具体原因

原因一句话:kmem导致内存泄露:

​ 内核对于每个 cgroup 子系统的的条目数是有限制的,限制的大小定义在 kernel/cgroup.c #L139,当正常在 cgroup 创建一个 group 的目录时,条目数就加1。我们遇到的情况就是因为开启了 kmem accounting 功能,虽然 cgroup 的目录删除了,但是条目没有回收。这样后面就无法创建65535个 cgroup 了。也就是说,在当前内核版本下,开启了 kmem accounting 功能,会导致 memory cgroup 的条目泄漏无法回收。

Kmem在3.X内核的机器上存在内存泄漏

cgroup 的 kmem account 特性在 3.x 内核上有内存泄露问题,如果开启了 kmem account 特性 会导致可分配内存越来越少,直到无法创建新 pod 或节点异常。

几点解释:

  1. kmem account 是cgroup 的一个扩展,全称CONFIG_MEMCG_KMEM,属于机器默认配置,本身没啥问题,只是该特性在 3.10 的内核上存在漏洞有内存泄露问题,4.x的内核修复了这个问题。
  2. 因为 kmem account 是 cgroup 的扩展能力,因此runc、docker、k8s 层面也进行了该功能的支持,即默认都打开了kmem 属性
  3. 因为3.10 的内核已经明确提示 kmem 是实验性质,我们仍然使用该特性,所以这其实不算内核的问题,是 k8s 兼容问题。

k8s在 1.9版本开启了对 kmem 的支持,因此 1.9 以后的所有版本都有该问题,但必须搭配 3.x内核的机器才会出问题。一旦出现会导致新 pod 无法创建,已有 pod不受影响,但pod 漂移到有问题的节点就会失败,直接影响业务稳定性。因为是内存泄露,直接重启机器可以暂时解决,但还会再次出现

了解更多理论原因,可参考 https://blog.kelu.org/tech/2020/09/29/cgroup-kmem.html。

总而言之:K8S 1.9版本及以后的版本,在内核是3.X的服务器上,都会出现内存泄漏的问题。在4.X的内核上,则修复了这个问题


问题处理

处理这个问题,可以升级服务器的内核。不过推荐使用下载kubelet和runc的源码,编译、再替换原来的。

一、配置go语言环境

我的机器系统是centos7.6。配置我参考了 http://docs.studygolang.com/doc/installhttps://www.cnblogs.com/biaopei/p/11883104.htmlhttps://studygolang.com/articles/7202。

1.下载go源码包,版本要>1.16

下载地址:https://golang.google.cn/dl/ 或者 https://studygolang.com/dl 官网 https://golang.org/dl/ 很可能访问通。我下载的是 go1.17.3.linux-amd64.tar.gz。

2.解压源码包

移除之前的源码包(如果有),并解压源码包到/usr/local:

rm -rf /usr/local/go && tar -C /usr/local -xzf go1.17.3.linux-amd64.tar.gz

3.配置golang的系统环境变量(选择一种配置方式即可)

临时配置:下面这个配置,是临时的,服务器重启后,要重新执行。

export PATH=$PATH:/usr/local/go/bin

永久配置方式一:

echo 'export PATH=$PATH:/usr/local/go/bin'>>/etc/profile  #配置系统变量  
source /etc/profile 

永久配置方案二:

vi /etc/profile
在文件中,追加如下环境变量
export GOROOT=/usr/local/go  #设置为go安装的路径
export GOPATH=$HOME/gocode   #默认安装包的路径
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

追加后,再执行如下命令,才能让环境变量生效。
source /etc/profile

环境变量生效后,验证了。

go version

二、编译runc

参考 https://github.com/opencontainers/runchttps://www.cnblogs.com/zhangmingcheng/p/14309962.html

1.源码下载:

下载源码时,通过git下载,要有git工具,yum install git 安装。

 mkdir -p /data/Documents/src/github.com/opencontainers/
 cd /data/Documents/src/github.com/opencontainers/
 git clone https://github.com/opencontainers/runc  (或者 git clone git://github.com/opencontainers/runc)

也可以手动从 https://github.com/opencontainers/runc 下载,再放入/data/Documents/src/github.com/opencontainers/ 中 )

2.安装编译工具

安装编译runc的工具libseccomp。其中,centos安装 libseccomp-devel,ubuntu安装 libseccomp-dev。

yum install libseccomp-devel

安装 gcc编译器:编译runc,还需要gcc编译器。

yum -y install gcc gcc-c++ kernel-devel

3.执行编译

cd runc
make BUILDTAGS='seccomp nokmem'

执行make指令的时候,一定要加上 BUILDTAGS='seccomp nokmem'。这样,重新编译的runc才不会开启kmem属性,也就不会造成内存泄漏

编译完成之后会在当前目录下看到一个runc的可执行文件

三、编译kubelet

1.源码下载:

mkdir -p /root/k8s/
cd /root/k8s/
git clone https://github.com/kubernetes/kubernetes  或者  git clone git://github.com/kubernetes/kubernetes
(这一步,建议从国内的码云下载,github会非常慢: git clone https://gitee.com/mirrors/Kubernetes.git)

版本还原:根据自己安装的K8S版本,将源码还原到对应的版本:

 cd Kubernetes/
 git checkout v1.20.0

2.编译

GO111MODULE=on KUBE_GIT_TREE_STATE=clean KUBE_GIT_VERSION=v1.20.0 make kubelet GOFLAGS="-tags=nokmem"

make编译的时候,必须要加上参数 GOFLAGS="-tags=nokmem"。这样编译的kubelet才不会开启kmem属性,也就不会导致内存泄漏。

生成的kubelet二进制文件在生成的_output路径下的bin当中。

四、替换runc和kubelet

1.备份原有的kubelet和runc

cp /usr/bin/kubelet /home/kubelet
cp /usr/bin/runc /home/runc

2.停止kubelet和docker,然后替换runc和kubelet

systemctl stop docker
systemctl stop kubelet
cp kubelet /usr/bin/kubelet
cp kubelet /usr/local/bin/kubelet
cp runc /usr/bin/runc

3.重启服务器,检查内存泄漏

cat /sys/fs/cgroup/memory/kubepods/memory.kmem.slabinfo

执行命令后,显示如下,则说明内存泄漏已修复。

posted @ 2021-12-13 19:45  FrankFyy  阅读(2423)  评论(0编辑  收藏  举报