容器化Node Exporter對主機磁盤使用率的監控問題
Node Exporter是prometheus社區開發的節點監控工具。在Prometheus生態中,如果一個組件被命名為Exporter,那么從狹義上來說,可以將它理解為一個適配器,用於將某些應用原生的非Prometheus格式的監控指標轉換為符合Prometheus標准的指標,供Prometheus Server抓取,從而能夠將該應用完美地融入Prometheus生態體系。一般來說,在Linux系統下,操作系統的狀態信息會以虛擬文件系統的形式在/proc和/sys兩個目錄之下。因此,Node Exporter的職責就是按需從/proc和/sys讀取指標(本質上是讀取相應文件的內容,例如內存相關的信息存放在/proc/meminfo中),然后進行格式轉換。
Node Exporter的工作原理非常簡單,如果以可執行文件的方式直接部署在宿主機上通常也不會發生太大的問題。但是隨着雲原生浪潮的到來,再加上Prometheus和Kubernetes原生的親和性,以Prometheus為中心構建Kubernetes集群的監控系統顯然是我們的不二之選。因此作為Prometheus生態重要的組成部分,Node Exporter的容器化也是不可避免的,並且一般它會以DaemonSet的形式運行在集群的每個節點之上。
已知Docker容器本質上只是一個普通進程,但是通過Namespace和CGroup等內核機制為其構建了一個邏輯上的隔離環境,從而允許其有獨立的網絡空間,掛載點視圖等等。由於Node Exporter的任務是監控宿主機的運行狀態,隔離的運行環境反而會對其運行造成干擾,因此它需要盡量與宿主機貢獻Namespace。一般與Node Exporter運行相關的並且我們能夠配置的是Network和PID這兩個Namespace。所以DaemonSet的相關配置如下:
apiVersion
已知Node Exporter主要通過讀取/proc和/sys來獲取監控指標,雖然已經通過上述方法共享了Network Namespace以及PID Namespace,但是容器和宿主機的/proc以及/sys中的內容仍然不是完全相同的。因此,最好的方法其實是將宿主機中的這兩個目錄掛載到容器中,再讓容器中的Node Exporter進程讀取從這兩個掛載目錄中獲取宿主機的運行信息,配置如下:
apiVersion
通過上述配置,在容器中運行的Node Exporter的確能夠正確獲取絕大多數的所在宿主機的運行指標。但是節點磁盤的容量數據除外。將常用的df命令的輸出結果和Node Exporter暴露的指標對比之后可以發現,Node Exporter暴露的指標不但少了若干個掛載磁盤的信息,而且同樣對於掛載在根目錄的磁盤,兩者的數據也並不一致。下面我們通過Node Exporter采集相關指標的實現原理來分析造成上述現象的原因。
當我們在談論磁盤的使用率時談論的實際上是掛載在某個目錄的磁盤分區的使用率。一個磁盤分區會由對應的文件系統進行管理,通過該文件系統我們就能獲取到該分區的使用情況。已知自從引入了Mount Namespace之后,掛載點信息已經從系統全局級別的信息變為了進程級別的信息。顯然對於宿主機的磁盤使用率的監控,我們應該訪問的是根Mount Namespace的掛載信息而1號進程肯定是位於根Namespace內的。因此Node Exporter會首先通過讀取/proc/1/mounts獲取根Mount Namespace的掛載點信息:
root@yzz:/dev# cat /proc/1/mounts
sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
udev /dev devtmpfs rw,nosuid,relatime,size=4066220k,nr_inodes=1016555,mode=755 0 0
devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0
tmpfs /run tmpfs rw,nosuid,noexec,relatime,size=817468k,mode=755 0 0
/dev/vda1 / ext4 rw,relatime,errors=remount-ro,data=ordered 0 0
securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /dev/shm tmpfs rw,nosuid,nodev 0 0
每一行都是對一個掛載點的描述,從左往右各列依次為設備名,掛載點,文件系統的類型等等,Node Exporter再依次對每行的掛載點執行statfs系統調用,獲取掛載在該路徑的文件系統的相關信息,從中我們可以獲取到對應設備的總的存儲容量以及當前可用的存儲容量,從而分析出磁盤的使用率。
通過上述Node Exporter對磁盤使用率指標獲取原理的分析,我們可以知道,之所以在容器中運行的Node Exporter無法獲取到正確的信息是因為容器的根目錄和宿主機的根目錄並不一致。因此Node Exporter對相對於容器根目錄的掛載點執行statfs系統調用是無法獲取到正確信息的。對於這個問題,解決方法是將宿主機的根目錄以Bind Mount的形式(且Propagation Type為Slave,從而宿主機的掛載點在容器中也可見)掛載到容器中並讓Node Exporter基於該路徑尋找對應磁盤的掛載點,具體配置如下:
apiVersion
此時,對於宿主機根目錄掛載磁盤信息的訪問,Node Exporter將不再直接訪問容器的根目錄,而是會訪問宿主機根目錄在容器中的掛載點,即/host/root,獲取得到。
綜上,對於Node Exporter這種較為特殊的應用,容器化的作用帶來的僅僅是打包部署的方便,而容器的隔離性實際上還會對應用的正常運行造成阻礙。但是通過本文的案例,我們也可以看到,相比於虛擬機,容器的隔離機制是更為靈活的,通過共享Namespace以及HostPath形式的Volume掛載,最終能夠讓對宿主機環境有較大依賴的程序依然能夠正常地在容器中運行。
