使用golang編寫Prometheus Exporter

來源:互聯網
上載者:User

Exporter是基於Prometheus實施的監控系統中重要的組成部分,承擔資料指標的採集工作,官方的exporter列表中已經包含了常見的絕大多數的系統指標監控,比如用於機器效能監控的node_exporter, 用於網路裝置監控的snmp_exporter等等。這些已有的exporter對於監控來說,僅僅需要很少的配置工作就能提供完善的資料指標採集。

有時我們需要自己去寫一些與商務邏輯比較相關的指標監控,這些指標無法通過常見的exporter擷取到。比如我們需要提供對於DNS解析情況的整體監控,瞭解如何編寫exporter對於業務監控很重要,也是完善監控系統需要經曆的一個階段。接下來我們就介紹如何編寫exporter, 本篇內容編寫的語言為golang, 官方也提供了python, java等其他的語言實現的庫,採集方式其實大同小異。 搭建環境

首先確保機器上安裝了go語言(1.7版本以上),並設定好了對應的GOPATH。接下來我們就可以開始編寫代碼了。以下是一個簡單的exporter

下載對應的prometheus包

go get github.com/prometheus/client_golang/prometheus/promhttp

程式主函數:

package mainimport (    "log"    "net/http"    "github.com/prometheus/client_golang/prometheus/promhttp")func main() {    http.Handle("/metrics", promhttp.Handler())    log.Fatal(http.ListenAndServe(":8080", nil))}

這個代碼中我們僅僅通過http模組指定了一個路徑,並將client_golang庫中的promhttp.Handler()作為處理函數傳遞進去後,就可以擷取指標資訊了,兩行代碼實現了一個exporter。這裡內部其實是使用了一個預設的收集器將通過NewGoCollector採集當前Go運行時的相關資訊比如go堆棧使用,goroutine的資料等等。 通過訪問http://localhost:8080/metrics即可查看詳細的指標參數。

上面的代碼僅僅展示了一個預設的採集器,並且通過介面調用隱藏了太多實施細節,對於下一步開發並沒什麼作用,為了實現自訂的監控我們需要先瞭解一些基本概念。 指標類別

Prometheus中主要使用的四類指標類型,如下所示
- Counter (累加指標)
- Gauge (測量指標)
- Summary (概略圖)
- Histogram (長條圖)

Counter 一個累加指標資料,這個值隨著時間只會逐漸的增加,比如程式完成的總任務數量,運行錯誤發生的總次數。常見的還有交換器中snmp採集的資料流量也屬於該類型,代表了持續增加的資料包或者傳輸位元組累加值。

Gauge代表了採集的一個單一資料,這個資料可以增加也可以減少,比如CPU使用方式,記憶體使用量量,硬碟當前的空間容量等等

Histogram和Summary使用的頻率較少,兩種都是基於採樣的方式。另外有一些庫對於這兩個指標的使用和支援程度不同,有些僅僅實現了部分功能。這兩個類型對於某一些業務需求可能比較常見,比如查詢單位時間內:總的回應時間低於300ms的佔比,或者查詢95%使用者查詢的門限值對應的回應時間是多少。 使用Histogram和Summary指標的時候同時會產生多組資料,_count代表了採樣的總數,_sum則代表採樣值的和。 _bucket則代表了落入此範圍的資料。

下面是使用historam來定義的一組指標,計算出了平均五分鐘內的查詢請求小於0.3s的請求佔比總量的比例值。

  sum(rate(http_request_duration_seconds_bucket{le="0.3"}[5m])) by (job)/  sum(rate(http_request_duration_seconds_count[5m])) by (job)

如果需要彙總資料,可以使用histogram. 並且如果對於分布範圍有明確的值的情況下(比如300ms),也可以使用histogram。但是如果僅僅是一個百分比的值(比如上面的95%),則使用Summary 定義指標

這裡我們需要引入另一個依賴庫

go get github.com/prometheus/client_golang/prometheus

下面先來定義了兩個指標資料,一個是Guage類型, 一個是Counter類型。分別代表了CPU溫度和磁碟失敗次數統計,使用上面的定義進行分類。

    cpuTemp = prometheus.NewGauge(prometheus.GaugeOpts{        Name: "cpu_temperature_celsius",        Help: "Current temperature of the CPU.",    })    hdFailures = prometheus.NewCounterVec(        prometheus.CounterOpts{            Name: "hd_errors_total",            Help: "Number of hard-disk errors.",        },        []string{"device"},    )

這裡還可以註冊其他的參數,比如上面的磁碟失敗次數統計上,我們可以同時傳遞一個device裝置名稱進去,這樣我們採集的時候就可以獲得多個不同的指標。每個指標對應了一個裝置的磁碟失敗次數統計。 註冊指標

func init() {    // Metrics have to be registered to be exposed:    prometheus.MustRegister(cpuTemp)    prometheus.MustRegister(hdFailures)}

使用prometheus.MustRegister是將資料直接註冊到Default Registry,就像上面的啟動並執行例子一樣,這個Default Registry不需要額外的任何代碼就可以將指標傳遞出去。註冊後既可以在程式層面上去使用該指標了,這裡我們使用之前定義的指標提供的API(Set和With().Inc)去改變指標的資料內容

func main() {    cpuTemp.Set(65.3)    hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc()    // The Handler function provides a default handler to expose metrics    // via an HTTP server. "/metrics" is the usual endpoint for that.    http.Handle("/metrics", promhttp.Handler())    log.Fatal(http.ListenAndServe(":8080", nil))}

其中With函數是傳遞到之前定義的label=”device”上的值,也就是產生指標類似於

cpu_temperature_celsius 65.3hd_errors_total{"device"="/dev/sda"} 1

當然我們寫在main函數中的方式是有問題的,這樣這個指標僅僅改變了一次,不會隨著我們下次採集資料的時候發生任何變化,我們希望的是每次執行採集的時候,程式都去自動的抓取指標並將資料通過http的方式傳遞給我們。 Counter資料擷取執行個體

下面是一個採集Counter類型資料的執行個體,這個例子中實現了一個自訂的,滿足採集器(Collector)介面的結構體,並手動註冊該結構體後,使其每次查詢的時候自動執行採集任務。

我們先來看下採集器Collector介面的實現

type Collector interface {    // 用於傳遞所有可能的指標的定義描述符    // 可以在程式運行期間添加新的描述,收集新的指標資訊    // 重複的描述符將被忽略。兩個不同的Collector不要設定相同的描述符    Describe(chan<- *Desc)    // Prometheus的註冊器調用Collect執行實際的抓取參數的工作,    // 並將收集的資料傳遞到Channel中返回    // 收集的指標資訊來自於Describe中傳遞,可以並發的執行抓取工作,但是必須要保證線程的安全。    Collect(chan<- Metric)}

瞭解了介面的實現後,我們就可以寫自己的實現了,先定義結構體,這是一個叢集的指標採集器,每個叢集都有自己的Zone,代表叢集的名稱。另外兩個是儲存的採集的指標。

type ClusterManager struct {    Zone         string    OOMCountDesc *prometheus.Desc    RAMUsageDesc *prometheus.Desc}

我們來實現一個採集工作,放到了ReallyExpensiveAssessmentOfTheSystemState函數中實現,每次執行的時候,返回一個按照主機名稱作為鍵採集到的資料,兩個傳回值分別代表了OOM錯誤計數,和RAM使用指標資訊。

func (c *ClusterManager) ReallyExpensiveAssessmentOfTheSystemState() (    oomCountByHost map[string]int, ramUsageByHost map[string]float64,) {    oomCountByHost = map[string]int{        "foo.example.org": int(rand.Int31n(1000)),        "bar.example.org": int(rand.Int31n(1000)),    }    ramUsageByHost = map[string]float64{        "foo.example.org": rand.Float64() * 100,        "bar.example.org": rand.Float64() * 100,    }    return}

實現Describe介面,傳遞指標描述符到channel

// Describe simply sends the two Descs in the struct to the channel.func (c *ClusterManager) Describe(ch chan<- *prometheus.Desc) {    ch <- c.OOMCountDesc    ch <- c.RAMUsageDesc}

Collect函數將執行抓取函數並返回資料,返回的資料傳遞到channel中,並且傳遞的同時綁定原先的指標描述符。以及指標的類型(一個Counter和一個Guage)

func (c *ClusterManager) Collect(ch chan<- prometheus.Metric) {    oomCountByHost, ramUsageByHost := c.ReallyExpensiveAssessmentOfTheSystemState()    for host, oomCount := range oomCountByHost {        ch <- prometheus.MustNewConstMetric(            c.OOMCountDesc,            prometheus.CounterValue,            float64(oomCount),            host,        )    }    for host, ramUsage := range ramUsageByHost {        ch <- prometheus.MustNewConstMetric(            c.RAMUsageDesc,            prometheus.GaugeValue,            ramUsage,            host,        )    }}

建立結構體及對應的指標資訊,NewDesc參數第一個為指標的名稱,第二個為協助資訊,顯示在指標的上面作為注釋,第三個是定義的label名稱數組,第四個是定義的Labels

func NewClusterManager(zone string) *ClusterManager {    return &ClusterManager{        Zone: zone,        OOMCountDesc: prometheus.NewDesc(            "clustermanager_oom_crashes_total",            "Number of OOM crashes.",            []string{"host"},            prometheus.Labels{"zone": zone},        ),        RAMUsageDesc: prometheus.NewDesc(            "clustermanager_ram_usage_bytes",            "RAM usage as reported to the cluster manager.",            []string{"host"},            prometheus.Labels{"zone": zone},        ),    }}

執行主程式

func main() {    workerDB := NewClusterManager("db")    workerCA := NewClusterManager("ca")    // Since we are dealing with custom Collector implementations, it might    // be a good idea to try it out with a pedantic registry.    reg := prometheus.NewPedanticRegistry()    reg.MustRegister(workerDB)    reg.MustRegister(workerCA)}

如果直接執行上面的參數的話,不會擷取任何的參數,因為程式將自動推出,我們並未定義http介面去暴露資料出來,因此資料在執行的時候還需要定義一個httphandler來處理http請求。

添加下面的代碼到main函數後面,即可實現資料傳遞到http介面上:

    gatherers := prometheus.Gatherers{        prometheus.DefaultGatherer,        reg,    }    h := promhttp.HandlerFor(gatherers,        promhttp.HandlerOpts{            ErrorLog:      log.NewErrorLogger(),            ErrorHandling: promhttp.ContinueOnError,        })    http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {        h.ServeHTTP(w, r)    })    log.Infoln("Start server at :8080")    if err := http.ListenAndServe(":8080", nil); err != nil {        log.Errorf("Error occur when start server %v", err)        os.Exit(1)    }

其中prometheus.Gatherers用來定義一個採集資料的收集器集合,可以merge多個不同的採集資料到一個結果集合,這裡我們傳遞了預設的DefaultGatherer,所以他在輸出中也會包含go運行時指標資訊。同時包含reg是我們之前產生的一個註冊對象,用來自訂採集資料。

promhttp.HandlerFor()函數傳遞之前的Gatherers對象,並返回一個httpHandler對象,這個httpHandler對象可以調用其自身的ServHTTP函數來接手http請求,並返迴響應。其中promhttp.HandlerOpts定義了採集過程中如果發生錯誤時,繼續採集其他的資料。

嘗試重新整理幾次瀏覽器擷取最新的指標資訊

clustermanager_oom_crashes_total{host="bar.example.org",zone="ca"} 364clustermanager_oom_crashes_total{host="bar.example.org",zone="db"} 90clustermanager_oom_crashes_total{host="foo.example.org",zone="ca"} 844clustermanager_oom_crashes_total{host="foo.example.org",zone="db"} 801# HELP clustermanager_ram_usage_bytes RAM usage as reported to the cluster manager.# TYPE clustermanager_ram_usage_bytes gaugeclustermanager_ram_usage_bytes{host="bar.example.org",zone="ca"} 10.738111282075208clustermanager_ram_usage_bytes{host="bar.example.org",zone="db"} 19.003276633920805clustermanager_ram_usage_bytes{host="foo.example.org",zone="ca"} 79.72085409108028clustermanager_ram_usage_bytes{host="foo.example.org",zone="db"} 13.041384617379178

每次重新整理的時候,我們都會獲得不同的資料,類似於實現了一個數值不斷改變的採集器。當然,具體的指標和採集函數還需要按照需求進行修改,滿足實際的業務需求。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.