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

執行命令后,顯示如下,則說明內存泄漏已修復。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM