天天看點

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,你可以從以下地方追蹤到:

  • ​​https://github.com/moby/moby/issues/5618​​
  • ​​https://github.com/coreos/bugs/issues/254​​

還有老哥放出了重制這個Bug的代碼:​​https://github.com/fho/docker-samba-loop​​​。

而根據我實際的實驗(采坑)下來,這個問題我花費了差不多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 或節點異常。

幾點解釋:

  1. kmem account 是cgroup 的一個擴充,全稱CONFIG_MEMCG_KMEM,屬于機器預設配置,本身沒啥問題,隻是該特性在 3.10 的核心上存在漏洞有記憶體洩露問題,4.x的核心修複了這個問題。
  2. 因為 kmem account 是 cgroup 的擴充能力,是以runc、docker、k8s 層面也進行了該功能的支援,即預設都打開了kmem 屬性
  3. 因為3.10 的核心已經明确提示 kmem 是實驗性質,我們仍然使用該特性,是以這其實不算核心的問題,是 k8s 相容問題。

其他細節原因下面會解釋

解決方案

既然是 3.x 的問題,直接更新核心到 4.x 及以上即可,核心問題解釋:

  • ​​https://github.com/torvalds/linux/commit/d6e0b7fa11862433773d986b5f995ffdf47ce672​​
  • ​​https://support.mesosphere.com/s/article/Critical-Issue-KMEM-MSPH-2018-0006​​

這種方式的缺點是:

  1. 需要更新所有節點,節點重新開機的話已有 pod 肯定要漂移,如果節點規模很大,這個更新操作會很繁瑣,業務部門也會有意見,要事先溝通。
  2. 這個問題歸根結底是軟體相容問題,3.x 自己都說了不成熟,不建議你使用該特性,k8s、docker卻 還要開啟這個屬性,那就不是核心的責任,因為我們是雲上機器,想替換4.x 核心需要虛機團隊做足夠的測試和評審,是以這是個長期方案,不能立刻解決問題。
  3. 已有業務在 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 -r
3.10.0-693.el7.x86_64


7.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
# 重建grub
grub2-mkconfig -o /boot/grub2/grub.cfg
# 檢查grub啟動參數
 grep "^menuentry" /boot/grub2/grub.cfg
menuentry '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.cfg
0 : 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

​​https://github.com/containerd/containerd/blob/master/README.md​​

Kubernetes CentOS7.4 系統核心更新 修複 K8S 記憶體洩露問題

Kernel 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. ​​https://github.com/kubernetes/kubernetes/blob/master/cmd/kubeadm/app/util/system/types_unix.go​​ 

Kernel 3.10隻是最低要求,你可以看到Kubernetes也支援4+甚至5+。核心 3.10 上最新的 CentOS7.6 版本。沒有針對核心 4 的任何計劃。雖然 Kubernetes 預設啟用的一些核心功能,例如“核心記憶體記帳 (kmem)”,僅在核心 3 中處于試驗階段,但在核心 4 上是穩定的。

Docker 提供核心版本檢查并在以下情況下禁用 kmem核心是 3.x。但是我找不到從 Kubernetes 禁用 kmem 的任何标志。修複它的一種可能方法是使用建構标志從源代碼重建 Kubernetes,以禁用來自 runc 的 kmem 記帳。

Kubernetes CentOS7.4 系統核心更新 修複 K8S 記憶體洩露問題

Other situations

  1. 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,​​https://github.com/kubernetes/kubernetes/issues/61937​​.
  2. The 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​​​. See​​changlog​​​. You can also find the fix by linus torvalds in​​net: tcp: close sock if net namespace is exiting​​​. But no CentOS fix was ever found so far. I have rebuilt​​an enhanced CentOS on kernel 3.10.0.957.10.1​​.
  3. Kernel 3.10 is only tech preview on overlayfs. kernel: [ 24.062493] TECH PREVIEW: Overlay filesystem may not be fully supported.
  4. 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.
  5. 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.
  6. 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, see​​Kubernetes: Using containerd 1.1 without Docker - aboutsimon.com​​
  7. Kubernetes 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.
  8. 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.
  9. CoreOS adopted kernel 4 very early. In 2015
  10. RHEL 8 defaults on kernel 4.x, but it is beta. No timeline for GA yet.​​Red Hat Enterprise Linux Overview | Red Hat Developer​​
  11. CentOS officially doesn’t support kernel 4.x by for now.​​https://en.wikipedia.org/wiki/CentOS​​.

其他情況

  1. 3.x 中的核心記憶體洩漏錯誤已經為我們發生了好幾個星期:SLUB:無法在節點 -1 上配置設定記憶體,導緻 Docker 節點崩潰。核心記憶體記帳僅在核心 3 上是實驗性的,但在核心 4 上穩定。現在 CentOS 沒有修複。請參閱問題,​​https://github.com/kubernetes/kubernetes/issues/61937​​。
  2. 我遇到過的 3.x 核心 socket 洩漏:unregister_netdevice: waiting for eth0 to become free。使用次數 = 1。在 Ubuntu 核心 4.4.114 中已修複。​​net: tcp: close sock if net namespace is exiting​​​。見​​changlog​​​。您還可以在​​net: tcp: close sock if net namespace is exiting 中​​​找到 linus torvalds 的修複。但是到目前為止還沒有找到 CentOS 修複程式。我​​在核心 3.10.0.957.10.1 上​​​重建​​了一個增強的 CentOS​​。
  3. Kernel 3.10 隻是overlayfs 上的技術預覽。核心:[24.062493] 技術預覽:可能不完全支援覆寫檔案系統。
  4. 從上面的需求例子可以看出,現在看來容器生态主要是在核心4.x上發展的。未來也應該如此。供應商不會更願意為核心 3 提供修複,以核心套接字洩漏 bug 為例,它在 CentOS 上已經存在好幾年了。
  5. 即使我們修複了那 2 個核心錯誤,我們如何確定未來未知的潛在錯誤能夠及時得到官方修複?但是一旦發現這樣的嚴重錯誤,核心 4.x 可以及時修補,因為整個生态系統主要在它上面玩。
  6. Docker 有核心記憶體洩漏的修複,而 k8s 沒有,似乎它主要考慮和開發核心 4.x。Kubernetes 也可以在沒有 Docker 的情況下工作,它有自己的 crictl 工具,可以替代 docker 用戶端。此外,它還直接包含 containerd/runc 作為其供應商代碼。現在可以在設定 Kubernetes 叢集時完全丢棄 Docker,參見​​Kubernetes: Using containerd 1.1 without Docker - aboutsimon.com​​
  7. Kubernetes 隻修補最後 3 個次要版本。次要版本大約每 3 個月發生一次。是以,我們将在 9 個月後轉向下一個次要版本,因為 Kubernetes 将越來越依賴核心 4.x 功能。當 k8s 版本上升時,我們将在核心 3.x 上處于高風險中。如果我們不跟上進度,我們就無法從 Kubernetes 獲得最新的安全更新檔。
  8. Docker 現在隻修補 17.06+。最新版本是 1809.3。Redhat 仍然使用 Docker 1.13.1,因為他們的核心太舊而無法運作更新的 Docker。1.13.1 比 17.03 更舊,不再處于維護範圍。
  9. CoreOS 很早就采用了核心 4。2015年
  10. RHEL 8 預設為核心 4.x,但它是測試版。GA 還沒有時間表。​​Red Hat Enterprise Linux Overview | Red Hat Developer​​
  11. CentOS 目前正式不支援核心 4.x。​​https://en.wikipedia.org/wiki/CentOS​​。
  12. Kubernetes CentOS7.4 系統核心更新 修複 K8S 記憶體洩露問題

 ​​The Analysis of Kernel & OS for running Kubernetes - Gary's Understandings​​

問題一:修複 K8S 記憶體洩露問題

問題描述

  1. 當 k8s 叢集運作日久以後,有的 node 無法再建立 pod,并且出現如下錯誤,當重新開機伺服器之後,才可以恢複正常使用。檢視 pod 狀态的時候會出現以下報錯。
applying cgroup … caused: mkdir …no space left on device      

或者在 describe pod 的時候出現 cannot allocate memory。

這時候你的 k8s 叢集可能就存在記憶體洩露的問題了,當建立的 pod 越多的時候記憶體會洩露的越多,越快。

  1. 具體檢視是否存在記憶體洩露
$ 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            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>      

解決方案

  1. 解決方法思路:關閉 runc 和 kubelet 的 kmem,因為更新核心的方案改動較大,此處不采用。
  2. kmem 導緻記憶體洩露的原因:
  • 配置 go 語言環境
$ wget https://dl.google.com/go/go1.12.9.linux-amd64.tar.gz
$ 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 https://github.com/opencontainers/runc
$ cd runc/
$ git checkout v1.0.0-rc9  # 切到v1.0.0-rc9 tag      
  • 編譯
# 安裝編譯元件
$ sudo yum install libseccomp-devel
$ make BUILDTAGS='seccomp nokmem'
# 編譯完成之後會在目前目錄下看到一個runc的可執行檔案,等kubelet編譯完成之後會将其替換      
  • 下載下傳 kubernetes 源碼
$ mkdir -p /root/k8s/
$ cd /root/k8s/
$ git clone https://github.com/kubernetes/kubernetes
$ cd kubernetes/
$ git checkout v1.15.3      
  • 制作編譯環境的鏡像(Dockerfile 如下)
FROM centos:centos7.3.1611

ENV GOROOT /usr/local/go
ENV GOPATH /usr/local/gopath
ENV PATH /usr/local/go/bin:$PATH

RUN yum install rpm-build which where rsync gcc gcc-c++ automake autoconf libtool make -y \
    && curl -L https://studygolang.com/dl/golang/go1.12.9.linux-amd64.tar.gz | 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"      
  1. 替換原有的 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      

繼續閱讀