Docker CPU Usage


背景

當一台機器上跑有多個 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的時候。

假設下面兩種情況:

  1. 假設有一個進程占用了 CPU 250ms;那么整個CPU在1s的維度上來看,他的使用率是25%。
  2. 假設有兩個進程一起使用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.usagecpuacct.usage_percpucpuacct.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 Creative Commons License

關注微信公眾號,獲取新文章的推送!
qrcode


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM