我在使用docker時也發現了這個問題,看來在docker內執行top/free命令的確有問題
原文:https://zhuanlan.zhihu.com/p/35914450
作者:兩片
Docker 是一種對程序環境進行封裝並實現資源的隔離技術,可秒級啟動,可瞬間啟動千千萬萬個實例,這些優點讓它在出現之初就受到極大的關注,其火爆程度就如今天的深度學習一般。人們拿到了錘子就到處找釘子,什么都想套上 Docker,連生產數據庫都想用 Docker 來運行,少不了碰到很多釘子,磕磕碰碰才形成今天這個比較合理的各個使用場景。
在一個完善的系統中,我們總需要知道自己的服務使用了多少CPU,內存資源,想知道磁盤讀寫多少,網絡流量如何。如果在 Docker 容器中執行 top,free 等命令會發現我們能看到CPU所有核的使用情況以及宿主機的內存占用,這並不是我們需要的,我們需要的是這個容器被限制了多少 CPU,內存,當前容器內的進程使用了多少。
要明白為何 top,free 顯示的是宿主機的情況,以及如何獲得容器的資源限制需要先理解 Docker 的兩項基礎技術:Namespace 和 cgroup。兩者在 CoolShell 的博客里都已經講解得非常清楚,這里只簡單說明一下。Namespace 解決了環境隔離的問題,它將進程的PID,Network,IPC等和其它進程隔離開來,結合 chroot,讓進程仿佛運行在一個獨占的操作系統中。cgroup 則對進程能夠使用的資源作限制,如在一台48核256G的機器上只讓容器使用2核2G。
容器中CPU,內存,磁盤資源都是被 cgroup 限制和統計的,所有信息都放在 /sys/fs/cgroup 這個虛擬文件夾里,在容器里運行 mount 命令可以看到這些掛載記錄
...
cgroup on /sys/fs/cgroup/cpuset type cgroup (ro,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu type cgroup (ro,nosuid,nodev,noexec,relatime,cpu)
cgroup on /sys/fs/cgroup/cpuacct type cgroup (ro,nosuid,nodev,noexec,relatime,cpuacct)
cgroup on /sys/fs/cgroup/memory type cgroup (ro,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/devices type cgroup (ro,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/freezer type cgroup (ro,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (ro,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/perf_event type cgroup (ro,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (ro,nosuid,nodev,noexec,relatime,hugetlb)
...
CPU
獲取CPU信息需要用到 cpuset, cpu, cpuacct
- cpuset 提供一個機制指定 cgroup 可以使用哪些CPU核哪些內存節點(an on-line node that contains memory)
- cpu 設置CPU的使用配額/份額
- cpuacct 提供 cgroup CPU 使用時長的統計信息
把上面三個文件夾的文件都看了一遍后,會發現 cgroup 里的提供的信息比物理機上的資源使用狀況信息少很多,這其實是非常合理的,因為兩者本質上就是不同的東西。比如物理機只有10%的時間在運行任務,那剩下90%時間物理機是真正稱得上空閑,而 cgroup 限制組內進程最多只能用50%的CPU,而組內進程只用了10%的CPU,但不能說還有40%或者80%是空閑的,在 cgroup 內根本沒有空閑這個概念,因此也就沒法從中得到常見的 idle 指標。
另外,cgroup 內沒有統計 iowait,中斷,這些也沒法拿到。不過沒關系,細化的指標可以在進程里找,系統信息足夠反映負載狀況即可。這里介紹一下我比較關心的3個指標的獲取方式。
LimitedCores: cgroup 限制可以使用的 CPU 核數
cgroup 有三種方式限制 CPU 的使用
- share,對應文件
cpu/cpu.shares,是系統內多個 cgroup 的進程同時運行時他們的CPU使用上限占比,比如只有兩個cgroup: [cgroup1.shares: 1024, cgroup2.shares: 512],那 cgroup1 可以用 2/3 的 CPU。 - quota,對應文件
cpu/cpu.cfs_quota_uscpu/cpu.cfs_period_us,表示在每個 period(時間間隔)內 cgroup 可以使用的 CPU 時間。文件里數字的單位是微秒,假設 {quota: 200000, period: 100000},意思是每 100ms 可以使用 200ms CPU 時間,相當於可以使用兩個核。quota 為 -1 的時候表示無限制。 - cpuset,對應文件
cpuset/cpuset.cpus, 表示 cgroup 可以用那幾個 CPU,比如0,3-7,12可以使用 7 個核。
對於 share,我們沒法知道有多少鄰居以及鄰居的值為多少,所以忽略這個限制。考慮到 cpuset 在生產環境極少使用,同時用 quota 和 cpuset 的就更少了,所以我們的策略是:先看 quota,如果 quota 有限制則返回,否則再看 cpuset。
Usage:cgroup 的 CPU 占用率,占了物理機的多少CPU
上面我們拿到了能用多少個核,自然知道 cgroup 的占用上限,只要知道 cgroup 用了物理機的多少 CPU 就可以知道飽和程度了。要獲取這個首先需要知道一段時間內物理機用了多少CPU時間,然后獲得 cgroup 用了多少CPU時間,最后相除。
- 物理機使用的CPU時間從 /proc/stat 里獲取,將里面 cpu 那一行的數字相加並且乘以物理機CPU核數即可得到從開機到現在用的CPU總時間。可以設置一秒的時間間隔,求差即可得到這一秒內用的CPU時間。
- cgroup 使用的CPU時間可以從
cpuacct/cpuacct.usage中獲得,也是求一段時間的差即可。
特別需要注意的是 /proc/stat 里單位是納秒,而 cpuacct.usage 里的是 Clock Tick,一般是 100納秒/tick,准確數字可以通過 getconf CLK_TCK 命令獲得。
Throttled:cgroup 的 CPU 使用被限制的次數
如果被頻繁限制的話,說明很可能分配的CPU不夠用了。可以從 cpu/cpu.stat 的字段獲得。
內存
cgroup 對內存的使用做了比較多的統計,但是內存使用率本身就是一個糊塗賬,因為很多內存是為了提高性能從磁盤映射過來的需要時可以清理掉。cgroup 里的內存信息和定義推薦看 Kernal Doc cgroup-v1 memory
需要額外提一下的是 cache 和 mapped_file,cache 是 page cache memory 也成為 disk cache 是磁盤映射到內存的內存,而 mapped_file 也是一樣意思,但實際中常常會發現 cache 比 mapped_file 大很多,這其實是 mapped_file 是還被進程引用着的,而 cache 則包括曾經被進程用過但現在已經沒有任何進程使用的映射,也就是 unmapped_file。
對於內存,可以重點關注以下幾個指標(沒有特別注明,所有指標都從 memory/memory.stat 中取:
- Total: cgroup 被限制可以使用多少內存,可以從文件里的 hierarchical_memory_limit 獲得,但不是所有 cgroup 都限制內存,沒有限制的話會獲得 2^64-1 這樣的值,我們還需要從
/proc/meminfo中獲得MemTotal,取兩者最小。 - RSS: Resident Set Size 實際物理內存使用量,在
memory/memory.stat的 rss 只是 anonymous and swap cache memory,文檔里也說了如果要獲得真正的 RSS 還需要加上 mapped_file。 - Cached:
memory/memory.stat中的 cache - MappedFile:
memory/memory.stat中的 mapped_file - SwapTotal: 限制的 swap 大小,(hierarchical_memsw_limit - hierarchical_memory_limit) 同樣會遇到內存沒有限制的情況。
- SwapUsed:
memory/memory.stat中的 total_swap
磁盤
cgroup 通過 blkio 子系統實現對磁盤讀寫控制。當前有兩種限制磁盤的策略,CFQ(就是各個cgroup平分磁盤使用時間)和 Throttling。
如果是 CFQ,可從 blkio/blkio.io_service_bytes_recursive 獲得各個磁盤的 IO 統計
如果是 Throttling,可從 blkio/blkio.throttle.io_service_bytes 獲得各個磁盤的 IO 統計
文件里會將 IO 分為 Read/Write, Sync/Async,將所有磁盤的 Read/Write相加即可得到磁盤讀寫量。
網絡
網絡統計信息放在 /sys/class/net/<ethX>/statistics 中,在容器中只能看到自己用到的網絡接口,但網絡接口的名字常常不確定,可以先通過命令 ip -o -4 route show to default | awk '{print $5}' 獲得默認網絡接口的名字。
然后就可以從 rx_bytes 獲得接收字節數,從 tx_bytes 獲得發出字節數了。
至此,比較重要的信息都已經拿到,有時還需要提防一下宿主機超賣的情況,有時出了問題並不是自己容器用資源太多,而是資源都被同主機的其它主機占用了,這時可以從 /proc 里拿額外的信息來判斷。
package
發現目前沒有專門針對 container 的指標獲取,於是寫了個 Go 的,放在 github 上,地址:container-metrics
