去年底我寫了一個(gè)阿里云云監(jiān)控的 Prometheus Exporter, 后續(xù)迭代的過程中有一些經(jīng)驗(yàn)總結(jié), 這篇文章就將它們串聯(lián)起來做一個(gè)匯總, 講講為什么要寫 Exporter 以及怎么寫一個(gè)好用的 Exporter?
何為 Prometheus Exporter?
Prometheus 監(jiān)控基于一個(gè)很簡(jiǎn)單的模型: 主動(dòng)抓取目標(biāo)的指標(biāo)接口(HTTP 協(xié)議)獲取監(jiān)控指標(biāo), 再存儲(chǔ)到本地或遠(yuǎn)端的時(shí)序數(shù)據(jù)庫(kù). Prometheus 對(duì)于指標(biāo)接口有一套固定的格式要求, 格式大致如下:
# HELP http_requests_total The total number of HTTP requests.
# TYPE http_requests_total counter
http_requests_total{method="post",code="200"} 1027
http_requests_total{method="post",code="400"} 3
對(duì)于自己寫的代碼, 我們當(dāng)然可以使用 Prometheus 的 SDK 暴露出上述格式的指標(biāo). 但對(duì)于大量現(xiàn)有服務(wù), 系統(tǒng)甚至硬件, 它們并不會(huì)暴露 Prometheus 格式的指標(biāo). 比如說:
- Linux 的很多指標(biāo)信息以文件形式記錄在 /proc/ 下的各個(gè)目錄中, 如 /proc/meminfo 里記錄內(nèi)存信息, /proc/stat 里記錄 CPU 信息;
- Redis 的監(jiān)控信息需要通過 INFO 命令獲取;
- 路由器等硬件的監(jiān)控信息需要通過 `SNMP** 協(xié)議獲取;
- …
要監(jiān)控這些目標(biāo), 我們有兩個(gè)辦法, 一是改動(dòng)目標(biāo)系統(tǒng)的代碼, 讓它主動(dòng)暴露 Prometheus 格式的指標(biāo), 當(dāng)然, 對(duì)于上述幾個(gè)場(chǎng)景這種辦法完全是不現(xiàn)實(shí)的. 這時(shí)候就只能采用第二種辦法:
編寫一個(gè)代理服務(wù), 將其它監(jiān)控信息轉(zhuǎn)化為 Prometheus 格式的指標(biāo)
這個(gè)代理服務(wù)的基本運(yùn)作方式, 可以用下面這張圖來表示:
而這樣的代理服務(wù), 就稱作 Prometheus Exporter, 對(duì)于上面那些常見的情形, 社區(qū)早就寫好了成熟的 Exporter, 它們就是 node_exporter, redis_exporter 和 snmp_exporter.
為什么要寫 Exporter?
嗯, 寫 exporter 可以把監(jiān)控信息接進(jìn) Prometheus, 那為什么非要接進(jìn) Prometheus 呢?
我們不妨以阿里云云監(jiān)控為例, 看看接進(jìn) Prometheus 的好處都有啥:
阿里云免費(fèi)提供了一部分云監(jiān)控服務(wù), 但云監(jiān)控的免費(fèi)功能其實(shí)很有限, 沒辦法支持這些痛點(diǎn)場(chǎng)景:
- Adhoc TopN 查詢: 比如”找到當(dāng)前對(duì)公網(wǎng)帶寬消耗最大的 10 臺(tái)服務(wù)器”;
- 容量規(guī)劃: 比如”分析過去一個(gè)月某類型服務(wù)的資源用量”;
- 高級(jí)報(bào)警: 比如”對(duì)比過去一周的指標(biāo)值, 根據(jù)標(biāo)準(zhǔn)差進(jìn)行報(bào)警”;
- 整合業(yè)務(wù)監(jiān)控: 業(yè)務(wù)的監(jiān)控信息存在于另一套監(jiān)控系統(tǒng)中, 兩套系統(tǒng)的看板, 警報(bào)都很難聯(lián)動(dòng);
幸好, 云監(jiān)控提供了獲取監(jiān)控信息的 API, 那么我們很自然地就能想到: 只要寫一個(gè)阿里云云監(jiān)控的 Exporter, 不就能將阿里云的監(jiān)控信息整合到 Prometheus 體系當(dāng)中了嗎?
當(dāng)然, Exporter 就是做這個(gè)的!
集成到 Prometheus 監(jiān)控之后, 借助 PromQL 強(qiáng)大的表達(dá)能力和 Alertmanager, Grafana 的強(qiáng)大生態(tài), 我們不僅能實(shí)現(xiàn)所有監(jiān)控信息的整合打通, 還能獲得更豐富的報(bào)警選擇和更強(qiáng)的看板能力. 下面就是一個(gè)對(duì) RDS 進(jìn)行 TopN 查詢的例子:
這個(gè)動(dòng)機(jī)對(duì)于其它類型的 Exporter 也都是適用的: 當(dāng)一個(gè)系統(tǒng)本身暴露了監(jiān)控信息, 卻又無法接入 Prometheus, 我們就可以考慮寫一個(gè) exporter 把它接進(jìn)來了.
寫一個(gè)好用的 Exporter
類似 “阿里云 Exporter” 這種形式的 Exporter 是非常好寫的, 邏輯就是一句話:
寫一個(gè) Web 服務(wù), 每當(dāng) Prometheus 請(qǐng)求我們這個(gè)服務(wù)問我們要指標(biāo)的時(shí)候, 我們就請(qǐng)求云監(jiān)控的 API 獲得監(jiān)控信息, 再轉(zhuǎn)化為 Prometheus 的格式返回出去;
但這樣寫完之后僅僅是”能用”, 要做到”好用”, 還有諸多考量.
從文檔開始
Prometheus 官方文檔中 Writing Exporter 這篇寫得非常全面, 假如你要寫 exporter 推薦先通讀一遍, 限于篇幅, 這里只概括一下:
- 做到開箱即用(默認(rèn)配置就可以直接開始用)
- 推薦使用 YAML 作為配置格式
- 指標(biāo)使用下劃線命名
- 為指標(biāo)提供 HELP String (指標(biāo)上的
# HELP
注釋, 事實(shí)上這點(diǎn)大部分 exporter 都沒做好) - 為 Exporter 本身的運(yùn)行狀態(tài)提供指標(biāo)
- 可以提供一個(gè)落地頁(yè)
下面幾節(jié)中, 也會(huì)有和官方文檔重復(fù)的部分, 但會(huì)略去理論性的部分(官方文檔已經(jīng)說的很好了), 著重講實(shí)踐例子.
可配置化
官方文檔里講了 Exporter 需要開箱即用, 但其實(shí)這只是基本需求, 在開箱即用的基礎(chǔ)上, 一個(gè)良好的 Exporter 需要做到高度可配置化. 這是因?yàn)榇蟛糠?Exporter 暴露的指標(biāo)中, 真正會(huì)用到的大概只有 20%, 冗余的 80% 指標(biāo)不僅會(huì)消耗不必要的資源還會(huì)拖累整體的性能. 對(duì)于一般的 Exporter 而言, BP 是默認(rèn)只提供必要的指標(biāo), 并且提供 extra 和 filter 配置, 允許用戶配置額外的指標(biāo)抓取和禁用一部分的默認(rèn)指標(biāo). 而對(duì)于阿里云 Exporter 而言, 由于阿里云有數(shù)十種類型的資源(RDS, ECS, SLB…), 因此我們無法推測(cè)用戶到底希望抓哪些監(jiān)控信息, 因此只能全部交給用戶配置. 當(dāng)然, 項(xiàng)目還是提供了包含 SLB, RDS, ECS 和 Redis 的默認(rèn)配置文件, 盡力做到開箱即用.
Info 指標(biāo)
針對(duì)指標(biāo)標(biāo)簽(Label), 我們考慮兩點(diǎn): “唯一性” 和 “可讀性”:
“唯一性”: 對(duì)于指標(biāo), 我們應(yīng)當(dāng)只提供有”唯一性” 的(Label), 比如說我們暴露出 “ECS 的內(nèi)存使用” 這個(gè)指標(biāo). 這時(shí), “ECS ID” 這個(gè)標(biāo)簽就可以唯一區(qū)分所有的指標(biāo). 這時(shí)我們假如再加入 “IP”, “操作系統(tǒng)”, “名字” 這樣的標(biāo)簽并不會(huì)增加額外的區(qū)分度, 反而會(huì)在某些狀況下造成一些問題. 比方說某臺(tái) ECS 的名字變了, 那么在 Prometheus 內(nèi)部就會(huì)重新記錄一個(gè)時(shí)間序列, 造成額外的開銷和部分 PromQL 計(jì)算的問題, 比如下面的示意圖:
序列A {id="foo", name="舊名字"} ..................
序列B {id="foo", name="新名字"} .................
“可讀性”: 上面的論斷有一個(gè)例外, 那就是當(dāng)標(biāo)簽涉及”可讀性”時(shí), 即使它不貢獻(xiàn)額外的區(qū)分度, 也可以加上. 比如 “IP” 這樣的標(biāo)簽, 假如我們只知道 ECS ID 而不知道 IP, 那么根本對(duì)不上號(hào), 排查問題也會(huì)異常麻煩.
可以看到, 唯一性和可讀性之間其實(shí)有一些權(quán)衡, 那么有沒有更好的辦法呢?
答案就是 Info 指標(biāo)(Info Metric). 單獨(dú)暴露一個(gè)指標(biāo), 用 label 來記錄實(shí)例的”額外信息”, 比如:
ecs_info{id="foo", name="DIO", os="linux", region="hangzhou", cpu="4", memory="16GB", ip="188.188.188.188"} 1
這類指標(biāo)的值習(xí)慣上永遠(yuǎn)為 1, 它們并記錄實(shí)際的監(jiān)控值, 僅僅記錄 ecs 的一些額外信息. 而在使用的時(shí)候, 我們就可以通過 PromQL 的 “Join”(group_left) 語(yǔ)法將這些信息加入到最后的查詢結(jié)果中:
# 這條 PromQL 將 aliyun_meta_rds_info 中記錄的描述和狀態(tài)從添加到了 aliyun_acs_rds_dashboard_MemoryUsage 中
aliyun_acs_rds_dashboard_MemoryUsage
* on (instanceId) group_left(DBInstanceDescription,DBInstanceStatus)
aliyun_meta_rds_info
阿里云 Exporter 就大量使用了 Info 指標(biāo)這種模式來提供實(shí)例的詳細(xì)信息, 最后的效果就是監(jiān)控指標(biāo)本身非常簡(jiǎn)單, 只需要一個(gè) ID 標(biāo)簽, 而看板上的信息依然非常豐富:
記錄 Exporter 本身的信息
任何時(shí)候元監(jiān)控(或者說自監(jiān)控)都是首要的, 我們不可能依賴一個(gè)不被監(jiān)控的系統(tǒng)去做監(jiān)控. 因此了解怎么監(jiān)控 exporter 并在編寫時(shí)考慮到這點(diǎn)尤為重要.
首先, 所有的 Prometheus 抓取目標(biāo)都有一個(gè) up
指標(biāo)用來表明這個(gè)抓取目標(biāo)能否被成功抓取. 因此, 假如 exporter 掛掉或無法正常工作了, 我們是可以從相應(yīng)的 up
指標(biāo)立刻知道并報(bào)警的.
但 up
成立的條件僅僅是指標(biāo)接口返回 200 并且內(nèi)容可以被解析, 這個(gè)粒度太粗了. 假設(shè)我們用 exporter 監(jiān)控了好幾個(gè)不同的模塊, 其中有幾個(gè)模塊的指標(biāo)無法正常返回了, 這時(shí)候 up
就幫不上忙了.
因此一個(gè) BP 就是針對(duì)各個(gè)子模塊, 甚至于各類指標(biāo), 記錄細(xì)粒度的 up
信息, 比如阿里云 exporter 就選擇了為每類指標(biāo)都記錄 up
信息:
aliyun_acs_rds_dashboard_MemoryUsage{id="foo"} 1233456
aliyun_acs_rds_dashboard_MemoryUsage{id="bar"} 3215123
aliyun_acs_rds_dashboard_MemoryUsage_up 1
當(dāng) aliyun_acs_rds_dashboard_MemoryUsage_up
這個(gè)指標(biāo)出現(xiàn) 0 的時(shí)候, 我們就能知道 aliyun rds 內(nèi)存信息的抓取不正常, 需要報(bào)警出來人工介入處理了.
另外, 阿里云的指標(biāo)抓取 API 是有流控和每月配額的, 因此阿里云 exporter 里還記錄了各種抓取請(qǐng)求的次數(shù)和響應(yīng)時(shí)間的分布, 分別用于做用量的規(guī)劃和基于響應(yīng)時(shí)間的監(jiān)控報(bào)警. 這也是”監(jiān)控 exporter”本身的一個(gè)例子.
設(shè)計(jì)落地頁(yè)
用過 node_exporter
的會(huì)知道, 當(dāng)訪問它的主頁(yè), 也就是根路徑 /
時(shí), 它會(huì)返回一個(gè)簡(jiǎn)單的頁(yè)面, 這就是 exporter 的落地頁(yè)(Landing Page).
落地頁(yè)什么都可以放, 我認(rèn)為最有價(jià)值的是放文檔和幫助信息(或者放對(duì)應(yīng)的鏈接). 而文檔中最有價(jià)值的莫過于對(duì)于每個(gè)指標(biāo)項(xiàng)的說明, 沒有人理解的指標(biāo)沒有任何價(jià)值.
可選: 一鍵起監(jiān)控
這一點(diǎn)超出了 exporter 本身的范疇, 但確確實(shí)實(shí)是 exporter “好用” 的一個(gè)極大助力. exporter 本身是無法單獨(dú)使用的, 而現(xiàn)實(shí)情況是 Prometheus, Grafana, Alertmanager 再對(duì)接 Slack, 釘釘啥的, 這一套假如需要從頭搭建, 還是有一定的門檻(用 k8s 的話至少得看一下 helm chart 吧), 甚至于有些時(shí)候想搭建監(jiān)控的是全棧(gan)工程師, 作為全公司的獨(dú)苗, 很可能更多的精力需要花在跟進(jìn)前端的新技術(shù)上(不我沒有黑前端…). 這時(shí)候, 一個(gè)一鍵拉起整套監(jiān)控系統(tǒng)的命令誘惑力是非常大的.
要一鍵拉起整套監(jiān)控棧, 首先 kubernetes 就不考慮了, 能無痛部署生產(chǎn)級(jí) kubernetes 集群的大佬不會(huì)需要這樣的命令. 這時(shí)候, 反倒涼透的 docker-compose 是一個(gè)很好的選擇. 還是以阿里云 exporter 為例, 倉(cāng)庫(kù)提供的 docker-compose stack 里提供了 Prometheus, aliyun-exporter, Grafana(看板), Alertmanager(發(fā)警報(bào)), alertmanager-dingtalk-webhook(適配 alertmanager 的警報(bào)到釘釘機(jī)器人) 的一鍵部署并且警報(bào)規(guī)則和 Grafana 看板頁(yè)一并配置完畢. 這么一來, 只要用戶有一臺(tái)裝了 docker 的機(jī)器, 他就能在5分鐘之內(nèi)打開 Grafana 看到這些效果(還有釘釘警報(bào)…假如這位用戶的服務(wù)器不太健康的話):
當(dāng)然了, 想要穩(wěn)固地部署這套架構(gòu), 還是需要多機(jī)做高可用或者直接扔到 k8s, swarm 這樣的編排系統(tǒng)上. 但假如沒有”一鍵部署”的存在, 很多對(duì) Prometheus 生態(tài)不熟悉的開發(fā)者就會(huì)被拒之門外; 另外, 對(duì)于有經(jīng)驗(yàn)的用戶, “一鍵部署”也能幫助他們快速理解這個(gè) exporter 的特性, 幫助他們判斷是否需要啟用這個(gè)組件.
結(jié)語(yǔ)
你可能已經(jīng)看出來了, 這篇文章的本意是打廣告(當(dāng)然, 我已經(jīng)非常努力地寫了我所認(rèn)為的”干貨”!). aliyun-exporter 這個(gè)項(xiàng)目其實(shí)最開始只是我練習(xí) Python 用的, 但在前幾天碰到一位用戶告訴我他們?cè)谏a(chǎn)中使用了這個(gè)項(xiàng)目, 這給了莫大的鼓舞, 正好我還沒有在公開場(chǎng)合 Promote 過這個(gè)項(xiàng)目, 因此這周就撈一把, 希望項(xiàng)目本身或這些衍生出來的經(jīng)驗(yàn)中有一樣能幫到大家吧.
都看到這了, 不如點(diǎn)個(gè) star?