背景
當一台機器上跑有多個 Docker Container 的時候,我們需要知道,哪些容器占用了多少資源。采集這些指標,來讓我們可以更加好的分配資源給每個 Container。
獲取容器CPU使用率
正好,這兩天碰到一個需求。需要我們在監控 Host 的 CPU 時候,同時將每個容器的 CPU使用率 也要拿到。
平常我們來看容器的資源占用,通過 stat
命令就可以

這里的字段 CPU
表示了CPU的使用率。
但是這樣的命令結果我們要拿到並做處理的時候並不方便。不過,據說 Docker Daemon 還有 API 接口,可以用來直接獲取JSON格式的數據。
來來來,接口搞起來……
當我拿到數據的時候,我是拒絕的:
"cpu_stats" : {
"cpu_usage" : {
"percpu_usage" : [
8646879,
24472255,
36438778,
30657443
],
"usage_in_usermode" : 50000000,
"total_usage" : 100215355,
"usage_in_kernelmode" : 30000000
},
"system_cpu_usage" : 739306590000000,
"throttling_data" : {"periods":0,"throttled_periods":0,"throttled_time":0}
}
這都是些什么鬼……
其實,要理解上面的數據,要現有一些前序的知識
Linux 如何計算CPU使用率
在 Linux 中,CPU 可不是像一個有刻度的尺子一樣,把百分比刻在上面,CPU使用了多少,就把刻度記錄一下。
實際上,CPU使用率的計算是通過時間比率計算的。簡單來說,CPU只會有兩種狀態: 忙(被占用)、閑(空閑)。當我們把1s的時間分成1000份,也就是以毫秒為單位來看待CPU的時候。
假設下面兩種情況:
- 假設有一個進程占用了 CPU 250ms;那么整個CPU在1s的維度上來看,他的使用率是25%。
- 假設有兩個進程一起使用CPU,每個都是用了 500ms;那么整個CPU使用率是 100%,每個進程占用了 50%
實際上,Linux對於CPU的使用分配和記錄遠比這個要復雜。比如,還要記錄是系統調用的CPU占用呢 還是用戶的CPU占用呢,還是等待IO的占用呢,等等。CPU時間的分配工作也是非常復雜,Linux中采用了完全公平調度器(Completely Fair Scheduler)來分配CPU。
繼續深入就有點跑題了。這些東西會在今年后續的文章中陸續談到。
那么,實際上, Linux Kernel 將 CPU 使用的信息記錄在哪了呢?這個信息記錄在了 /proc/stat
這個文件中。
它看起來就像下面這樣:
# intr 那個字段太長,截斷掉了
[root@sean ~]# cat /proc/stat
cpu 1344357 729 298522 107841813 41774 0 1754 0 0 0
cpu0 1344357 729 298522 107841813 41774 0 1754 0 0 0
intr 206534153 29 10 0 0 154 .......
ctxt 317949799
btime 1524823711
processes 677986
procs_running 3
procs_blocked 0
softirq 66031632 2 35326999 1 2928073 535817 0 32 0 0 27240708
我們關心的是第一行的意義。通過查看 man 5 proc
,可以確定它的具體意義
user (1) 用戶模式花費的CPU時間
nice (2) 低優先級用戶態花費的CPU時間
system (3) 系統模式花費的CPU時間
idle (4) 空閑的CPU時間
iowait (since Linux 2.5.41)
(5) 等待IO的CPU時間
irq (since Linux 2.6.0-test4)
(6) 中斷花費的CPU時間
softirq (since Linux 2.6.0-test4)
(7) 軟中斷花費的時間
steal (since Linux 2.6.11)
(8) 被虛擬環境其他操作系統占用的CPU時間
guest (since Linux 2.6.24)
(9) 在Linux內核的控制下為客戶操作系統運行一個虛擬CPU花費的時間。
guest_nice (since Linux 2.6.33)
(10) 同上,低優先級的。
這些加起來,就是花費的CPU總體時間。
Container 如何記錄CPU使用
這個文件中,記錄了所有的進程花費的總的開銷,並不能標識出某一個 Container 總共花費了多少。那如何找出 Container 的開銷呢?
這個就要看 CGroup 的了。
Linux下的Container技術依賴於 CGroup(Control Group),它不僅僅追蹤進程組,還會暴露 CPU、Memory、IO 使用率的指標出來。CGroup 通過偽文件系統的方式將它們記錄下來。在最近幾個版本的發行版中,一般會放在 /sys/fs/cgroup
中。
在這個文件夾下,可以看到多個子文件夾,代表了 cgroup 記錄的各個分類
[root@test cgroup]# pwd
/sys/fs/cgroup
[root@test cgroup]# ll
total 0
dr-xr-xr-x 3 root root 0 May 10 12:59 blkio
lrwxrwxrwx 1 root root 11 Jan 29 12:07 cpu -> cpu,cpuacct
lrwxrwxrwx 1 root root 11 Jan 29 12:07 cpuacct -> cpu,cpuacct
dr-xr-xr-x 3 root root 0 May 10 12:59 cpu,cpuacct
dr-xr-xr-x 3 root root 0 May 10 12:59 cpuset
dr-xr-xr-x 3 root root 0 May 10 12:59 devices
dr-xr-xr-x 3 root root 0 May 10 12:59 freezer
dr-xr-xr-x 3 root root 0 May 10 12:59 hugetlb
dr-xr-xr-x 3 root root 0 May 10 12:59 memory
dr-xr-xr-x 3 root root 0 May 10 12:59 net_cls
dr-xr-xr-x 3 root root 0 May 10 12:59 perf_event
dr-xr-xr-x 3 root root 0 May 10 12:59 pids
dr-xr-xr-x 5 root root 0 May 10 12:59 systemd
對於 Docker 來說,在每一個分類下,都會有一個 docker
的文件夾,來記錄docker相關的信息。而docker container 的信息,則記錄在 更下一層的目錄中,以 container 的 全ID 為目錄名。以記錄container CPU相關的信息為例,目錄應該在 /sys/fs/cgroup/cpu/docker/<container-full-id>
。
對於 CPU 來說,Linux 使用了 The CPU Accounting (cpuacct) 子系統。它會定期自動匯報有關 CGroup中 任務使用 CPU 資源的使用情況。
cpuacct 有三種匯報記錄:
cpuacct.stat
Group中 CPU 總共的使用時間(納秒為單位),包含所有類型的CPU消耗cpuacct.usage
Group中 用戶模式 和 系統模式 各自使用的 CPU時間。這個匯報的單位是 1/100 秒,在 Linux 中也叫"user jiffies"。文件中有兩個字段- user 用戶使用的 CPU時間
- system 系統使用的 CPU 時間
cpuacct.usage_percpu
每個 CPU 的使用時間
total 0
-rw-r--r-- 1 root root 0 May 9 18:22 cgroup.procs
-r--r--r-- 1 root root 0 May 10 13:07 cpuacct.stat
-rw-r--r-- 1 root root 0 May 10 13:07 cpuacct.usage
這就是,到現在為止,我們需要的所有前置知識啦。下面我們回到主題。
/container/<cid>/stats API 的CPU數據如何來的?
這些數據就是我們上面兩個小節所講的地方拿來的。
"cpu_stats" : {
"cpu_usage" : {
"percpu_usage" : [
8646879,
24472255,
36438778,
30657443
],
"usage_in_usermode" : 50000000,
"total_usage" : 100215355,
"usage_in_kernelmode" : 30000000
},
"system_cpu_usage" : 739306590000000,
"throttling_data" : {"periods":0,"throttled_periods":0,"throttled_time":0}
}
cpu_usage 中的所有的值 是從 cpuacct.usage
、cpuacct.usage_percpu
、cpuacct.stat
獲取。
system_cpu_usage 從 /proc/stat 中獲取。
⚠️ 這里需要注意的是,這三個文件中,cpuacct.stat 使用的單位和 其他兩個是不一樣的,在獲取數據的代碼中,要將以 USER_HZ 單位的值轉為 以納秒為單位的值。
來看看代碼
有了數據,怎么計算 Container CPU 使用率
我把它貼在下面,分析一下:
func calculateCPUPercentUnix(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
var (
cpuPercent = 0.0
// calculate the change for the cpu usage of the container in between readings
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage) - float64(previousCPU)
// calculate the change for the entire system between readings
systemDelta = float64(v.CPUStats.SystemUsage) - float64(previousSystem)
)
if systemDelta > 0.0 && cpuDelta > 0.0 {
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
}
return cpuPercent
}
有同學說,有了系統總時間,有了Container總時間,做一個簡單的除法運算就可以完成比例的運算過程:
Container CPU使用率
= container CPU使用總時間
/ 系統CPU的總時間
* CPU核數
注意,注意!這個是有問題的。
注意,注意!這個是有問題的。
注意,注意!這個是有問題的。
前面講過了,這個數值是 使用CPU的時間,如果計算的時候拋開了時間的概念,那么肯定是不對的。在剛才那個算式中,有一個假設的前提:Container啟動的時間 和 系統的啟動時間 是一樣的。這明顯是不對的。
所以,參見上面的代碼。Docker 在計算 Container 的 CPU使用率 的時候,會取兩次數值,通過兩次數值的差值再計算使用率。
其他 metrics
我們的文章主要介紹了如何獲取CPU使用率。但是,其他的指標也是同樣的方法,比如內存使用,IO使用。
源碼在我貼出的代碼片段附近就可以看到,有興趣的同學可以繼續深入研究下。
結尾
作者和出處(reposkeeper) 授權分享 By CC BY-SA 4.0
關注微信公眾號,獲取新文章的推送!