内核功能导致重启_诊断修复 TiDB Operator 在 K8s 测试中遇到的 Linux 内核问题
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:
https://github.com/kubernetes/kubernetes/issues/61937
https://github.com/opencontainers/runc/issues/1725
https://support.mesosphere.com/s/article/Critical-Issue-KMEM-MSPH-2018-0006
問題起源
薛定諤平臺是我司開發的基于 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//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:
https://github.com/kubernetes/kubernetes/issues/64743
https://github.com/projectcalico/calico/issues/1109
https://github.com/moby/moby/issues/5618
問題起源
我們的薛定諤分布式測試集群運行一段時間后,經常會持續出現“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_UNREGISTER?和?NETDEV_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 升級到新版后,不必再擔心這個問題。
PingCAP EE 團隊致力于提高公司內部的自動化程度和團隊協作效率,小伙伴們可通過?這篇文章?了解他們,如果你對這個團隊正在“折騰”方向感興趣的話,歡迎和我們聊一聊~hire@pingcap.com?
總結
以上是生活随笔為你收集整理的内核功能导致重启_诊断修复 TiDB Operator 在 K8s 测试中遇到的 Linux 内核问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python必须连网开发吗_Python
- 下一篇: python字符串无效的原因_pytho