手把手教你如何正確獲取容器的CPU利用率?
今天我們來深入理解關(guān)于容器 cpu 利用率相關(guān)的兩個(gè)問題。
第一個(gè)問題:如何正確地獲取容器中的 cpu 利用率?
在上一篇《Linux 中的各項(xiàng) CPU 利用率是這樣算出來的!》中我們討論了 Linux 是如何計(jì)算系統(tǒng)的 cpu 利用率。在物理機(jī)上,使用這種方法查看 cpu 的使用情況是沒有問題的。
但是在容器中,默認(rèn)情況下 /proc/stat 是使用的宿主機(jī)的偽文件,也就是說查看到的是物理機(jī)的 cpu 使用情況,而不是容器本身的。那么我們該如何才能正確地獲取到容器本身的 cpu 使用率呢?
第二個(gè)問題:容器 cpu 使用率的指標(biāo)項(xiàng)為什么比物理機(jī)上少了 nice/irq/softirq?
不少公司基于部署的 kubelet 解決了獲取的問題,將容器的總 cpu 用量、user 用戶態(tài)、system 系統(tǒng)態(tài)都正確地上報(bào)了。
但如果你仔細(xì)觀察過就會發(fā)現(xiàn),是在物理機(jī)的 top 輸出中有 user/nice/system/irq/softirq 等很多指標(biāo)項(xiàng),而容器中卻只有 user 和 system。其它的指標(biāo)項(xiàng)去哪兒了?
實(shí)際上,容器中統(tǒng)計(jì)到 cpu user、system 指標(biāo)項(xiàng)的含義和物理機(jī)中 top 輸出的同名指標(biāo)項(xiàng)的含義是完全不同的。
好,我們現(xiàn)在對以上兩個(gè)問題進(jìn)行深入的剖析!
一、獲取容器 cpu 利用率的思路
絕大部分同學(xué)都習(xí)慣了使用 top 來查看 cpu 利用率。到了容器里,大家也都是習(xí)慣打開 top 看看。但經(jīng)常會遇到明明是 2 核的容器,top 顯示出來的核卻有很多個(gè)。
這是因?yàn)樵谀J(rèn)情況下,容器中的 /proc/stat 并沒有單獨(dú)掛載,而是使用的宿主機(jī)的。而 top 命令中對 cpu 核數(shù)的判斷,以及對 cpu 利用率的顯示都是根據(jù) /proc/stat 文件的輸出來計(jì)算的。所以自然容器下想看 cpu 利用率就不能使用這種方法了。
那么容器下就沒有辦法正確獲取 cpu 的使用情況了嗎?肯定不是的,辦法總比困難多。
第一個(gè)辦法是對容器中的 /proc/ 目錄下的一些文件進(jìn)行掛載,包括 /proc/stat。在容器中不再使用和宿主機(jī)相同的文件。lxcfs 就是基于這種思想做出來的項(xiàng)目。業(yè)界有不少公司都使用它修改了容器里的 stat 偽文件。該項(xiàng)目的 github 地址:https://github.com/lxc/lxcfs
安裝了 lxcfs 后,開發(fā)們又可以愉快地像在物理上一樣地使用 top 來玩耍了。
第二個(gè)方法就是直接找到容器所屬的 cgroup 目錄,在這里也有當(dāng)前 cgroup 所消耗的 cpu 資源的統(tǒng)計(jì)信息。根據(jù)這個(gè)信息可以計(jì)算出容器的 cpu 利用率。
kubelet 中集成的 cadvisor 就是采用上述方案來上報(bào)容器 cpu 利用率的打點(diǎn)信息的。每隔一段定長的時(shí)間都進(jìn)行采樣,將數(shù)據(jù)上傳給 Prometheus。這樣,我們就能在 Prometheus 上看到容器的 cpu 利用信息了。不過 cadvisor 具體訪問 cgroup 目錄是通過調(diào)用 libcontainer 獲取的。github 地址:https://github.com/opencontainers/runc/tree/main/libcontainer
如果我們自己在業(yè)務(wù)中出于某些需求需要獲取容器的 cpu 利用率的話,我建議的是采用后面的這個(gè)方法。不過要注意的是,cgroup 有 V1 和 V2 兩個(gè)版本。使用 libcontainer 的話它替我們做了這個(gè)處理。如果我們自己計(jì)算的話,需要首先判斷當(dāng)前在用的 cgroup 是 V1 還是 V2。
libcontainer 中是通過 cgroup 掛載的文件系統(tǒng)的 magic number 來判斷的。
其中 cgroup2 的 magic number 是固定的。
判斷出來后,需要針對 cgroup1 和 cgroup2 來區(qū)分開始處理。判斷完后,下一步是找到容器的 cgroup 的路徑。通過檢查 /proc/{pid}/cgroup 可以查看到進(jìn)程所在的 cgroup。
在 cgroup V1 中,是通過 cpuacct 子系統(tǒng)來統(tǒng)計(jì) cpu 利用率的,cpu 相關(guān)的內(nèi)核偽文件有如幾個(gè)。路徑都在 /sys/fs/cgroup/cpuacct 目錄下。找到一個(gè)并打開后,我們可以看到如下幾個(gè) cpu 相關(guān)的文件。
其中每個(gè)文件的作用是
cpuacct.stat:顯示當(dāng)前 cgroup cpu 采樣統(tǒng)計(jì)信息
cpuacct.usage:顯示當(dāng)前 cgroup 中所有進(jìn)程的總 cpu 時(shí)間用量,另外 cpuacct.usage_sys 輸出的是系統(tǒng)態(tài)時(shí)間,cpuacct.usage_user 輸出的是用戶態(tài)時(shí)間。
我們找一個(gè) cpuacct.stat 來看
其中輸出的數(shù)據(jù)的單位是納秒。加入我們再時(shí)間 t1 獲得該 cgroup 使用的納秒數(shù)位 a1, t2 獲得使用的納秒數(shù)位 a2。則該容器使用的 cpu 時(shí)間就等于 (a2-a1)/(t2-t1)。
這個(gè)輸出的是用量,想換算成使用率還需要除以當(dāng)前的核數(shù)。假如用量算出來是 0.5 核,對于 1 核的容器來說,當(dāng)前容器的 cpu 使用率就是 50%。
在 cgroup V2 中,輸出稍有不同。V2 是在 cpu.stat 中輸出的,一個(gè)示例如下。
其中 usage_usec 代表容器自統(tǒng)計(jì)以后所使用的 cpu 時(shí)間,user_usec 是使用的用戶態(tài)時(shí)間,system_usec 為內(nèi)核態(tài)時(shí)間。它們的單位都為微秒。
計(jì)算的方式同樣是在 t1 t2 兩個(gè)點(diǎn)采樣,然后用類似的公司計(jì)算而得出。
講到這里,我們已經(jīng)把容器 cpu 使用率的統(tǒng)計(jì)方法介紹清楚了。不過不知道你有沒有發(fā)現(xiàn)一個(gè)問題,我們在物理機(jī)的 top 命令中,輸出的 cpu 利用率相關(guān)的項(xiàng)目有用戶態(tài)使用率(包括 user、nice)系統(tǒng)態(tài)利用率(system、irq、softirq)。
但不管采用上面哪一個(gè)辦法,我們似乎只能把容器中 user 或者 system 的 cpu 耗時(shí)計(jì)算出來。而在宿主機(jī)中看到的 nice、irq、softirq 卻變得無影無蹤了。這一點(diǎn)似乎和物理機(jī)上是不一致的。
不過別著急,我們馬上展開對容器 cpu 利用率內(nèi)部處理過程的發(fā)掘,我們將能找到這個(gè)問題的答案。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)??


二、cgroup V2 cpu 利用率統(tǒng)計(jì)實(shí)現(xiàn)
cgroup 有 V1 和 V2 兩個(gè)版本。他們都通過偽文件向用戶態(tài)提供了 cgroup 使用的 cpu 時(shí)間的信息。正是有了這個(gè)基礎(chǔ),我們才能夠在上一節(jié)中使用這個(gè)數(shù)據(jù)在兩個(gè)時(shí)間點(diǎn)之間采樣后計(jì)算 cpu 利用率。
本節(jié)我們就來看看,cgroup 提供的 cpu 使用時(shí)間信息是怎么來的。為了簡便,本文只以 cgroup V2 為例。
2.1 cgroup cpu 時(shí)間統(tǒng)計(jì)相關(guān)定義
cgroup 在內(nèi)核中的定義就叫 struct cgroup。

cgroup 的 rstat_cpu 是一個(gè) percpu 變量,對每個(gè)邏輯核都會分配一個(gè)數(shù)組元素。cgroup 在每個(gè)核上的使用時(shí)間信息會先在這里存儲。bstat 是整個(gè) cgroup 的全局 cpu 使用統(tǒng)計(jì)。
我們來看下具體的定義:
其中 task_cputime 中包含了系統(tǒng)態(tài)時(shí)間 stime、用戶態(tài)時(shí)間 utime 以及總的時(shí)間 sum_exec_runtime。
2.2 查看 cpu.stat 過程
在 cgroup V2 中,是通過 cpu.stat 偽文件來查看到容器內(nèi)所有進(jìn)程使用的用戶態(tài)、系統(tǒng)態(tài)以及匯總時(shí)間的微秒數(shù)的。
其實(shí)這個(gè)數(shù)據(jù)是來自于內(nèi)核中 cgroup 對象的 bstat 成員。在這個(gè)成員中存儲了當(dāng)前 cgroup 內(nèi)進(jìn)程所使用的用戶態(tài)時(shí)間、系統(tǒng)態(tài)時(shí)間以及總時(shí)間。對 cpu.stat 偽文件的訪問就是讀取的這個(gè)數(shù)據(jù)并打印輸出的。大致流程圖如下:

打開 cpu.stat 會觸發(fā)內(nèi)核對應(yīng)的處理函數(shù) cpu_stat_show。在這個(gè)函數(shù)中會將當(dāng)前 cgroup 中對每個(gè) cpu 記錄使用率信息匯總起來。最后轉(zhuǎn)化成微秒輸出。
我們來看下詳細(xì)工作過程。在 cgroup.c 中定義了對于 cgroup 的各個(gè)偽文件的處理函數(shù)。
對于 cpu.stat 來說,它對應(yīng)的處理函數(shù)就是 cpu_stat_show。cpu_stat_show 調(diào)用 cgroup_base_stat_cputime_show 訪問內(nèi)核變量并打印輸出。
在這個(gè)函數(shù)中做了這么幾件事情。
第一件,是先查找到當(dāng)前偽文件對應(yīng)的 cgroup 內(nèi)核對象。
第二件,將 percpu 變量 中保存的 cpu 時(shí)間統(tǒng)計(jì)匯總到全局 bstat.cputime 中
第三件,訪問該對象的 bstat.cputime 中存儲的 utime、stime、sum_exec_runtime。
第四件,調(diào)用 do_div 將內(nèi)存中的納秒值轉(zhuǎn)化成微秒,并打印輸出
我們重點(diǎn)來展開看下第二件事,percpu 變量的統(tǒng)計(jì)匯總。cgroup_rstat_flush_hold 調(diào)用了 cgroup_rstat_flush_locked 來進(jìn)行 percpu 中 cpu 統(tǒng)計(jì)信息進(jìn)行匯總。
在上述函數(shù)中遍歷每個(gè) cpu,然后調(diào)用 cgroup_base_stat_flush 來將該 cpu 上的記錄的 cpu 統(tǒng)計(jì)信息匯總到 cgroup 的全局統(tǒng)計(jì) bstat 中。
具體的統(tǒng)計(jì)思路是記錄一個(gè)上一次統(tǒng)計(jì)信息,本次將當(dāng)前時(shí)間和上一次相減得出最近使用的 cpu 增量,然后把這些增量都調(diào)用 cgroup_base_stat_accumulate 累加到 cgroup 的 bstat 中。
接下來就是訪問全局 bstat.cputime 中 utime、stime,并可能會進(jìn)行一些調(diào)整,然后打印輸出。
在接下來的一個(gè)小節(jié)中我們再看下,percpu 變量 rstat 中的數(shù)據(jù)是怎么加進(jìn)來的。
2.3 累加到 percpu 統(tǒng)計(jì)信息中
在《Linux 中的各項(xiàng) CPU 利用率是這樣算出來的!》 中我們介紹到了,系統(tǒng)會定期在每個(gè) cpu 核上發(fā)起 timer interrupt。每次當(dāng)時(shí)間中斷到來的時(shí)候,采樣 cpu 使用情況并匯總起來,提供給 /proc/stat 訪問用。對于系統(tǒng)中每一個(gè) cgroup 來說,也是在這個(gè)時(shí)機(jī)里統(tǒng)計(jì)并匯總起來的。

定時(shí)器定時(shí)將 cgroup 在每個(gè) cpu 上的使用信息都記錄到 percpu 變量 rstat_cpu。我們來看看詳細(xì)的統(tǒng)計(jì)過程。
每次執(zhí)行時(shí)鐘中斷處理程序 都會調(diào)用 都會調(diào)用 update_process_times 來進(jìn)行時(shí)鐘中斷的處理。在時(shí)鐘中斷中處理的事情包括 cpu 利用率的統(tǒng)計(jì),以及周期性的進(jìn)程調(diào)度等。其中和容器 cpu 利用率相關(guān)的調(diào)用棧比較深,如下圖所示。
update_process_times 調(diào)用 account_process_tick 來處理 cpu 處理時(shí)間的累積。在 account_process_tick 中根據(jù)當(dāng)前的狀態(tài)判斷是該累計(jì)用戶態(tài)時(shí)間,還是內(nèi)核態(tài)時(shí)間。
無論是累計(jì)用戶態(tài)時(shí)間處理函數(shù) account_user_time 還是累積內(nèi)核態(tài)時(shí)間 account_system_time,最后都用調(diào)用到 __cgroup_account_cputime_field 將當(dāng)前節(jié)拍的時(shí)間添加到 cgroup 內(nèi)核對象的 。
在這個(gè)函數(shù)中,將本次節(jié)拍的時(shí)間 delta_exec 添加到了 cgroup 中為統(tǒng)計(jì) cpu 使用率的 percpu 變量 rstat_cpu 中了(cgroup_base_stat_cputime_account_begin 函數(shù)訪問的是 cgroup 下的 rstat_cpu)。
這里我們還看到了,對于 user 和 nice,cgroup 都統(tǒng)一添加到了 utime(用戶態(tài)時(shí)間)下,對于 system、irq 和 softirq 都統(tǒng)計(jì)到了 stime (系統(tǒng)態(tài)時(shí)間)下。
這就是前面我們提到的問題的答案,為什么容器 cpu 利用率只能體現(xiàn) user 和 system,而在宿主機(jī)中可以看到的 nice、irq、softirq 卻看不到了。
這是因?yàn)槿萜鲗⑺杏脩魬B(tài)時(shí)間都記錄到了一起,系統(tǒng)態(tài)時(shí)間都記錄到了一起。而不像在宿主機(jī)中分的那么細(xì)。在容器中的 cpu 的 user 指標(biāo)和宿主機(jī)中 top 命令輸出的 user 指標(biāo)含義是完全不一樣的,system 也是。這個(gè)點(diǎn)值得大家注意。在容器中:
用戶態(tài)時(shí)間:和宿主機(jī)中的 user + nice 相對應(yīng)
系統(tǒng)態(tài)時(shí)間:和宿主機(jī)中的 system + irq + softirq 相對應(yīng)。
三、總結(jié)
這篇文章中,我們討論兩個(gè)核容器 cpu 使用率相關(guān)的問題。
第一個(gè)問題:如何正確地獲取容器中的 cpu 利用率?
這個(gè)問題有兩個(gè)解決思路。
思路之一是使用 lxcfs,將容器中的 /proc/stat 替換掉。這樣 top 等命令就不再顯示的是宿主機(jī)的 cpu 利用率了,而是容器的。思路之二是直接使用 cgroup 提供的偽文件來進(jìn)行統(tǒng)計(jì),這些偽文件一般位于 /sys/fs/cgroup/... 路徑。kubelet 中集成的 cadvisor 就是采用上述方案來上報(bào)容器 cpu 利用率的打點(diǎn)信息的。
第二個(gè)問題:容器 cpu 使用率的指標(biāo)項(xiàng)為什么比物理機(jī)上少了 nice/irq/softirq?
這個(gè)問題的根本原因是容器 cpu 利用率的指標(biāo)項(xiàng) user、system 和宿主機(jī)的同名指標(biāo)項(xiàng)根本就不是一個(gè)東西。
容器將所有用戶態(tài)時(shí)間都記錄到了 user 指標(biāo)項(xiàng),系統(tǒng)態(tài)時(shí)間都記錄到了 system。
容器中的 user 指標(biāo):在指標(biāo)含義上等同于宿主機(jī)的 user + nice容器中的 system 指標(biāo):在指標(biāo)含義上等同于宿主機(jī)的 system + irq + softirq
原文作者:開發(fā)內(nèi)功修煉

手把手教你如何正確獲取容器的CPU利用率?的評論 (共 條)
