用Go语言从零开始开发一个Prometheus Exporter

用Go语言从零开始开发一个Prometheus Exporter

singless
云计算从业者,DevOps践行者
35篇原创内容

 

1 prometheus介绍

Prometheus 是一个开源的系统监控和报警工具,专为高效采集和存储时间序列数据而设计,目前广泛用于监控微服务架构、云基础设施和Kubernetes集群等环境。Prometheus的架构图如下。

图片

 

 

exporter可以理解为prometheus的客户端,作用是把从节点 ( 操作系统或应用服务或存储等) 中采集到的监控数据作为Metrics指标,再将Metrics指标以HTTP的方式暴露给Prometheus。常用的exporter有node exporter、mysql exporter等。

2 metrics介绍

在Prometheus中,"metrics"(度量)是指系统或应用程序的性能数据或状态数据,这些数据以时间序列的形式存储,并用于监控和分析。Prometheus 的 metrics 数据模型是基于时间序列(time series)的,每个时间序列由以下几个部分组成:

  • 指标名称 (Metric Name): 描述指标的名称。

  • 标签 (Labels): 可选,一组键值对,用于标识同一指标的不同实例。

  • 时间戳 (Timestamp): 可选的 Unix 时间戳,表示指标记录的时间点。

  • 值 (Value): 指标的数值。

Metrics的数据模型如下图所示

图片

3 metrics的类型

 

Prometheus的client libraries中目前提供了四种核心的Metrics类型Counter、Gauge、Histogram和Summary

3.1 Counter类型

 

对于只会增加的数据,可以使用counter类型。counter是一个只增不减的计数器,除非重置,反映的是线性数据。它的值通常从零开始累加,例如可以用来表示promhttp_metric_handler_requests_total (/metrics的总请求数) 的值。

图片

3.2 Gauge类型

对于会增加也会减少的数据,可以使用Gauge类型。Gauge用来表示可增可减的数据,反映的是动态变化的数据。例如memory_free_bytes (节点剩余可用内存数)。

图片

3.3 Histogram类型

Histogram测量离散事件的分布。常见例子是 HTTP 请求的延迟、函数运行时或 I/O 请求大小。Histogram统计了指标的时序流历史样本点,输出在对应区间内样本点的数量。

图片

3.4 Summary类型

Summary也是测量离散事件的分布,不过引入了分位数的概念,能够输出指标的时序流历史中,指定分位数后的统计值。

图片

4 使用go开发一个exporter

尽管官方和第三方已经提供了很多实用的exporter,但有时候我们仍然需要自己去开发exporter,因为自己的Prometheus Exporter可以根据应用和业务的特定需求来自定义和精细化监控指标,确保监控更准确。并且还能整合多种数据源,并灵活调整监控内容,适应复杂和变化的环境。开发步骤如下
  • 根据开发语言下载好prometheus client库
  • 创建metrics指标
  • 给metrics指标赋值
  • 注册metrics指标
  • 通过http暴露metics指标

先来一个例子
package main

import (
    "net/http"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
    "log"
)

// 定义自定义指标
var (
    httpRequestsTotal = prometheus.NewCounter(prometheus.CounterOpts{
        Name: "http_requests_total",
        Help: "Total number of HTTP requests.",
    })
    currentMemoryUsage = prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "current_memory_usage_bytes",
        Help: "Current memory usage in bytes.",
    })
)

func init() {
    // 注册自定义指标
    prometheus.MustRegister(httpRequestsTotal)
    prometheus.MustRegister(currentMemoryUsage)
}

func main() {
    // 增加计数器
    httpRequestsTotal.Inc()

    // 设置仪表的值
    currentMemoryUsage.Set(512.0)

    // 暴露metrics
    http.Handle("/metrics", promhttp.Handler())
    log.Println("Beginning to serve on port :8080")
    log.Fatal(http.ListenAndServe(":8080", nil))
}

运行代码,访问http://localhost:8080/metrics,可以查看到如下结果

 

# HELP current_memory_usage_bytes Current memory usage in bytes.
# TYPE current_memory_usage_bytes gauge
current_memory_usage_bytes 512
# HELP http_requests_total Total number of HTTP requests.
# TYPE http_requests_total counter
http_requests_total 1

 

4.1 准备工作

先新建一个文件夹singless_exporter,再将go模块的安装源修改为国内源。进入singless_exporter文件夹,安装prometheus client相关库。go不能采用太老的版本,否则有些库或模块可能无法使用。

图片

检查模块是否已成功安装

图片

 

4.2 自定义指标

以下通过四段代码演示如何自定义Counter、Gauge、Histogram和Summary四种Metrics指标
  • counter
package main

import (
 "fmt"
 "net/http"
 "time"

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

func main() {
 // 创建一个 Counter 指标
 counter := prometheus.NewCounter(prometheus.CounterOpts{
  Name: "requests_total",
  Help: "Total number of requests",
 })

 // 注册指标
 prometheus.MustRegister(counter)

 // 给指标赋值。模拟请求,每秒递增计数器
 go func() {
  for {
   counter.Inc()
   time.Sleep(time.Second)
  }
 }()

 // 通过http暴露指标
 http.Handle("/metrics", promhttp.Handler())
 fmt.Println("Counter exporter is running on :8080/metrics")
 http.ListenAndServe(":8080", nil)
}
运行代码检查指标

图片

  • Gauge
package main

import (
        "fmt"
        "net/http"
        "math/rand"
        "time"

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

func main() {
        // 创建一个 Gauge 指标
        gauge := prometheus.NewGauge(prometheus.GaugeOpts{
                Name: "temperature_celsius",
                Help: "Current temperature in Celsius",
        })

        // 注册指标
        prometheus.MustRegister(gauge)

        // 模拟温度变化,每秒随机变化温度
        go func() {
                for {
                        // 模拟随机温度值
                        temperature := 20.0 + 10.0*rand.Float64()
                        gauge.Set(temperature)
                        time.Sleep(time.Second)
                }
        }()

        // 暴露指标
        http.Handle("/metrics", promhttp.Handler())
        fmt.Println("Gauge exporter is running on :8080/metrics")
        http.ListenAndServe(":8080", nil)
}
运行代码查看效果

图片

  • Histogram
package main

import (
 "fmt"
 "net/http"
 "time"
    "math/rand"

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

func main() {
 // 创建一个 Histogram 指标
 histogram := prometheus.NewHistogram(prometheus.HistogramOpts{
  Name:    "request_duration_seconds",
  Help:    "Duration of requests in seconds",
  Buckets: prometheus.DefBuckets, // 默认桶设置
 })

 // 注册指标
 prometheus.MustRegister(histogram)

 // 模拟请求,每秒随机生成请求持续时间并观察直方图
 go func() {
  for {
   duration := time.Duration(rand.Intn(100)) * time.Millisecond
   histogram.Observe(duration.Seconds())
   time.Sleep(time.Second)
  }
 }()

 // 暴露指标
 http.Handle("/metrics", promhttp.Handler())
 fmt.Println("Histogram exporter is running on :8080/metrics")
 http.ListenAndServe(":8080", nil)
}
运行代码查看效果

图片

  • Summary
package main

import (
 "fmt"
 "net/http"
 "time"
    "math/rand"

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

func main() {
 // 创建一个 Summary 指标
 summary := prometheus.NewSummary(prometheus.SummaryOpts{
  Name:       "request_latency_seconds",
  Help:       "Latency of requests in seconds",
  Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, // 指定摘要目标
 })

 // 注册指标
 prometheus.MustRegister(summary)

 // 模拟请求,每秒随机生成请求持续时间并观察摘要
 go func() {
  for {
   duration := time.Duration(rand.Intn(100)) * time.Millisecond
   summary.Observe(duration.Seconds())
   time.Sleep(time.Second)
  }
 }()

 // 暴露指标
 http.Handle("/metrics", promhttp.Handler())
 fmt.Println("Summary exporter is running on :8080/metrics")
 http.ListenAndServe(":8080", nil)
}
运行代码查看效果

图片

4.3 通过http暴露指标

promhttp 包提供了有关 HTTP 服务器和客户端工具。可以通过创建http.Handler实例来达到以http协议暴露Prometheus指标的目的。
package main

import (
   "net/http"

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

func main()  {
   http.Handle("/metrics", promhttp.Handler())
   http.ListenAndServe(":8080", nil)
}
运行代码,访问localhost:8080/metrics可以看到默认的指标

5 实战案例

下面再通过一段代码演示如何获取Linux系统内存的total、used、free三个值
package main

import (
 "fmt"
 "os"
 "strconv"
 "strings"
 "time"
 "net/http"

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

// Define a custom Collector to collect memory metrics
type memoryCollector struct {
 totalMetric prometheus.Gauge
 usedMetric  prometheus.Gauge
 freeMetric  prometheus.Gauge

 total float64 // 新增字段,用于存储总内存值
 used  float64 // 新增字段,用于存储已使用内存值
 free  float64 // 新增字段,用于存储空闲内存值
}

// Implement the Describe method for the Collector interface
func (c *memoryCollector) Describe(ch chan<- *prometheus.Desc) {
 c.totalMetric.Describe(ch)
 c.usedMetric.Describe(ch)
 c.freeMetric.Describe(ch)
}

// Implement the Collect method for the Collector interface
func (c *memoryCollector) Collect(ch chan<- prometheus.Metric) {
 // Open /proc/meminfo file
 file, err := os.Open("/proc/meminfo")
 if err != nil {
  fmt.Println("Error opening /proc/meminfo:", err)
  return
 }
 defer file.Close()

 // Read memory info from file
 buf := make([]byte, 1024)
 n, err := file.Read(buf)
 if err != nil {
  fmt.Println("Error reading /proc/meminfo:", err)
  return
 }

 // Parse memory info
 lines := strings.Split(string(buf[:n]), "\n")
 for _, line := range lines {
  fields := strings.Fields(line)
  if len(fields) < 3 {
   continue
  }
  switch fields[0] {
  case "MemTotal:":
   total, err := strconv.ParseFloat(fields[1], 64)
   if err != nil {
    fmt.Println("Error parsing MemTotal:", err)
    continue
   }
   c.total = total
   c.totalMetric.Set(total)
  case "MemFree:":
   free, err := strconv.ParseFloat(fields[1], 64)
   if err != nil {
    fmt.Println("Error parsing MemFree:", err)
    continue
   }
   c.free = free
   c.freeMetric.Set(free)
  case "MemAvailable:":
   available, err := strconv.ParseFloat(fields[1], 64)
   if err != nil {
    fmt.Println("Error parsing MemAvailable:", err)
    continue
   }
   // Calculate used memory as total - available
   c.used = c.total - available
   c.usedMetric.Set(c.used)
  }
 }

 // Collect metrics
 c.totalMetric.Collect(ch)
 c.usedMetric.Collect(ch)
 c.freeMetric.Collect(ch)
}

func main() {
 // Create new memoryCollector
 memory := &memoryCollector{
  totalMetric: prometheus.NewGauge(prometheus.GaugeOpts{
   Name: "memory_total_bytes",
   Help: "Total memory in bytes",
  }),
  usedMetric: prometheus.NewGauge(prometheus.GaugeOpts{
   Name: "memory_used_bytes",
   Help: "Used memory in bytes",
  }),
  freeMetric: prometheus.NewGauge(prometheus.GaugeOpts{
   Name: "memory_free_bytes",
   Help: "Free memory in bytes",
  }),
 }

 // Register the memoryCollector with Prometheus
 prometheus.MustRegister(memory)

 // Create a new ServeMux
 mux := http.NewServeMux()

 // Register the Prometheus handler to the /metrics endpoint
 mux.Handle("/metrics", promhttp.Handler())

 // Start HTTP server to expose metrics
 httpServer := &http.Server{
  Addr:         ":8080",
  ReadTimeout:  10 * time.Second,
  WriteTimeout: 10 * time.Second,
  Handler:      mux, // Use the custom ServeMux
 }
 go func() {
  if err := httpServer.ListenAndServe(); err != nil {
   fmt.Println("Error starting HTTP server:", err)
  }
 }()

 fmt.Println("Memory exporter is running on :8080/metrics")

 // Keep the program running
 select {}
}
运行代码后,输入端口号,可以看到内存相关信息

图片


关注公众号 singless,获取更多有价值的文章

singless
云计算从业者,DevOps践行者
35篇原创内容

 

图片


图片

你可能还喜欢

点击下方文字即可阅读

AI概念扫盲:人工智能入门指南

AI场景下高性能网络技术RoCE v2介绍

如何区分应用所在的运行环境:物理机、虚拟机、容器还是 K8s?

K8S下的GPU监控

K8S集群下的GPU调度

云原生 · 目录
上一篇lldpd介绍
文章已于2024-06-23修改
 

微信扫一扫
关注该公众号

 
 
 
 
posted @ 2024-06-25 13:50  技术颜良  阅读(24)  评论(0编辑  收藏  举报