Promethues-手把手教你写exporter:从入门到多实例监控

#作者:朱雷

一、Promethues exporter简介

1.1. 定义与作用

Exporter 是 Prometheus 生态中用于‌采集目标系统监控数据‌并将其转换为 Prometheus 兼容格式的组件。

其核心作用包括:
‌数据转换‌:将非 Prometheus 原生支持的监控数据(如硬件、数据库、中间件等)标准化为 Prometheus 可识别的指标格式‌。
‌端点暴露‌:通过 HTTP 服务提供 /metrics 默认接口,供Prometheus Server 定期拉取数据‌。

1.2. Exporter分类

类型‌特点示例原理
直接采集型监控目标原生支持 Prometheus 协议,直接暴露指标接口(无需独立 Exporter)Kubernetes目标系统内置指标生成逻辑‌
间接采集型需独立部署 Exporter,通过适配器或脚本从目标系统采集并转换数据Node Exporter、MySQL Exporter、Redis ExporterExporter 调用目标系统API或连接到目标系统使用工具命令生成指标‌

二、开发环境

主机nodeGo版本Redis cluster版本备注
BCLinux>=1.166.2.123主节点/3从节点

三、开发指南

3.1. 指标类型

Prometheus 文档: https: //prometheus.io/docs/concepts/metric_types/
Prometheus 客户端库提供四种核心指标类型:

指标类型说明备注
Counter计数器:是一种累积指标,表示单个单调递增的计数器,其值只能在重新启动时增加或重置为零 可以使用计数器来表示已处理的请求数、已完成的任务数或错误数不要使用计数器来显示可能减少的值
Gauge仪表: 表示可以任意上升或下降的单个数值仪表通常用于测量温度或当前内存使用情况等值,但也用于测量可以上升和下降的“计数”,例如并发请求的数量
Histogram直方图: 对观察结果进行采样(通常是请求持续时间或响应大小等) ,并将它们计数到可配置的存储桶中。提供所有观察值的总和可以从直方图甚至直方图聚合中计算分位数。直方图也适用于计算 Apdex 分数。在对桶进行操作时,请记住直方图是累积的
Summary摘要:会抽样观察结果(通常是请求持续时间和响应大小等)。虽然它还提供观察结果的总数和所有观察值的总和,但它会在滑动时间窗口内计算可配置的分位数

除了基本指标类型 Gauge、Counter、Summary 和 Histogram 之外,Prometheus 数据模型的一个非常重要的部分是以“标签”的维度对样本进行分区,从而生成指标向量。

基本类型包括 GaugeVec、CounterVec、SummaryVec 和 HistogramVec。

虽然只有基本指标类型实现了 Metric 接口,但指标及其向量版本都实现了 Collector 接口。Collector 管理大量指标的收集,但为了方便起见,指标也可以“自行收集”。请注意,Gauge、Counter、Summary 和 Histogram 本身是接口,而 GaugeVec、CounterVec、SummaryVec 和 HistogramVec 不是。

要创建 Metrics 及其向量版本的实例,需要一个合适的Opts 结构,即 GaugeOpts、CounterOpts、SummaryOpts 或 HistogramOpts。

3.2.一个简单例子

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))
}

这是个基本的程序,它导出两个指标,一个仪表和一个计数器,后者带有标签,可将其转换为(一维)向量。它使用自定义注册表注册指标,并通过 /metrics 端点上的 HTTP 服务器公开它们。

3.3.开发一个redis cluster exporter

从上面简单例子可以知道,开发到采集流程为:
在这里插入图片描述
从流程图中可以看出,除了 “设置指标值” 需要自己实现外,其余都是标准化操作。
下面我们根据这个流程开发一个redis cluster exporter 参考,抛砖引玉,并在后面扩展成可以自定义监控多台redis cluster 实例上的集群状态数据。

关键代码:

3.3.1. 定义指标类型

type clusterMetrics struct {
    cluster_state          prometheus.Gauge
    cluster_slots_assigned prometheus.Gauge
    cluster_slots_ok       prometheus.Gauge
    cluster_slots_pfail    prometheus.Gauge
    cluster_slots_fail     prometheus.Gauge
    cluster_known_nodes    prometheus.Gauge
    cluster_size           prometheus.Gauge
    // cluster_size           *prometheus.CounterVec
}

3.3.2. 使用自定义注册表注册指标

func NewClusterMetrics(reg prometheus.Registerer) *clusterMetrics {
    m := &clusterMetrics{
        cluster_state: prometheus.NewGauge(prometheus.GaugeOpts{
            Name: "redis_cluster_state",
            Help: "redis cluster current state ok or not",
        }),
        cluster_slots_assigned: prometheus.NewGauge(prometheus.GaugeOpts{
            Name: "redis_cluster_slots_assigned",
            Help: "分配的槽位数",
        }),
        cluster_slots_ok: prometheus.NewGauge(prometheus.GaugeOpts{
            Name: "redis_cluster_slots_ok",
            Help: "不在FAIL或PFAIL状态槽位数",
        }),
        cluster_slots_pfail: prometheus.NewGauge(prometheus.GaugeOpts{
            Name: "redis_cluster_slots_pfail",
            Help: "在PFAIL状态槽位数",
        }),
        cluster_slots_fail: prometheus.NewGauge(prometheus.GaugeOpts{
            Name: "redis_cluster_slots_fail",
            Help: "在FAIL状态槽位数",
        }),
        cluster_known_nodes: prometheus.NewGauge(prometheus.GaugeOpts{
            Name: "redis_cluster_known_nodes",
            Help: "the number of node",
        }),
        cluster_size: prometheus.NewGauge(prometheus.GaugeOpts{
            Name: "redis_cluster_size",
            Help: "主节点数",
        }),
    }
    reg.MustRegister(m.cluster_state)
    reg.MustRegister(m.cluster_slots_assigned)
    reg.MustRegister(m.cluster_slots_ok)
    reg.MustRegister(m.cluster_slots_pfail)
    reg.MustRegister(m.cluster_slots_fail)
    reg.MustRegister(m.cluster_known_nodes)
    reg.MustRegister(m.cluster_size)
    return m
}

3.3.3. 设置指标状态数据

func GetRedisClusterStateMetrics(addr, pwd string, logger klog.Logger) *prometheus.Registry {

    reg := prometheus.NewRegistry()
    m := NewClusterMetrics(reg)

    // 创建 Redis 客户端
    conn := redis.NewClient(&redis.Options{
        Addr:     addr, // Redis 地址,可以是本地或者远程
        Password: pwd,  // 如果没有设置密码,可以为空
        // DB:         0,    // 使用默认的 DB
        MaxRetries: 3,
    })

    ok, err := conn.Ping().Result()
    level.Info(logger).Log("Ping 返回值: %s", ok)
    if err != nil || ok != "PONG" {
        level.Error(logger).Log("redis连接失败. redis地址: %s. 错误信息: %s %s", addr, ok, err)
    }
    level.Info(logger).Log("redis连接成功, redis地址: %s", addr)

    val, err := conn.Do("CLUSTER", "INFO").Result()
    if err == redis.Nil || err != nil || val.(string) == "" {
        level.Error(logger).Log("获取redis state 地址: %s. 错误信息: %s", addr, err)
        return reg
    }

    infoMap := TextToMap(val.(string))
    m.cluster_state.Set(0)
    if val, ok := infoMap["cluster_state"]; ok && val == "ok" {
        m.cluster_state.Set(1)
    }
    if val, ok := infoMap["cluster_slots_assigned"]; ok {
        floatValue, _ := strconv.ParseFloat(val, 64)
        m.cluster_slots_assigned.Set(floatValue)
    }
    if val, ok := infoMap["cluster_slots_ok"]; ok {
        floatValue, _ := strconv.ParseFloat(val, 64)
        m.cluster_slots_ok.Set(floatValue)
    }
    if val, ok := infoMap["cluster_slots_pfail"]; ok {
        floatValue, _ := strconv.ParseFloat(val, 64)
        m.cluster_slots_pfail.Set(floatValue)
    }
    if val, ok := infoMap["cluster_slots_fail"]; ok {
        floatValue, _ := strconv.ParseFloat(val, 64)
        m.cluster_slots_fail.Set(floatValue)
    }
    if val, ok := infoMap["cluster_known_nodes"]; ok {
        floatValue, _ := strconv.ParseFloat(val, 64)
        m.cluster_known_nodes.Set(floatValue)
    }
    if val, ok := infoMap["cluster_size"]; ok {
        floatValue, _ := strconv.ParseFloat(val, 64)
        m.cluster_size.Set(floatValue)
    }
    return reg
}

从 cluster info 获取集群状态信息数据,经处理后给指标设置值。
其他指标可以自行定义指标类型,并从redis的字段标签{LABLES} INFO 中获取数据值。

3.3.4. 扩展使exporter监控多台redis集群实例

以上程序只能一个exporter监控采集一个redis 实例数据,要同时采集多个实例需要开启多个exporter进程。如果资源比较紧张,这会导致资源浪费。
我们可以改造程序让它适配采集多个redis集群实例数据。

改造思路
提供一个多个实例连接参数信息的配置文件
修改暴漏指标信息的HTTP服务器接收redis连接信息参数name
改造代码:
配置文件参考:

host:
  test001: # name 参数名,标识一个redis 实例
    ip: 192.168.3.127
    pwd: ''
    port: 6379
  test002:   # name 参数名,标识一个redis 实例
    ip: 192.168.2.128
    pwd: xxxxx
    port: 6379

程序代码参考:

func newHandler(logger klog.Logger) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        params := r.URL.Query()["name"]
        level.Info(logger).Log("msg", "collect[] params", "params", strings.Join(params, ","))

        host := viper.GetStringMapString(fmt.Sprintf("host.%s", params[0]))
        addr := host["ip"] + ":" + host["port"]
        reg := GetRedisClusterStateMetrics(addr, host["pwd"], logger)

        h := promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg})
        h.ServeHTTP(w, r)
    }
}

更多改造和完整程序实例请参考:
https://github.com/zhuleiandy888/redis-cluster-exporter-template

3.3.5. 监控指标展示

在这里插入图片描述

四、Promethues 监控配置文件参考

抓取自定义 redis cluster exporter 配置文件:

scrape_configs:
      - job_name: 'redis_cluster_exporter'
        scrape_timeout: 10s
        scrape_interval: 30s
        static_configs:
          - targets:
            - '192.168.3.240:9104'  # 建议使用节点域名
          labels:
            name: test001
            __metrics_path__: /metrics
            __param_name: test001

          - targets: 
            - '192.168.3.240:9104'   # 建议使用节点域名
          labels:	
            name: test002
            __metrics_path__: /metrics
            __param_name: test002
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值