通過TCP Allocate連線數告警瞭解prometheus-NodeExporter資料採集及相關知識擴散

姚紅發表於2022-03-23

1.問題由來

近日有環境告警如下:TCP Allocate連線數過多

 

 

很多資料告訴我們使用:netstat –ant | grep ^tcp | wc –l命令查詢,但查詢的值與告警中獲取的只相差很大,於是下載NodeExporter的原始碼進行檢視進行一探究竟。

 原始檔:https://www.cnblogs.com/yaohong/p/16046499.html

 

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檢視的不一樣。

原始檔:https://www.cnblogs.com/yaohong/p/16046499.html

 

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原始碼發現ss實際上是解析/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)

 原始檔:https://www.cnblogs.com/yaohong/p/16046499.html

 

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。這種情況就是其中一種,也是最常見的情況。

原始檔:https://www.cnblogs.com/yaohong/p/16046499.html

 

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指令碼

 原始檔:https://www.cnblogs.com/yaohong/p/16046499.html

相關文章