自行创建的 Prometheus exporter
首先
今天是品川降临日历2019年的第22天。
普罗米修斯是众所周知的一种监控软件,它可以访问受监视对象并收集数据,以进行监视和通知。受监视对象可以是 exporter,它从数据中获取响应并发送给普罗米修斯,还可以是批处理、应用程序等通过 Push 发送的数据,这些数据将被存储并准备好 PushGateway,然后响应给普罗米修斯。
exporter 包括官方和非官方版本,并且有多个可供使用。同时,也有为了自行创建 exporter 的客户端库可供使用。
虽然目前没有制作自己的 exporter 的机会,但为了未来的某一天,我想利用现有的客户端库尝试自己动手制作一个。
获取度量
暂时创建一个用于获取Linux内存使用率的导出器。
对于Linux,可以通过查看/proc/meminfo文件来了解内存使用率,因此我们将设计它以读取此文件。
我试着自己做。
hirano00o/sample-exporter:Prometheus的样本导出器
在创建过程中,参考了客户端库的示例和node_exporter。
在node_exporter中,我们过去通过参数指定要获取的信息并进行过滤,但这次不会使用与获取信息有关的参数,所以变成了这样。
func main() {
flag.Parse()
c, err := collector.NewSampleCollector()
if err != nil {
log.Fatal(err)
}
// NewしたCollectorを登録する。ここに登録したもののメトリクスが取得できる
prometheus.MustRegister(c)
http.Handle("/metrics", promhttp.Handler())
log.Println("Listening on ", *addr)
if err := http.ListenAndServe(*addr, nil); err != nil {
log.Fatal(err)
}
}
collect.go 是一个基础框架,可以让您在需要获取除了内存以外的信息时轻松添加。
var (
...
factories = make(map[string]func() (Collector, error))
collectorState = make(map[string]int)
)
// memory.goでinit()で呼ぶ
func registCollector(collector string, f func() (Collector, error)) {
factories[collector] = f
collectorState[collector] = 0
}
type SampleCollector struct {
Collectors map[string]Collector // keyがstring型, valueはCollectorと言う名のinterface型
}
// memory.goでも利用するし、メモリ以外にもCPU等取得情報を追加したいときに備えて、interfaceで定義しておく
type Collector interface {
Update(ch chan<- prometheus.Metric) error
}
func NewSampleCollector() (*SampleCollector, error) {
collectors := make(map[string]Collector)
for k, _ := range collectorState {
f, err := factories[k]()
if err != nil {
return nil, err
}
collectors[k] = f
}
// 今回はメモリだけだが、CPU等の他情報の取得も簡単に追加できる
return &SampleCollector{Collectors: collectors}, nil
}
// Describe と Collect は、Collector interface に実装されている
// https://godoc.org/github.com/prometheus/client_golang/prometheus#Collector
func (sc SampleCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- scrapeDurationDesc
ch <- scrapeSuccessDesc
}
// goroutine で複数の(今回はメモリだけだが)情報を並列に取得(execute)
func (sc SampleCollector) Collect(ch chan<- prometheus.Metric) {
wg := sync.WaitGroup{}
wg.Add(len(sc.Collectors))
for name, c := range sc.Collectors {
go func(name string, c Collector) {
execute(name, c, ch)
wg.Done()
}(name, c)
}
wg.Wait()
}
func execute(name string, c Collector, ch chan<- prometheus.Metric) {
begin := time.Now()
err := c.Update(ch)
duration := time.Since(begin)
var success float64
if err != nil {
log.Printf("ERROR: %s collector failed after %fs: %s", name, duration.Seconds(), err.Error())
success = 0
}
success = 1
// 集計したい情報は chan に Metric を渡す
ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, duration.Seconds(), name)
ch <- prometheus.MustNewConstMetric(scrapeSuccessDesc, prometheus.GaugeValue, success, name)
}
在node_exporter中,获取内存信息是通过解析/proc/meminfo文件来完成的,而这次我们使用了以下的库:shirou/gopsutil: 用于Go语言的psutil。
const (
subsystem = "memory"
)
type memoryCollector struct{}
// MustRegister するために、init() で collect.go の factories に追加する
func init() {
registCollector(subsystem, NewMemoryCollector)
}
func NewMemoryCollector() (Collector, error) {
return &memoryCollector{}, nil
}
func (c *memoryCollector) Update(ch chan<- prometheus.Metric) error {
var metricType prometheus.ValueType
// メモリ情報の取得
v, err := mem.VirtualMemory()
if err != nil {
return fmt.Errorf("could not get memory info: %s", err)
}
...
for i := 0; i < t.NumField(); i++ {
...
if strings.Contains(t.Field(i).Name, "Total") == true {
// Total (例えばMemTotal)が入っていたら、Counter
// メトリクスの種類は https://prometheus.io/docs/concepts/metric_types/
metricType = prometheus.CounterValue
} else {
metricType = prometheus.GaugeValue
}
...
// 集計したい情報なので chan に Metric を渡す
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
// メトリクス名を BuildFQName() で作成し、指定
prometheus.BuildFQName(namespace, subsystem, t.Field(i).Name),
fmt.Sprintf("Memory information filed %s", t.Field(i).Name),
nil, nil,
),
// メトリクスタイプと値を指定、値はfloat64
metricType, f64,
)
}
return nil
}
结局
由于长度较长,我只输出了部分内容,但看起来成功获取了。
$ curl http://localhost:9090/metrics | grep -i "sample_memory_UsedPercent"
# HELP sample_memory_UsedPercent Memory information filed UsedPercent
# TYPE sample_memory_UsedPercent counter
sample_memory_UsedPercent 1.9555757729632135
$ curl http://localhost:9090/metrics | grep -i "sample_memory_UsedPercent"
# HELP sample_memory_UsedPercent Memory information filed UsedPercent
# TYPE sample_memory_UsedPercent counter
sample_memory_UsedPercent 1.9468305687203793
最后一句
结果尽管变成了类似于node_exporter的劣质版本,但意外地很容易就能自己制作出一款exporter。
举个例子,在公司里可以制作一款通用且易于使用的exporter,并进行分发,暂时安装就可以。这样在grafana进行长期稳定性测试时很方便的。
Note: The meaning of “node_exporter” and “exporter” may require further clarification for a more accurate paraphrase.
结果把劣化的node_exporter版本做出来了,但制作exporter事实上很容易。例子,公司通用的简单使用的exporter制作发送,就是暂时OK的形式。这样稳定测试时在grafana上看起来很方便。