編寫 Exporter
如果您正在為自己的程式碼進行插樁,應該遵循如何使用 Prometheus 客戶端庫為程式碼插樁的一般規則。但是,當從另一個監控或插樁系統獲取指標時,情況往往沒有那麼黑白分明。
本文件包含編寫 Exporter 或自定義收集器(collector)時應考慮的事項。其中涵蓋的理論對於進行直接插樁的人員也會有所幫助。
如果您在編寫 Exporter 時對本文件中的任何內容存有疑問,請透過 IRC(libera 上的 #prometheus)或郵件列表與我們聯絡。
可維護性與純淨度
編寫 Exporter 時,您需要做出的主要決定是您願意投入多少精力來獲取完美的指標。
如果所討論的系統只有少數幾個極少發生變化的指標,那麼做到盡善盡美是一個輕鬆的選擇,HAProxy exporter 就是一個很好的例子。
另一方面,如果一個系統擁有數百個隨著新版本釋出而頻繁變化的指標,而你仍試圖追求完美,那麼你就不得不承擔大量持續的維護工作。MySQL exporter 就屬於這一類。
node exporter 則是兩者的結合,其複雜性因模組而異。例如,mdadm 收集器手動解析檔案並暴露專為該收集器建立的指標,因此我們可以把這些指標做好。而對於 meminfo 收集器,其結果因核心版本而異,因此我們只需進行最低限度的轉換以建立有效的指標即可。
配置
在針對應用程式進行開發時,您的目標應該是讓 Exporter 無需使用者進行任何自定義配置,只需告知其應用程式的位置即可。您可能還需要提供過濾某些指標的能力,因為在大規模部署中,這些指標可能過於細粒度且消耗資源,例如 HAProxy exporter 允許過濾單臺伺服器的統計資訊。同樣,可能存在一些預設停用的高開銷指標。
在與其他監控系統、框架和協議互動時,您通常需要提供額外的配置或自定義,以生成適用於 Prometheus 的指標。在最理想的情況下,監控系統的資料模型與 Prometheus 足夠相似,您可以自動確定如何轉換指標。對於 Cloudwatch 、SNMP 和 collectd 來說就是如此。在大多數情況下,我們只需要提供讓使用者選擇想要提取哪些指標的功能。
在其他情況下,系統的指標是完全非標準的,取決於系統的使用情況和底層應用程式。在這種情況下,使用者必須告訴我們如何轉換這些指標。其中最典型的是 JMX exporter ,而 Graphite 和 StatsD Exporter 也需要配置來提取標籤。
建議確保 Exporter 開箱即用無需配置,並在需要時提供一系列用於轉換的示例配置。
YAML 是標準的 Prometheus 配置格式,預設情況下所有配置都應使用 YAML。
指標
命名
遵循關於指標命名的最佳實踐。
通常,指標名稱應該讓熟悉 Prometheus 但不熟悉特定系統的人能夠很好地猜測該指標的含義。一個名為 http_requests_total 的指標並不是特別有用——這些請求是在它們進入時測量的,還是在某個過濾器中,或者是在它們到達使用者程式碼時測量的?而 requests_total 甚至更糟,指的是什麼型別的請求?
對於直接插樁,給定的指標應該恰好存在於一個檔案中。相應地,在 Exporter 和收集器中,一個指標應該恰好適用於一個子系統並進行相應命名。
指標名稱絕不應該程式化地生成,除非是在編寫自定義收集器或 Exporter 時。
應用程式的指標名稱通常應帶有 Exporter 名稱作為字首,例如 haproxy_up。
指標必須使用基本單位(例如秒、位元組),並將它們轉換為更易讀格式的工作留給圖表工具。無論您最終使用什麼單位,指標名稱中的單位必須與所使用的單位相匹配。同樣,暴露比例(ratio)而不是百分比(percentage)。更好的是,為比例的兩個組成部分各指定一個計數器。
指標名稱不應包含它們匯出時攜帶的標籤,例如 by_type,因為如果該標籤被聚合掉,該名稱就失去意義了。
唯一的例外是當您透過多個指標匯出具有不同標籤的相同資料時,在這種情況下,這通常是區分它們最理智的方法。對於直接插樁,只有在匯出帶有所有標籤的單個指標會導致基數過高時,才應出現這種情況。
Prometheus 指標和標籤名稱採用 snake_case(蛇形命名法)書寫。將 camelCase(駝峰命名法)轉換為 snake_case 是理想的,儘管自動轉換對於類似 myTCPExample 或 isNaN 的名稱並不總是產生好的結果,因此有時最好保持原樣。
暴露的指標不應包含冒號,冒號保留給使用者定義的記錄規則在聚合時使用。
指標名稱中只有 [a-zA-Z0-9:_] 是有效的。
字尾 _sum、_count、_bucket 和 _total 被 Summary、Histogram 和 Counter 使用。除非您正在生成其中之一,否則請避免使用這些字尾。
_total 是 Counter 的約定字尾,如果您使用的是 COUNTER 型別,則應使用它。
process_ 和 scrape_ 字首是保留的。如果您的字首遵循匹配的語義,可以在這些字首上新增您自己的字首。例如,Prometheus 使用 scrape_duration_seconds 表示抓取耗時,一個好的做法是同時擁有一個以 Exporter 為中心的指標,例如 jmx_scrape_duration_seconds,表示該特定 Exporter 執行其操作所花費的時間。對於您可以訪問 PID 的程序統計資訊,Go 和 Python 都提供了可以為您處理此問題的收集器。一個很好的例子是 HAProxy exporter 。
當您有成功請求計數和失敗請求計數時,暴露此資訊的最佳方式是將其作為一個表示總請求數的指標和另一個表示失敗請求數的指標。這使得計算失敗率變得容易。不要使用一個帶有失敗或成功標籤的單一指標。同樣,對於快取的命中(hit)或未命中(miss),最好一個指標表示總數,另一個指標表示命中數。
考慮使用監控的人對指標名稱進行程式碼或網頁搜尋的可能性。如果這些名稱非常確立且不太可能在熟悉這些名稱的人群範圍之外使用(例如 SNMP 和網路工程師),那麼保持它們原樣可能是一個好主意。這一邏輯並不適用於所有 Exporter,例如 MySQL exporter 的指標可能被各種人員使用,而不僅僅是 DBA。帶有原始名稱的 HELP 字串可以提供與使用原始名稱幾乎相同的好處。
標籤
閱讀關於標籤的一般建議。
避免使用 type 作為標籤名稱,它太通用了,而且通常沒有意義。您還應該儘可能避免使用可能與目標(target)標籤衝突的名稱,例如 region、zone、cluster、availability_zone、az、datacenter、dc、owner、customer、stage、service、environment 和 env。但是,如果應用程式就是這樣呼叫某些資源的,最好不要透過重新命名來引起混淆。
避免僅僅因為它們共享一個字首而試圖將它們放入同一個指標中。除非您確信某些內容作為單個指標是有意義的,否則使用多個指標會更安全。
標籤 le 對 Histogram 具有特殊含義,而 quantile 對 Summary 具有特殊含義。通常應避免使用這些標籤。
讀/寫和傳送/接收最好作為獨立的指標,而不是作為標籤。這通常是因為您一次只關心其中一個,而且以這種方式使用它們會更容易。
經驗法則是,一個指標在進行求和或取平均值時應該是有意義的。Exporter 還會遇到另一種情況,即資料從根本上是表格形式的,如果不這樣做,使用者將不得不對指標名稱執行正則表示式才能使用。考慮主機板上的電壓感測器,雖然對它們進行數學運算沒有意義,但將它們放在一個指標中比每個感測器使用一個指標更有意義。一個指標中的所有值應該(幾乎)總是具有相同的單位,例如,想象一下如果風扇速度與電壓混在一起,而你又無法自動將它們分開會怎樣。
不要這樣做
my_metric{label="a"} 1
my_metric{label="b"} 6
my_metric{label="total"} 7
或者這樣
my_metric{label="a"} 1
my_metric{label="b"} 6
my_metric{} 7
前者會導致對您的指標執行 sum() 的使用者出錯,而後者則破壞了求和操作且非常難以使用。一些客戶端庫(例如 Go)會主動嘗試阻止您在自定義收集器中執行後一種操作,並且所有客戶端庫都應該阻止您在直接插樁時執行後一種操作。絕對不要做這兩者中的任何一個,而是依賴 Prometheus 聚合。
如果您的監控暴露了像這樣的總計(total),請丟棄該總計。如果由於某種原因必須保留它,例如該總計包含未單獨計數的內容,請使用不同的指標名稱。
插樁標籤應該保持最少,每增加一個標籤,使用者在編寫 PromQL 時就需要多考慮一個。因此,應避免包含可以在不影響時間序列唯一性的情況下移除的插樁標籤。關於指標的附加資訊可以透過 info 指標新增,示例如下文介紹的如何處理版本號。
然而,在某些情況下,預計實際上該指標的所有使用者都會需要附加資訊。如果是這樣,新增一個非唯一標籤,而不是一個 info 指標,才是正確的解決方案。例如 mysqld_exporter 的 mysqld_perf_schema_events_statements_total 的 digest 標籤是完整查詢模式的雜湊值,足以保證唯一性。但是,如果沒有人類可讀的 digest_text 標籤,它的用處就不大,而對於長查詢,該標籤將只包含查詢模式的開頭,因此不是唯一的。因此我們最終同時使用了面向人類的 digest_text 標籤和用於保證唯一性的 digest 標籤。
目標標籤,而非靜態抓取的標籤
如果您發現自己想將相同的標籤應用到所有的指標上,請停下來。
這通常發生在以下兩種情況中。
第一種是對於指標非常有用的某些標籤,例如軟體的版本號。相反,應使用 https://www.robustperception.io/how-to-have-labels-for-machine-roles/ 中描述的方法。
第二種情況是當標籤實際上是目標(target)標籤時。這些是像 region、群集名稱等,它們來自您的基礎設施設定,而不是應用程式本身。一個應用程式不應該指明它在您的標籤分類體系中處於什麼位置,這是由執行 Prometheus 伺服器的人來配置的,監控同一應用程式的不同人可能會給它賦予不同的名稱。
因此,這些標籤屬於 Prometheus 的抓取配置(scrape configs),透過您使用的任何服務發現來進行。在這裡應用機器角色(machine roles)的概念也是可以的,因為這對於至少一部分拉取它的人來說可能是很有用的資訊。
型別
您應該嘗試將指標的型別與 Prometheus 的型別相匹配。這通常意味著 Counter 和 Gauge。Summary 的 _count 和 _sum 也相對常見,偶爾您還會看到分位數(quantile)。Histogram 很少見,如果您遇到一個,請記住暴露格式(exposition format)暴露的是累積值。
指標的型別通常不是很明顯,特別是當您自動處理一組指標時。一般而言,UNTYPED 是一個安全的預設值。
Counter 無法遞減,所以如果您有一個來自其他插樁系統的計數器型別可以遞減(例如 Dropwizard metrics),那麼它就不是計數器,而是一個 Gauge。在那裡,UNTYPED 可能是最適合使用的型別,因為如果將 GAUGE 用作計數器,會產生誤導。
幫助字串
當您在轉換指標時,使使用者能夠追溯原始指標是什麼以及是什麼規則觸發了該轉換,是非常有用的。在幫助字串中放入收集器或 Exporter 的名稱、應用的任何規則的 ID 以及原始指標的名稱和詳細資訊,將極大地幫助使用者。
Prometheus 不喜歡一個指標擁有不同的幫助字串。如果您是透過許多其他指標製作一個指標,請選擇其中之一放入幫助字串中。
例如,SNMP exporter 使用 OID,JMX exporter 放入一個示例 mBean 名稱。HAProxy exporter 具有手寫的字串。node exporter 也有各種各樣的例子。
丟棄用處不大的統計資料
一些插樁系統除了暴露最小值、最大值和標準差外,還會暴露 1 分鐘、5 分鐘、15 分鐘速率、自應用程式啟動以來的平均速率(例如在 Dropwizard metrics 中稱為 mean)。
這些都應該被丟棄,因為它們沒有什麼用處且增加了混亂。Prometheus 可以自己計算速率,而且通常更準確,因為暴露的平均值通常是指數衰減的。您不知道計算最小值或最大值的時間範圍,而且標準差在統計上是無用的,如果您需要計算它,您可以隨時暴露平方和、_sum 和 _count。
分位數(Quantile)也有相關的問題,您可以選擇丟棄它們或將它們放入 Summary 中。
帶點的字串
許多監控系統沒有標籤,而是像這樣操作:my.class.path.mymetric.labelvalue1.labelvalue2.labelvalue3。
Graphite 和 StatsD Exporter 共享一種使用小型配置語言轉換這些內容的方法。其他 Exporter 也應當實現相同的機制。該轉換目前僅在 Go 中實現,如果能將其剝離到一個單獨的庫中,將會大有裨益。
收集器
當為您自己的 Exporter 實現收集器時,您絕不應該使用通常的直接插樁方法,然後每次抓取時更新指標。
相反,應該每次建立新的指標。在 Go 中,這可以在您的 Collect() 方法中使用 MustNewConstMetric 來完成。對於 Python,請參閱 https://github.com/prometheus/client_python#custom-collectors ;對於 Java,請在您的 collect 方法中生成一個 List<MetricFamilySamples>,示例請參見 StandardExports.java 。
這樣做的原因有兩個。首先,兩個拉取操作可能會同時發生,而直接插樁實際上使用的是檔案級的全域性變數,因此會產生競態條件。其次,如果一個標籤值消失了,它仍會被匯出。
透過直接插樁來為您自己的 Exporter 本身進行插樁是可以的,例如 Exporter 在所有抓取中傳輸的總位元組數或執行的呼叫次數。對於諸如 blackbox exporter 和 SNMP exporter 這種不與單個目標繫結的 Exporter,這些指標應該只在常規的 /metrics 呼叫時暴露,而不應該在特定目標的抓取中暴露。
關於抓取本身的指標
有時您想匯出關於抓取本身的指標,例如抓取花費了多長時間或處理了多少條記錄。
這些指標應該作為 Gauge 暴露,因為它們是關於一個事件(即抓取)的,並且指標名稱應以 Exporter 名稱為字首,例如 jmx_scrape_duration_seconds。通常會排除 _exporter,而且如果該 Exporter 同樣適合僅作為收集器(collector)使用,那麼一定要排除它。
應避免其他關於抓取的“元(meta)”指標。例如,抓取次數的計數器或抓取耗時的直方圖。讓 Exporter 追蹤這些指標會與 Prometheus 本身自動生成的指標重複。這增加了每個 Exporter 例項的儲存成本。
機器與程序指標
許多系統(例如 Elasticsearch)暴露了機器指標,如 CPU、記憶體和檔案系統資訊。由於 node exporter 在 Prometheus 生態系統中已經提供了這些指標,因此應當丟棄此類指標。
在 Java 領域,許多插樁框架暴露了程序級別和 JVM 級別的統計資料,例如 CPU 和 GC。Java 客戶端和 JMX exporter 已經透過 DefaultExports.java 以首選形式包含了這些內容,因此這些指標也應該被丟棄。
其他語言和框架也是如此。
部署
每個 Exporter 應該恰好監控一個應用程式例項,最好直接部署在同一臺機器上並緊鄰該程式。這意味著對於您執行的每個 HAProxy,您都要執行一個 haproxy_exporter 程序。對於每個帶有 Mesos worker 的機器,您要在其上執行 Mesos exporter ,如果一臺機器同時包含兩者,還要為 master 執行另一個。
這背後的理論是,對於直接插樁,這正是您要做的事情,我們正努力在其他佈局中儘可能接近這一點。這意味著所有服務發現都是在 Prometheus 中完成的,而不是在 Exporter 中。這樣做的好處是 Prometheus 擁有所需的目標資訊,可以讓使用者使用 blackbox exporter 來探測您的服務。
存在兩個例外
第一個例外是部署在所監控的應用程式旁邊是完全沒有意義的。SNMP、blackbox 和 IPMI exporter 是這方面的主要例子。對於 IPMI 和 SNMP exporter,這些裝置通常是無法執行程式碼的黑盒(儘管如果您可以在它們上面執行 node exporter 會更好);而對於 blackbox exporter,您正在監控諸如 DNS 名稱之類的東西,那裡同樣沒有地方可以執行程式碼。在這種情況下, Prometheus 仍應執行服務發現,並將要抓取的目標傳遞過去。示例請參閱 blackbox 和 SNMP exporter。
請注意,目前僅能使用 Go、Python 和 Java 客戶端庫來編寫這種型別的 Exporter。
第二個例外是當您從某個系統的隨機例項中提取一些統計資料,並且不在乎與哪一個例項通訊時。例如,對於一組 MySQL 副本,您希望對資料執行一些業務查詢並將其匯出。使用您通常的負載均衡方法使 Exporter 與其中一個副本進行通訊是最合理的方法。
當您監控具有主節點選舉(master-election)的系統時,這並不適用,在這種情況下,您應該單獨監控每個例項,並在 Prometheus 中處理其“主節點狀態(masterness)”。這是因為主節點並不總是恰好只有一個,而且在 Prometheus 不知情的情況下更改抓取目標會導致異常。
排程
指標只應在 Prometheus 抓取它們時從應用程式拉取,Exporter 不應根據自己的定時器執行抓取。也就是說,所有的抓取都應該是同步的。
因此,您不應該為您暴露的指標設定時間戳,讓 Prometheus 來處理。如果您認為自己需要時間戳,那麼您可能實際上需要的是 Pushgateway。
如果檢索某個指標的成本特別高(例如需要一分鐘以上),則可以快取它。這應該在 HELP 字串中註明。
Prometheus 的預設抓取超時時間是 10 秒。如果可以預料到您的 Exporter 會超過此時間,您應該在使用者文件中明確說明。
推送
某些應用程式和監控系統僅推送指標,例如 StatsD、Graphite 和 collectd。
這裡有兩個需要考慮的地方。
首先,您何時使指標過期?Collectd 以及與 Graphite 通訊的程式都會定期匯出,當它們停止時,我們希望停止暴露這些指標。Collectd 包含過期時間,所以我們使用它;Graphite 則沒有,因此這在 Exporter 上是一個配置引數(flag)。
StatsD 有點不同,因為它處理的是事件而不是指標。最好的模式是在每個應用程式旁邊執行一個 Exporter,並在應用程式重啟時將其重啟,以便清除狀態。
其次,此類系統往往允許您的使用者傳送增量(delta)或原始計數器(raw counter)。您應該儘可能依賴原始計數器,因為這是 Prometheus 的通用模型。
對於服務級別的指標(例如服務級的批處理作業),您應該讓您的 Exporter 將指標推送到 Pushgateway 並在事件結束後退出,而不是自己處理狀態。對於例項級別的批處理指標,目前還沒有明確的模式。可供選擇的方案包括:濫用 node exporter 的 textfile 收集器,依賴記憶體中狀態(如果不需要在重啟後持久化,這可能是最好的),或者實現類似於 textfile 收集器的功能。
抓取失敗
針對您與之通訊的應用程式沒有響應或出現其他問題導致抓取失敗的情況,目前有兩種模式。
第一種是返回 5xx 錯誤。
第二種方法是提供一個 myexporter_up 變數(例如 haproxy_up),其值為 0 或 1,具體取決於抓取(scrape)是否成功。
後者更適用於即使抓取失敗也仍能獲取一些有用指標的情況,例如 HAProxy exporter 仍能提供程序統計資料。前者對使用者來說處理起來稍微容易一些,因為 up 會以常規方式工作,儘管您無法區分是 exporter 宕機還是應用程式宕機。
落地頁
如果訪問 http://yourexporter/ 時能呈現一個簡單的 HTML 頁面,其中包含該 exporter 的名稱以及指向 /metrics 頁面的連結,對使用者來說會更加友好。
埠號
使用者可能會在同一臺機器上執行許多 exporter 和 Prometheus 元件,因此為了方便起見,每個元件都應有一個唯一的埠號。
我們在 https://github.com/prometheus/prometheus/wiki/Default-port-allocations 記錄它們,該頁面是公開可編輯的。
在開發您的 exporter 時,可以隨時佔用下一個空閒的埠號,最好是在公開宣佈之前。如果您還沒有準備好釋出,可以先填寫您的使用者名稱和 WIP(開發中)。
這是一個旨在讓我們的使用者使用起來更輕鬆的登錄檔,而不是開發特定 exporter 的承諾。對於內部應用程式的 exporter,我們建議使用預設埠分配範圍之外的埠。
釋出
一旦您準備好向全世界釋出您的 exporter,請給郵件列表傳送郵件,並透過編輯這個 GitHub 倉庫檔案 來提交 PR,將其新增到可用 exporter 列表中。