溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么排查Kubelet CPU 使用率過高問題

發(fā)布時(shí)間:2021-06-23 09:44:57 來源:億速云 閱讀:283 作者:chen 欄目:云計(jì)算

本篇內(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%。本文記錄下了本次問題排查的過程。

怎么排查Kubelet CPU 使用率過高問題

集群環(huán)境

怎么排查Kubelet CPU 使用率過高問題

排查過程

使用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ò)誤情況。

怎么排查Kubelet CPU 使用率過高問題

從上圖可以看到,執(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í)間戳,如下:

怎么排查Kubelet CPU 使用率過高問題

從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_PRIVATEFUTEX_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 CPU 使用率過高問題

從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í)!

向AI問一下細(xì)節(jié)

免責(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)容。

AI