您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“怎么排查Kubelet CPU 使用率過高問題”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“怎么排查Kubelet CPU 使用率過高問題”吧!
我們發(fā)現(xiàn)客戶的Kubernetes集群環(huán)境中所有的worker節(jié)點(diǎn)的Kubelet進(jìn)程的CPU使用率長(zhǎng)時(shí)間占用過高,通過pidstat可以看到CPU使用率高達(dá)100%。本文記錄下了本次問題排查的過程。
使用strace工具對(duì)kubelet進(jìn)程進(jìn)行跟蹤
1、由于Kubelet進(jìn)程CPU使用率異常,可以使用strace工具對(duì)kubelet進(jìn)程動(dòng)態(tài)跟蹤進(jìn)程的調(diào)用情況,首先使用strace -cp <PID>命令統(tǒng)計(jì)kubelet進(jìn)程在某段時(shí)間內(nèi)的每個(gè)系統(tǒng)調(diào)用的時(shí)間、調(diào)用和錯(cuò)誤情況。
從上圖可以看到,執(zhí)行系統(tǒng)調(diào)用過程中,futex拋出了五千多個(gè)errors,這并不是一個(gè)正常的數(shù)量,而且該函數(shù)占用的時(shí)間達(dá)到了99%,所以需要進(jìn)一步查看kubelet進(jìn)程相關(guān)的調(diào)用。
2、由于strace -cp命令只能查看進(jìn)程的整體調(diào)用情況,所以我們可以通過strace -tt -p <PID>命令打印每個(gè)系統(tǒng)調(diào)用的時(shí)間戳,如下:
從strace輸出的結(jié)果來看,在執(zhí)行futex相關(guān)的系統(tǒng)調(diào)用時(shí),有大量的Connect timed out,并返回了-1和ETIMEDOUT的error,所以才會(huì)在strace -cp中看到了那么多的error。
futex是一種用戶態(tài)和內(nèi)核態(tài)混合的同步機(jī)制,當(dāng)futex變量告訴進(jìn)程有競(jìng)爭(zhēng)發(fā)生時(shí),會(huì)執(zhí)行系統(tǒng)調(diào)用去完成相應(yīng)的處理,例如wait或者wake up,從官方的文檔了解到,futex有這么幾個(gè)參數(shù):
futex(uint32_t *uaddr, int futex_op, uint32_t val, const struct timespec *timeout, /* or: uint32_t val2 */ uint32_t *uaddr2, uint32_t val3);
官方文檔給出ETIMEDOUT的解釋:
ETIMEDOUT The operation in futex_op employed the timeout specified in timeout, and the timeout expired before the operation completed.
意思就是在指定的timeout時(shí)間中,未能完成相應(yīng)的操作,其中futex_op對(duì)應(yīng)上述輸出結(jié)果的FUTEX_WAIT_PRIVATE和FUTEX_WAIT_PRIVATE,可以看到基本都是發(fā)生在FUTEX_WAIT_PRIVATE時(shí)發(fā)生的超時(shí)。
從目前的系統(tǒng)調(diào)用層面可以判斷,futex無法順利進(jìn)入睡眠狀態(tài),但是futex進(jìn)行了哪些操作還是不清楚,因此仍無法判斷kubeletCPU飆高的原因,所以我們需要進(jìn)一步從kubelet的函數(shù)調(diào)用中去看到底是執(zhí)行卡在了哪個(gè)地方。
FUTEX_PRIVATE_FLAG:這個(gè)參數(shù)告訴內(nèi)核futex是進(jìn)程專用的,不與其他進(jìn)程共享,這里的FUTEX_WAIT_PRIVATE和FUTEX_WAKE_PRIVATE就是其中的兩種FLAG;
futex相關(guān)說明1: https://man7.org/linux/man-pages/man7/futex.7.html fuex相關(guān)說明2: https://man7.org/linux/man-pages/man2/futex.2.html
使用go pprof工具對(duì)kubelet函數(shù)調(diào)用進(jìn)行分析
早期的Kubernetes版本,可以直接通過debug/pprof 接口獲取debug數(shù)據(jù),后面考慮到相關(guān)安全性的問題,取消了這個(gè)接口,具體信息可以參考CVE-2019-11248(https://github.com/kubernetes/kubernetes/issues/81023)。因此我們將通過kubectl開啟proxy進(jìn)行相關(guān)數(shù)據(jù)指標(biāo)的獲取:
1、首先使用kubectl proxy命令啟動(dòng)API server代理
kubectl proxy --address='0.0.0.0' --accept-hosts='^*$'
這里需要注意,如果使用的是Rancher UI上復(fù)制的kubeconfig文件,則需要使用指定了master IP的context,如果是RKE或者其他工具安裝則可以忽略。
2、構(gòu)建Golang環(huán)境。go pprof需要在golang環(huán)境下使用,本地如果沒有安裝golang,則可以通過Docker快速構(gòu)建Golang環(huán)境
docker run -itd --name golang-env --net host golang bash
3、使用go pprof工具導(dǎo)出采集的指標(biāo),這里替換127.0.0.1為apiserver節(jié)點(diǎn)的IP,默認(rèn)端口是8001,如果docker run的環(huán)境跑在apiserver所在的節(jié)點(diǎn)上,可以使用127.0.0.1。另外,還要替換NODENAME為對(duì)應(yīng)的節(jié)點(diǎn)名稱。
docker exec -it golang-env bash go tool pprof -seconds=60 -raw -output=kubelet.pprof http://127.0.0.1:8001/api/v1/nodes/${NODENAME}/proxy/debug/pprof/profile
4、輸出好的pprof文件不方便查看,需要轉(zhuǎn)換成火焰圖,推薦使用FlameGraph工具生成svg圖
git clone https://github.com/brendangregg/FlameGraph.git cd FlameGraph/ ./stackcollapse-go.pl kubelet.pprof > kubelet.out ./flamegraph.pl kubelet.out > kubelet.svg
轉(zhuǎn)換成火焰圖后,就可以在瀏覽器直觀地看到函數(shù)相關(guān)調(diào)用和具體調(diào)用時(shí)間比了。
5、分析火焰圖
從kubelet的火焰圖可以看到,調(diào)用時(shí)間最長(zhǎng)的函數(shù)是*k8s.io/kubernetes/vendor/github.com/google/cadvisor/manager.(containerData).housekeeping,其中cAdvisor是kubelet內(nèi)置的指標(biāo)采集工具,主要是負(fù)責(zé)對(duì)節(jié)點(diǎn)機(jī)器上的資源及容器進(jìn)行實(shí)時(shí)監(jiān)控和性能數(shù)據(jù)采集,包括CPU使用情況、內(nèi)存使用情況、網(wǎng)絡(luò)吞吐量及文件系統(tǒng)使用情況。
深入函數(shù)調(diào)用可以發(fā)現(xiàn)k8s.io/kubernetes/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs.(*Manager).GetStats這個(gè)函數(shù)占用k8s.io/kubernetes/vendor/github.com/google/cadvisor/manager.(*containerData).housekeeping這個(gè)函數(shù)的時(shí)間是最長(zhǎng)的,說明在獲取容器CGroup相關(guān)狀態(tài)時(shí)占用了較多的時(shí)間。
6、既然這個(gè)函數(shù)占用時(shí)間長(zhǎng),那么我們就分析一下這個(gè)函數(shù)具體干了什么。
查看源代碼: https://github.com/kubernetes/kubernetes/blob/ded8a1e2853aef374fc93300fe1b225f38f19d9d/vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs/memory.go#L162
func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error { // Set stats from memory.stat. statsFile, err := os.Open(filepath.Join(path, "memory.stat")) if err != nil { if os.IsNotExist(err) { return nil } return err } defer statsFile.Close() sc := bufio.NewScanner(statsFile) for sc.Scan() { t, v, err := fscommon.GetCgroupParamKeyValue(sc.Text()) if err != nil { return fmt.Errorf("failed to parse memory.stat (%q) - %v", sc.Text(), err) } stats.MemoryStats.Stats[t] = v } stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"] memoryUsage, err := getMemoryData(path, "") if err != nil { return err } stats.MemoryStats.Usage = memoryUsage swapUsage, err := getMemoryData(path, "memsw") if err != nil { return err } stats.MemoryStats.SwapUsage = swapUsage kernelUsage, err := getMemoryData(path, "kmem") if err != nil { return err } stats.MemoryStats.KernelUsage = kernelUsage kernelTCPUsage, err := getMemoryData(path, "kmem.tcp") if err != nil { return err } stats.MemoryStats.KernelTCPUsage = kernelTCPUsage useHierarchy := strings.Join([]string{"memory", "use_hierarchy"}, ".") value, err := fscommon.GetCgroupParamUint(path, useHierarchy) if err != nil { return err } if value == 1 { stats.MemoryStats.UseHierarchy = true } pagesByNUMA, err := getPageUsageByNUMA(path) if err != nil { return err } stats.MemoryStats.PageUsageByNUMA = pagesByNUMA return nil }
從代碼中可以看到,進(jìn)程會(huì)去讀取memory.stat這個(gè)文件,這個(gè)文件存放了cgroup內(nèi)存使用情況。也就是說,在讀取這個(gè)文件花費(fèi)了大量的時(shí)間。這時(shí)候,如果我們手動(dòng)去查看這個(gè)文件,會(huì)是什么效果?
# time cat /sys/fs/cgroup/memory/memory.stat >/dev/null real 0m9.065s user 0m0.000s sys 0m9.064s
從這里可以看出端倪了,讀取這個(gè)文件花費(fèi)了9s,顯然是不正常的。
基于上述結(jié)果,我們?cè)赾Advisor的GitHub上查找到一個(gè)issue(https://github.com/google/cadvisor/issues/1774),從該issue中可以得知,該問題跟slab memory 緩存有一定的關(guān)系。從該issue中得知,受影響的機(jī)器的內(nèi)存會(huì)逐漸被使用,通過/proc/meminfo看到使用的內(nèi)存是slab memory,該內(nèi)存是內(nèi)核緩存的內(nèi)存頁(yè),并且其中絕大部分都是dentry緩存。從這里我們可以判斷出,當(dāng)CGroup中的進(jìn)程生命周期結(jié)束后,由于緩存的原因,還存留在slab memory中,導(dǎo)致其類似僵尸CGroup一樣無法被釋放。
也就是每當(dāng)創(chuàng)建一個(gè)memory CGroup,在內(nèi)核內(nèi)存空間中,就會(huì)為其創(chuàng)建分配一份內(nèi)存空間,該內(nèi)存包含當(dāng)前CGroup相關(guān)的cache(dentry、inode),也就是目錄和文件索引的緩存,該緩存本質(zhì)上是為了提高讀取的效率。但是當(dāng)CGroup中的所有進(jìn)程都退出時(shí),存在內(nèi)核內(nèi)存空間的緩存并沒有清理掉。
內(nèi)核通過伙伴算法進(jìn)行內(nèi)存分配,每當(dāng)有進(jìn)程申請(qǐng)內(nèi)存空間時(shí),會(huì)為其分配至少一個(gè)內(nèi)存頁(yè)面,也就是最少會(huì)分配4k內(nèi)存,每次釋放內(nèi)存,也是按照最少一個(gè)頁(yè)面來進(jìn)行釋放。當(dāng)請(qǐng)求分配的內(nèi)存大小為幾十個(gè)字節(jié)或幾百個(gè)字節(jié)時(shí),4k對(duì)其來說是一個(gè)巨大的內(nèi)存空間,在Linux中,為了解決這個(gè)問題,引入了slab內(nèi)存分配管理機(jī)制,用來處理這種小量的內(nèi)存請(qǐng)求,這就會(huì)導(dǎo)致,當(dāng)CGroup中的所有進(jìn)程都退出時(shí),不會(huì)輕易回收這部分的內(nèi)存,而這部分內(nèi)存中的緩存數(shù)據(jù),還會(huì)被讀取到stats中,從而導(dǎo)致影響讀取的性能。
1、清理節(jié)點(diǎn)緩存,這是一個(gè)臨時(shí)的解決方法,暫時(shí)清空節(jié)點(diǎn)內(nèi)存緩存,能夠緩解kubelet CPU使用率,但是后面緩存上來了,CPU使用率又會(huì)升上來。
echo 2 > /proc/sys/vm/drop_caches
2、升級(jí)內(nèi)核版本
其實(shí)這個(gè)主要還是內(nèi)核的問題,在GitHub上這個(gè)commit(https://github.com/torvalds/linux/commit/205b20cc5a99cdf197c32f4dbee2b09c699477f0)中有提到,在5.2+以上的內(nèi)核版本中,優(yōu)化了CGroup stats相關(guān)的查詢性能,如果想要更好的解決該問題,建議可以參考自己操作系統(tǒng)和環(huán)境,合理的升級(jí)內(nèi)核版本。 另外Redhat在kernel-4.18.0-176(https://bugzilla.redhat.com/show_bug.cgi?id=1795049)版本中也優(yōu)化了相關(guān)CGroup的性能問題,而CentOS 8/RHEL 8默認(rèn)使用的內(nèi)核版本就是4.18,如果目前您使用的操作系統(tǒng)是RHEL7/CentOS7,則可以嘗試逐漸替換新的操作系統(tǒng),使用這個(gè)4.18.0-176版本以上的內(nèi)核,畢竟新版本內(nèi)核總歸是對(duì)容器相關(guān)的體驗(yàn)會(huì)好很多。
kernel相關(guān)commit: https://github.com/torvalds/linux/commit/205b20cc5a99cdf197c32f4dbee2b09c699477f0 redhat kernel bug fix: https://bugzilla.redhat.com/show_bug.cgi?id=1795049
到此,相信大家對(duì)“怎么排查Kubelet CPU 使用率過高問題”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。