如何编写一个简单的Prometheus exporter

从零编写一个prometheus exporter

背景

最近需要根据获取到的一个http指标去进行比例的监控。分析了下需求,发现实 现一个exporter会比较好。http指标也是需要用go程序获取的,于是开始研究使 用go 编写一个自定义的Prometheus exporter。

思路

首先查阅了官方文档prometheus go application,发现极其简单, 使用的是go func()方式去

后来看了下gauge的方式,到了go版本的文档, 发现一个问题,就是示例中的Inc无法自增

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

import (
	"log"
	"net/http"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

type metrics struct {
	cpuTemp  prometheus.Gauge
	hdFailures *prometheus.CounterVec
}

func NewMetrics(reg prometheus.Registerer) *metrics {
	m := &metrics{
		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"},
		),
	}
	reg.MustRegister(m.cpuTemp)
	reg.MustRegister(m.hdFailures)
	return m
}

func main() {
	// Create a non-global registry.
	reg := prometheus.NewRegistry()

	// Create new metrics and register them using the custom registry.
	m := NewMetrics(reg)
	// Set values for the new created metrics.
	m.cpuTemp.Set(65.3)
	m.hdFailures.With(prometheus.Labels{"device":"/dev/sda"}).Inc()

	// Expose metrics and custom registry via an HTTP server
	// using the HandleFor function. "/metrics" is the usual endpoint for that.
	http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}))
	log.Fatal(http.ListenAndServe(":8080", nil))
}

研究了挺多办法,最后我还是放弃了,准备用exporter的正规方式collect方式 去编写。

建议阅读官方文档和github的源码 https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Collector

几个exporter的代码都可以借鉴一下 https://github.com/treydock/ssh_exporter/blob/master/collector/collector.go https://github.com/Lusitaniae/apache_exporter/blob/master/collector/collector.go

这些exporter的入口都差不多,所以仿照写了一下, https://github.com/liuliancao/demo-prometheus-exporter

简单说下,几个重要的点

  • 在prometheus里面,prometheus.MustRegister()表示一定会把这个数据注册 到采集器里面,当然你也可以MustRegister一个数据
  • 在prometheus里面有多种数据类型,比如Counter, Gauge等,具体你可以去 https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#pkg-types 去查看
  • 为什么echo出来一个metrics 比如aaa{test_label="xxx"} 3.5? 也可以做,但是当我们需要采集的指标越来越多的时候,这样会些微影响可读 性,并且无法充分利用prometheus的数据结构。
  • collector怎么编写 collector采集器需要定义采集的数据 collector定义:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
type Exporter struct {
	  host       string

	  version   *prometheus.Desc
	  total     *prometheus.GaugeVec
	  available *prometheus.GaugeVec
	  logger    log.Logger
  }

  func NewExporter(logger log.Logger, config *Config) *Exporter {
	  return &Exporter{
		  host:       config.host,
		  logger:     logger,
		  version: prometheus.NewDesc(
			  prometheus.BuildFQName(namespace, "", "version"),
			  "demo exporter version",
			  nil,
			  nil),
		  total: prometheus.NewGaugeVec(
			  prometheus.GaugeOpts{
				  Namespace: namespace,
				  Name:      "total",
				  Help:      "demo total",
			  },
			  []string{"region_id", "instance_id"},
		  ), // 定义gauge类型带标签region_id, instance_id的数据结构
	  }

collector的describe和collect方法编写,一个collector都需要这两种方法, 否则会报错。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
	  ch <- e.version
	  e.total.Describe(ch)
  }

  func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
	  ch <- prometheus.MustNewConstMetric(e.version, prometheus.GaugeValue, 0.8)
	  e.total.Reset()

	  start++
	  e.total.With(prometheus.Labels{"region_id": "region1", "instance_id": "instance1"}).Set(float64(start))
	  e.total.Collect(ch)
  }

我这边理解是每个采集值都要在describe和collect体现。

对于有标签,动态的gauge的,需要通过With().Set()方法更改 并且需要增加 e.total.Collect(ch) 这样的方法去更新。

这样执行的结果就是demo_total这个值每次都会+1,从2开始。