透過 node-exporter 徹底弄懂機器監控:01. node-exporter 框架講解

SRETalk發表於2024-05-31

前言

Prometheus 生態裡有很多采集器負責各類監控資料的採集,其中使用最廣泛的,顯然是 node-exporter,負責 Linux、BSD 等系統的常規監控指標的採集,比如 CPU、記憶體、硬碟、網路、IO 等。其 github 地址是:https://github.com/prometheus/node_exporter 。很多人都用過,但對其細節未必清楚。

我想寫一個小專欄,透過 node-exporter 這個採集器,講解各類指標的含義、採集方法、使用場景、注意事項。順帶講解 Linux 的很多觀測手段。想必對於初中級研發、運維人員都會有幫助,專欄依舊會放到星球裡,爭取讓星球的資源慢慢更為豐富起來。這塊寫完之後,後面可以繼續 mysql、redis 等相關的專欄,一點點磕。

安裝 node-exporter

要想方便除錯,理解整個知識,建議還是要把 node-exporter 的程式碼下載下來,能夠本地編譯執行。這裡我做一個簡單演示,我的電腦是 Mac,M1 晶片,首先下載 go 安裝包(https://go.dev/dl/):https://go.dev/dl/go1.22.2.darwin-arm64.tar.gz。一般使用 tar.gz 的檔案就好,不用 pkg。

cd /Users/ulric/works/tgz
wget https://go.dev/dl/go1.22.2.darwin-arm64.tar.gz
tar -zxf go1.22.2.darwin-arm64.tar.gz

操作如上,/Users/ulric/works/tgz/go 這個目錄就是 go 的安裝目錄,然後配置環境變數:

export GOROOT=/Users/ulric/works/tgz/go
export GOPATH=/Users/ulric/works/gopath
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin

GOROOT 是 go 的安裝目錄,GOPATH 是 go 的工作目錄,PATH 是環境變數,這樣配置之後,就可以使用 go 命令了。上面的幾行命令可以儲存在 ~/.bash_profile 或者 ~/.zshrc 裡,這樣每次開啟終端都會自動載入。

驗證 go 環境是否正常安裝:

% go version
go version go1.22.2 darwin/arm64

然後下載 node-exporter 的程式碼:

cd /Users/ulric/works
git clone https://github.com/prometheus/node_exporter.git

然後就可以編譯了,如果你的網路環境不好,編譯之前可以設定代理:

export GOPROXY=https://goproxy.cn,direct
cd /Users/ulric/works/node_exporter
go build

如果一切正常,就可以執行 node_exporter 做測試了,我先看看其版本:

ulric@ulric-flashcat node_exporter % ./node_exporter --version
node_exporter, version  (branch: , revision: 0d3400ebc976e14d5b87db276bb2ec32f55b4052)
  build user:
  build date:
  go version:       go1.22.2
  platform:         darwin/arm64
  tags:             unknown

如上,就完成了 node-exporter 的原始碼安裝。

啟動 node-exporter

生產環境啟動 node-exporter,通常是透過 systemd 等方式啟動,咱們這裡為了學習方便,就直接把程序啟動在前臺即可:

ulric@ulric-flashcat node_exporter % ./node_exporter --log.level=debug
ts=2024-05-23T04:08:01.560Z caller=node_exporter.go:193 level=info msg="Starting node_exporter" version="(version=, branch=, revision=0d3400ebc976e14d5b87db276bb2ec32f55b4052)"
ts=2024-05-23T04:08:01.560Z caller=node_exporter.go:194 level=info msg="Build context" build_context="(go=go1.22.2, platform=darwin/arm64, user=, date=, tags=unknown)"
ts=2024-05-23T04:08:01.561Z caller=node_exporter.go:199 level=debug msg="Go MAXPROCS" procs=1
ts=2024-05-23T04:08:01.561Z caller=filesystem_common.go:111 level=info collector=filesystem msg="Parsed flag --collector.filesystem.mount-points-exclude" flag=^/(dev)($|/)
ts=2024-05-23T04:08:01.562Z caller=filesystem_common.go:113 level=info collector=filesystem msg="Parsed flag --collector.filesystem.fs-types-exclude" flag=^devfs$
ts=2024-05-23T04:08:01.562Z caller=node_exporter.go:111 level=info msg="Enabled collectors"
ts=2024-05-23T04:08:01.562Z caller=node_exporter.go:118 level=info collector=boottime
ts=2024-05-23T04:08:01.562Z caller=node_exporter.go:118 level=info collector=cpu
ts=2024-05-23T04:08:01.562Z caller=node_exporter.go:118 level=info collector=diskstats
ts=2024-05-23T04:08:01.562Z caller=node_exporter.go:118 level=info collector=filesystem
ts=2024-05-23T04:08:01.562Z caller=node_exporter.go:118 level=info collector=loadavg
ts=2024-05-23T04:08:01.562Z caller=node_exporter.go:118 level=info collector=meminfo
ts=2024-05-23T04:08:01.562Z caller=node_exporter.go:118 level=info collector=netdev
ts=2024-05-23T04:08:01.562Z caller=node_exporter.go:118 level=info collector=os
ts=2024-05-23T04:08:01.562Z caller=node_exporter.go:118 level=info collector=powersupplyclass
ts=2024-05-23T04:08:01.562Z caller=node_exporter.go:118 level=info collector=textfile
ts=2024-05-23T04:08:01.562Z caller=node_exporter.go:118 level=info collector=thermal
ts=2024-05-23T04:08:01.562Z caller=node_exporter.go:118 level=info collector=time
ts=2024-05-23T04:08:01.562Z caller=node_exporter.go:118 level=info collector=uname
ts=2024-05-23T04:08:01.565Z caller=tls_config.go:313 level=info msg="Listening on" address=[::]:9100
ts=2024-05-23T04:08:01.565Z caller=tls_config.go:316 level=info msg="TLS is disabled." http2=false address=[::]:9100

可以看到,node-exporter 啟動成功,監聽在 9100 埠,可以透過瀏覽器訪問:http://localhost:9100/metrics 檢視採集到的指標。或者透過 curl 命令:

curl -s http://localhost:9100/metrics

在我的本地 Mac 上,大概會採集 610 個指標,包括:

  • go 字首的指標:這是 node-exporter 程序本身的一些指標,比如 gc 耗時、記憶體使用等
  • node 字首的指標:機器的一些常規指標,比如 CPU、記憶體、硬碟、網路、IO 等,咱們後面重點研究這類指標
  • promhttp 字首的指標:node-exporter 的 http 服務的一些指標,比如請求次數

node-exporter 啟動引數

./node_exporter --help 可以檢視 node-exporter 的啟動引數,主要引數:

  • collector 字首的指標:控制是否啟用某個採集器,node-exporter 內建了多個採集器,比如 cpu、meminfo、ntp 等都是不同的採集器;collector 字首的還有一些引數是控制各個採集器具體行為的,比如 --collector.ntp.server 控制 ntp 採集器的 ntp 伺服器地址
  • web 字首的指標:控制 node-exporter 的 http 服務,比如 --web.listen-address 控制監聽地址,--web.telemetry-path 控制暴露指標資料的 API 路徑
  • log 字首的指標:控制日誌列印,比如 --log.level 控制日誌級別

大量引數都是圍繞 collector 的,因為 node-exporter 的核心就是採集器,不同的採集器負責不同的指標採集。有部分 collector 是預設開啟的,有部分是預設關閉的,README 中有詳細說明。對於那些預設關閉的 collector,如果你想啟用,就要小心測試了,看看採集耗時、對機器的資源佔用的影響等。

node-exporter 原始碼結構

程式碼倉庫根目錄下,有個 node_exporter.go,main 函式入口就在這裡。collector 目錄下是各個外掛的實現,比如 meminfo 相關的:

ulric@ulric-flashcat collector % ll meminfo*
-rw-r--r--  1 ulric  staff  1998 12 18 17:20 meminfo.go
-rw-r--r--  1 ulric  staff  2515 12 18 17:20 meminfo_darwin.go
-rw-r--r--  1 ulric  staff  1853 12 18 17:20 meminfo_linux.go
-rw-r--r--  1 ulric  staff  1163 12 18 17:20 meminfo_linux_test.go
-rw-r--r--  1 ulric  staff  1520 12 18 17:20 meminfo_netbsd.go
-rw-r--r--  1 ulric  staff  4655 12 18 17:20 meminfo_numa_linux.go
-rw-r--r--  1 ulric  staff  2950 12 18 17:20 meminfo_numa_linux_test.go
-rw-r--r--  1 ulric  staff  2483 12 18 17:20 meminfo_openbsd.go
-rw-r--r--  1 ulric  staff  2336 12 18 17:20 meminfo_openbsd_amd64.go

這些原始碼檔案分成了很多不同的字尾,這是因為不同的系統,meminfo 的實現是不同的,go 語言透過字尾來區分不同的系統,比如 meminfo_darwin.go 是 Mac 系統的實現,meminfo_linux.go 是 Linux 系統的實現。

不同的外掛,都會有個 init() 函式,這個函式會在 node-exporter 啟動的時候被呼叫,用來註冊外掛。比如 meminfo 外掛:

func init() {
	registerCollector("meminfo", defaultEnabled, NewMeminfoCollector)
}

所謂的外掛註冊,核心就是把各個外掛的資訊(名稱、是否啟用、工廠函式)儲存在全域性變數中,這樣一來,node-exporter 啟動的時候,就可以根據這些資訊,動態建立外掛例項,然後呼叫採集函式,採集指標。典型的外掛化設計思路。

外掛在 node-exporter 中抽象為一個 interface,只有一個 Update 函式:

type Collector interface {
	// Get new metrics and expose them via prometheus registry.
	Update(ch chan<- prometheus.Metric) error
}

比如記憶體採集外掛 meminfo,就實現了這個介面:

func (c *meminfoCollector) Update(ch chan<- prometheus.Metric) error {
	var metricType prometheus.ValueType
	memInfo, err := c.getMemInfo()
	if err != nil {
		return fmt.Errorf("couldn't get meminfo: %w", err)
	}
	level.Debug(c.logger).Log("msg", "Set node_mem", "memInfo", memInfo)
	for k, v := range memInfo {
		if strings.HasSuffix(k, "_total") {
			metricType = prometheus.CounterValue
		} else {
			metricType = prometheus.GaugeValue
		}
		ch <- prometheus.MustNewConstMetric(
			prometheus.NewDesc(
				prometheus.BuildFQName(namespace, memInfoSubsystem, k),
				fmt.Sprintf("Memory information field %s.", k),
				nil, nil,
			),
			metricType, v,
		)
	}
	return nil
}

node-exporter 框架層面,會建立 prometheus.Metric 型別的 channel,作為一個監控資料接收器,傳給 Update,各個外掛實現 Update 函式,把採集到的指標資料寫入 channel,node-exporter 框架層面,會把這些資料透過 /metrics 介面暴露出來。

小結

作為專欄第一篇,對 node-exporter 整體做了一些介紹,包括其定位、安裝方式、啟動引數、原始碼結構等。後續會逐個外掛詳細講解,一起揭開 Linux 監控資料的神秘面紗,看看這些資料是如何採集的,用來幹啥的,有啥坑,以及一些重要指標的含義。

相關文章