Container及其內部程式監控剖析

宜信技術學院發表於2019-08-23

來源:宜信技術學院

目前市場上的虛擬化技術種類很多,例如moby(docker)、LXC、RKT等等。在帶來方便應用部署和資源充分利用的好處的同時,如何監控相應Container及其內部應用程式成為運維人員不可避免遇到的新情況。UAV.Container從虛擬化技術的基礎原理和Linux作業系統的核心特性出發,得到Container容器和內部程式的各維度監控資料,使無論是虛擬機器或物理機運維人員,還是業務運維人員角度,都能得到合適的監控維度。

虛擬化技術從基礎原理上主要是cgroups、namespace和file system的應用,而作業系統作為cgroup和namespace根節點,無論在container裡啟動何種應用,從核心角度上來說,肯定在作業系統有其一定的特徵和表現形式。我們需要做的就是對這些特徵做加工處理,以得到相應的監控資料。

下面我們以docker技術舉例,其他虛擬化技術類似。

1. Container ID

Container ID是一個Container的唯一標識。從容器監控的角度我們需要能得到該程式在哪個Container裡執行。在作業系統層面,程式的cgroup的掛載情況就能有所體現。如圖所示,我們在一個ID為3411554ff684的Container內部跑一個Tomcat程式。

Container及其內部程式監控剖析


由於Container的pid namespace是作業系統的pid namespace的子namespace,那麼該程式在作業系統級也應該有相應的pid,用docker top命令驗證一下:

Container及其內部程式監控剖析


該容器內程式在宿主機上的程式號為1848。接下來進入/proc/1848/cgroup下看看該程式的cgroup掛載情況

Container及其內部程式監控剖析


從cgroup檔案裡清楚的顯示了實現了該容器的虛擬化技術、Container ID和此container的資源掛載路徑,對比一下這裡面顯示的Container ID,和建立Container時的ID完全相同。這也驗證了通過掃描宿主機程式的cgroup資訊可以獲得Container ID。這樣就將pid和Container ID做了關聯。

2. CPU

雖然cgroup管控了該cgroup下所有程式的CPU使用情況,但從作業系統的角度上,不論程式是否隸屬於某個子cgroup下,仍然是共用宿主機的CPU。所以監控宿主機上該程式的CPU就能得到程式的CPU監控指標。

Linux上常用的CPU監控命令是top。top對CPU監控的原理是在time1時刻獲取CPU從啟動時的累計總時間countAll1和busy總時間countBusy1,再到time2時刻獲取CPU總時間countAll2和busy總時間countBusy2,最後用busy的時間差值減去總時間的差值得到了在time1到time2這個時間段內機器CPU的佔用情況。也就是:

CPU佔用率(%) = (countBusy2 - countBusy1)/(countAll2 - countAll1) * 100

程式同理,在兩個時刻分別得到每個程式的busy總時間countProcBusy1和countProcBusy2,則得到程式CPU佔用率:

程式CPU佔用率(%) = (countProcBusy2 - countProcBusy1)/(countProcAll2 - countProcAll1)*100

宿主機從啟動開始的CPU總時間片可以從/proc/stat下獲取:

Container及其內部程式監控剖析


第一行是總的CPU使用情況,具體引數的意思:

所以,選擇當前為time1,3秒後為time2,countAll = user + nice + system + idle + iowait + irq + softirq + stealstolean + guest + guest_nice。countBusy為countAll減去idle的值,這樣上面第一個公式的所有需要的值就齊了,可以直接計算。

第二行、第三行是每個邏輯CPU的使用情況,這裡記下有兩個邏輯CPU,CPU的邏輯核數與CPU顯示模式irix和solaris有關。

接下來是countProcBusy的計算,程式的CPU時間片位於/proc/$pid/stat下,如圖所示:

Container及其內部程式監控剖析


這個檔案裡面體現了很多程式的相關資訊。其中第14、15、16、17個引數與CPU有關。

所以,countProcBusy = utime + stime + cutime + cstime,該值包括其所有執行緒的cpu時間。而countProcAll2-countProcAll1=3s,通過兩個時刻的countProcBusy和countProcAll,程式CPU的佔用率就能得到了。

其中需要注意的問題有兩點:

1).jiffies實際上指的是核心時鐘使用的節拍總數,所以這裡的jiffies需要換算成秒才能應用上面的除法公式。

2).剛剛我們談到CPU顯示模式irix和solaris,簡單來說irix模式就是機器有N個邏輯CPU,CPU顯示上限就是N*100%,solaris模式就是不管機器有多少邏輯CPU,CPU顯示上限就是100%,而/proc/$pid/stat顯示的是計算了所有邏輯CPU時間的,所以兩種顯示方式意味著計算方法稍有差異,solaris模式的結果需要在上面程式CPU佔用率公式基礎之上除以邏輯核數。

3. 記憶體

程式記憶體的監控有兩個維度的資料:一是物理佔用記憶體大小,二是程式記憶體佔用百分比的大小。

程式記憶體佔用率(%) = 程式實體記憶體佔用大小 / 宿主機總記憶體大小 * 100

與CPU類似,/proc/$pid/status檔案記錄了程式實體記憶體使用情況,其中VmRSS即為該程式目前所佔實際實體記憶體的大小。

Container及其內部程式監控剖析


/proc/meminfo檔案下記錄了機器記憶體佔用情況,這個檔案很長,擷取其中的一部分展示一下,MemTotal就是宿主機總記憶體大小:

Container及其內部程式監控剖析


這樣,這個程式的實體記憶體佔用和機器總記憶體就都得到了,相應的程式記憶體的佔用率也就得到了。

4. 磁碟IO

磁碟IO獲取也很簡單,/proc/$pid/io已經幫我們把這個程式的io情況記錄下來了,但是與CPU類似,io檔案裡存的也是該程式從啟動到現在的io總量,那麼:

磁碟I/O(bytes/秒) = (time2時刻I/O – time1時刻I/O) / (time2 – time1)

Container及其內部程式監控剖析


其中的read_bytes和write_bytes分別為該程式從啟動到目前為止的讀取位元組數和寫入位元組數,分別取2個時刻的值,根據上面的公式,就得到了該程式的磁碟IO。

5. 埠號和連線數

由於Network Namespace對網路做了隔離,所以如果程式在Container內部執行,該程式的埠資訊也應該是程式本身監聽的埠號,而不是真正實際對外的埠,而Container內外埠的對映機制是由應用的虛擬化技術本身控制的,這就避免不了與實現容器的虛擬化技術打交道了,那麼問題就轉化成獲取容器內程式本身監聽的埠了。

/proc/$pid/net/tcp(tcp6,udp,udp6)就對埠號和連線數做了相應的歷史記錄。這些檔案格式都類似,以tcp6舉例

Container及其內部程式監控剖析


解釋幾個關鍵的key:

因為st = 0A代表listen,所以從其中挑選出st = 0A的資料,取出對應的inode號,這裡這個inode號是socket號,則問題轉換為了這個程式是否還存在這個socket號代表的socket。在/proc/$pid/fd下有該程式所有的fd(file descriptor),擷取一段舉個例子。

Container及其內部程式監控剖析


每個檔案描述符後面的軟鏈實際上就是開啟的檔案,以socket開頭的就是這個程式開啟的socket,在中括號中間的部分就是socket號。拿著這個socket號和上面tcp6裡獲得的inode號做一個匹配,如果對應上,那麼tcp6裡的st = 0A的埠就是這個程式監聽的。至於容器內外埠的對映,這就需要根據應用的虛擬化技術的對映方法來獲取了。連線數計算與埠掃描是同理的,區別只在於需要對st = 01(establish)進行掃描計數累加。

總結:

1. 上面的方法將容器內所有程式的CPU、記憶體、磁碟IO、埠號和連線數都拿到了。根據Container ID就可以對不同的Container的對應資料做加和,就獲得了Container級的監控資料。

2. 在容器內的程式是通過在作業系統級別反映出的pid和Container ID的對應關係來關聯的。這樣就可以通過讀取/proc下的檔案來獲取監控資料。

參考文件:

elixir.free-electrons.com/linux/v3.10…


相關文章