通過TCP Allocate連線數告警瞭解prometheus-NodeExporter資料採集及相關知識擴散
1.問題由來
近日有環境告警如下:TCP Allocate連線數過多
很多資料告訴我們使用:netstat –ant | grep ^tcp | wc –l命令查詢,但查詢的值與告警中獲取的只相差很大,於是抱著學習的心態下載NodeExporter的原始碼進行檢視進行一探究竟。
2.NodeExporter原始碼初探
通過檢視node_exporter-1.1.2程式碼瞭解到node_sockstat_TCP_alloc呼叫的是node_exporter.go程式碼中parseSockstatProtocol
函式。
func parseSockstatProtocol(kvs map[string]int) NetSockstatProtocol { var nsp NetSockstatProtocol for k, v := range kvs { // Capture the range variable to ensure we get unique pointers for // each of the optional fields. v := v switch k { case "inuse": nsp.InUse = v case "orphan": nsp.Orphan = &v case "tw": nsp.TW = &v case "alloc": nsp.Alloc = &v case "mem": nsp.Mem = &v case "memory": nsp.Memory = &v } } return nsp }
進一步分析呼叫可知TCP Alloc的值取自於/proc/net/sockstat檔案。
// NetSockstat retrieves IPv4 socket statistics. func (fs FS) NetSockstat() (*NetSockstat, error) { return readSockstat(fs.proc.Path("net", "sockstat")) }
那麼第一個疑問解決了,知道了TCP Alloc的取值方法。
那麼問題來了,為什麼netstat –ant | grep ^tcp | wc –l和/proc/net/sockstat檢視的不一樣。
3.ss VS netstat
3.1.socket
socket
是用於與網路通訊的Linux檔案描述符。在Linux中,所有東西都是一個檔案。在這種情況下,可以將socket視為寫入網路而不是寫入磁碟的檔案。socket在TCP和UDP中有不同的風格。
3.2.procfs
Procfs
(proc filesystem)是Linux公開的一種檔案系統,它就像窺探核心記憶體一樣。它存在於/proc中,並在/proc/net/tcp和/proc/net/udp 中暴露TCP和UDP套接字的資訊。
3.3.ss VS netstat
通過查詢netstat相關資料《netstat》瞭解到如下資訊,人們正在從netstat轉向ss,因為netstat(實際上是網路工具)已被棄用。但為什麼還要如此多的人在使用netstat,猜測是因為netstat也可能被安裝在更多的地方。
On Linux, netstat (part of "net-tools") is superseded by ss (part of iproute2). The replacement for netstat -r is ip route, the replacement for netstat -i is ip -s link, and the replacement for netstat -g is ip maddr, all of which are recommended instead.
ss包含在iproute2包中,是netstat的替代品。它除了顯示類似於netstat的資訊。並且可以顯示比其他工具更多的TCP和狀態資訊。對於跟蹤TCP連線和套接字,它是一種新的、非常有用的和更快的(與netstat相比)工具,同時ss直接查詢核心,響應速度比netstat快得多。。
關於netstat的替代如下:
$ netstat -r replaced by $ ip route
$ netstat -i replaced by $ ip -s lin
$ netstat -g replaced by $ ip maddr
而ss命令是怎麼獲取到相關引數的?通過檢視ss原始碼發現s
s實際上是解析/proc/net/sockstat的輸出
。
tcp_total在/proc/net/sockstat的輸出中實際上是“alloc”;
tcp4_hash在/proc/net/sockstat的輸出中實際上是“inuse”;
tcp_tws在/proc/net/sockstat的輸出中實際上是“tw”;
因此,/proc/net/sockstat的輸出必須與ss -s的輸出一致
。
# cat /proc/net/sockstat && echo "----" && cat /proc/net/sockstat6 && echo "---" && ss -s sockets: used 7095 TCP: inuse 2066 orphan 0 tw 193 alloc 3235 mem 290 UDP: inuse 6 mem 3 UDPLITE: inuse 0 RAW: inuse 0 FRAG: inuse 0 memory 0 ---- TCP6: inuse 1072 UDP6: inuse 4 UDPLITE6: inuse 0 RAW6: inuse 0 FRAG6: inuse 0 memory 0 --- Total: 7095 (kernel 17923) TCP: 3428 (estab 3079, closed 290, orphaned 0, synrecv 0, timewait 193/0), ports 0 Transport Total IP IPv6 * 17923 - - RAW 0 0 0 UDP 10 6 4 TCP 3138 2066 1072 INET 3148 2072 1076 FRAG 0 0 0
讓我們手動解析下/proc/net/sockstat和sockstat6的輸出:
s.tcp4_hashed = 2066 s.tcp6_hashed = 1072 s.closed = 290 s.tcp_tws = 193
我們可得出如下公式:
alloc=s.tcp_total=s.tcp_total =s.tcp4_hashed + s.tcp6_hashed + s.closed - s.tcp_tws
減去s.tcp_tws是因為290個closed套接字中193個是tcp_tws狀態。
關於/proc/net/sockstat的輸出資訊如下:
sockets: used:已使用的所有協議套接字總量 TCP: inuse:正在使用(正在偵聽)的TCP套接字數量。 TCP: orphan:無主(不屬於任何程序)的TCP連線數(無用、待銷燬的TCP socket數) TCP: tw:等待關閉的TCP連線數。 TCP:alloc(allocated):已分配(已建立、已申請到sk_buff)的TCP套接字數量。 TCP:mem:套接字緩衝區使用量(單位不詳。用scp實測,速度在4803.9kB/s時:其值=11,netstat –ant 中相應的22埠的Recv-Q=0,Send-Q≈400)
4.什麼是tcp alloc
在socket統計中,有兩種型別的TCP套接字:allocated (已分配)的和inuse(使用狀態)。
1,.allocated :所有的TCP socket狀態都被計數為alloc。
2,inuse:除TCP_CLOSE之外的所有TCP socket狀態都被計算為inuse(使用狀態)。
在許多情況下,TCP套接字可以標記為TCP_CLOSE。然而,核心將TCP套接字的初始狀態設定為“TCP_CLOSE”。
因此,如果名為Closed的列具有較高的數字,而名為timewait的列具有較低的數字,那麼應用程式可能會建立TCP套接字,而不做其他任何事情。在許多情況下,核心可能會將一個TCP套接字標記為TCP_CLOSE。這種情況就是其中一種,也是最常見的情況。
5.NodeExporter採集記憶體和CPU的方式
5.1.NodeExporter採集記憶體使用率
在prometheus中獲取記憶體使用率的公式為:
(1 - (node_memory_MemAvailable_bytes{instance=~"$node"} / (node_memory_MemTotal_bytes{instance=~"$node"})))* 100
通過分析NodeExporter的原始碼node_exporter-1.1.2/node_exporter_test.go,可知記憶體讀取/proc/meminfo檔案:
func (fs FS) Meminfo() (Meminfo, error) { b, err := util.ReadFileNoStat(fs.proc.Path("meminfo")) if err != nil { return Meminfo{}, err } m, err := parseMemInfo(bytes.NewReader(b)) if err != nil { return Meminfo{}, fmt.Errorf("failed to parse meminfo: %v", err) } return *m, nil }
從而可知prometheus中node_memory_MemAvailable_bytes的值是取自/proc/meminfo的MemAvailable引數值,node_memory_MemTotal_bytes是取自/proc/meminfo的MemTotal引數值。
而記憶體使用率公式為:
(1-MemAvailable/MemTotal)*100
5.2.NodeExporter採集CPU使用率
在prometheus中獲取記憶體使用率的公式為:
100 - (avg by (instance) (irate(node_cpu_seconds_total{instance=~"$node",mode="idle"}[5m])) * 100)
通過分析NodeExporter的原始碼procfs-0.0.8/procfs-0.0.8/stat.go,可知記憶體讀取/proc/stat檔案:
func (fs FS) Stat() (Stat, error) { fileName := fs.proc.Path("stat") data, err := util.ReadFileNoStat(fileName) if err != nil { return Stat{}, err } stat := Stat{}
如果通過shell指令碼讀取/proc/stat檔案內容計算出CPU使用率可參考:LINUX 根據 /proc/stat 檔案計算cpu使用率的shell指令碼