Prometheus -- 浅谈Exporter

news/2024/5/19 2:03:00 标签: prometheus

Prometheus系统 – Exporter原理

为什么我们需要Exporter?

广义上讲所有可以向Prometheus提供监控样本数据的程序都可以被称为一个Exporter。而Exporter的一个实例称为target,如下所示,Prometheus通过轮询的方式定期从这些target中获取样本数据:

img

Prometheus 已经成为云原生应用监控行业的标准,在很多流行的监控系统中都已经实现了 Prometheus的监控接口,例如 etcd、Kubernetes、CoreDNS等,它们可以直接被Prometheus监控,但大多数监控对象都没办法直接提供监控接口,主要原因有:

  • 很多系统在Prometheus诞生之前的很多年就已经发布了,例如MySQL和Redis等。
  • 它们本身不支持HTTP接口,例如对于硬件性能指标,操作系统并没有原生的HTTP接口可以获取。
  • 考虑到安全性,稳定性以及代码耦合等因素的影响,软件作者并不愿意将监控代码加入现有的代码中。

这些都导致无法通过一个规范解决所有监控问题。在此背景之下,Exporter 应运而生。Exporter 是一个采集监控数据并通过 Prometheus 监控规范对外提供数据的组件。除了官方实现的Exporter如Node Exporter、HAProxy Exporter、MySQLserver Exporter,还有很多第三方实现如Redis Exporter和RabbitMQ Exporter等。

在这里插入图片描述

Exporter分类

  • 社区提供的:
    • 例如:Node Exporter,MySQL Exporter,Fluentd Exporter
    • 官方文档链接:https://prometheus.io/docs/instrumenting/exporters/
  • 用户自定义的:
    • 用户可以基于Prometheus提供的Client Library创建自己的Exporter程序。
    • 这里给出Client Go的链接:https://github.com/prometheus/client_golang

Exporter获取监控数据的方式

Exporter主要通过被监控对象提供的监控相关的接口获取监控数据,主要有如下几种方式:

  • HTTP/HTTPS方式。例如RabbitMQ exporter通过RabbitMQ的HTTPS接口获取监控数据。
  • TCP方式。例如Redis exporter通过Redis提供的系统监控相关命令获取监控指标,MySQL server exporter通过MySQL开发的监控相关的表获取监控指标。
  • 本地文件方式。例如Node exporter通过读取proc文件系统下的文件,计算得到整个操作系统的状态。
  • 标准协议方式。

Exporter规范

Prometheus 在面对众多繁杂的监控对象时并没有采用逐一适配的方式,而是制定了一套独特的监控数据规范,符合这套规范的监控数据都可以被Prometheus统一采集、分析和展现。

所有的Exporter程序都需要按照Prometheus的规范,返回监控的样本数据。以Node Exporter为例,当访问/metrics地址时会返回以下内容:

# HELP node_cpu Seconds the cpus spent in each mode.
# TYPE node_cpu counter
node_cpu{cpu="cpu0",mode="idle"} 362812.7890625
# HELP node_load1 1m load average.
# TYPE node_load1 gauge
node_load1 3.0703125

Exporter返回的样本数据,主要由三个部分组成:样本的一般注释信息(HELP),样本的类型注释信息(TYPE)和样本。Prometheus会对Exporter响应的内容逐行解析:

  • 如果当前行以# HELP开始,Prometheus将会按照以下规则对内容进行解析,得到当前的指标名称以及相应的说明信息:# HELP <metrics_name> <doc_string>
  • 如果当前行以# TYPE开始,Prometheus会按照以下规则对内容进行解析,得到当前的指标名称以及指标类型: # TYPE <metrics_name> <metrics_type>
  • 除了# 开头的所有行都会被视为是监控样本数据。 每一行样本需要满足以下格式规范:
metric_name [
  "{" label_name "=" `"` label_value `"` { "," label_name "=" `"` label_value `"` } [ "," ] "}"
] value [ timestamp ]

自定义Exporter

官方给出了example可以给我们参考:

https://github.com/prometheus/client_golang/blob/main/examples/random/main.go

现在我们来解析一下:

  • 定义指标

    rpcDurations = prometheus.NewSummaryVec(
        prometheus.SummaryOpts{
            Name:       "rpc_durations_seconds",
            Help:       "RPC latency distributions.",
            Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
        },
        []string{"service"},
    )
    
  • 注册指标:

    prometheus.MustRegister(rpcDurations)
    
  • 记录监控样本数据:

    go func() {
        for {
            v := rand.Float64() * *uniformDomain
            rpcDurations.WithLabelValues("uniform").Observe(v)
            time.Sleep(time.Duration(100*oscillationFactor()) * time.Millisecond)
        }
    }()
    
  • 暴露接口

    http.Handle("/metrics", promhttp.HandlerFor(
        prometheus.DefaultGatherer,
        promhttp.HandlerOpts{
            // Opt into OpenMetrics to support exemplars.
            EnableOpenMetrics: true,
        },
    ))
    log.Fatal(http.ListenAndServe(*addr, nil))
    
  • 观察监控指标

    # HELP rpc_durations_seconds RPC latency distributions.
    # TYPE rpc_durations_seconds summary
    rpc_durations_seconds{service="uniform",quantile="0.5"} 4.2852774516474985e-05
    rpc_durations_seconds{service="uniform",quantile="0.9"} 0.00012093205759592392
    rpc_durations_seconds{service="uniform",quantile="0.99"} 0.00012093205759592392
    rpc_durations_seconds_sum{service="uniform"} 0.0002537090545263203
    rpc_durations_seconds_count{service="uniform"} 4
    

Node Exporter解析

  • 初始化注册采集

    // NodeCollector implements the prometheus.Collector interface.
    type NodeCollector struct {
        Collectors map[string]Collector
        logger     log.Logger
    }
    

    NodeCollector是采集器的集合,Collectors是包含了各种采集器的集合,每个采集器在启动的时候都会将自身注册到这个Collector中。

    // collector/meminfo.go
    func init() {
        registerCollector("meminfo", defaultEnabled, NewMeminfoCollector)
    }
    
  • 注册给Prometheus

    func (h *handler) innerHandler(filters ...string) (http.Handler, error) {
        nc, err := collector.NewNodeCollector(h.logger, filters...)
        r := prometheus.NewRegistry()
      r.Register(nc); 
    }
    
  • 采集指标

    • 遍历Collectors,执行采集动作。

      // Collect implements the prometheus.Collector interface.
      func (n NodeCollector) Collect(ch chan<- prometheus.Metric) {
          wg := sync.WaitGroup{}
          wg.Add(len(n.Collectors))
          for name, c := range n.Collectors {
              go func(name string, c Collector) {
                  execute(name, c, ch, n.logger)
                  wg.Done()
              }(name, c)
          }
          wg.Wait()
      }
      
      func execute(name string, c Collector, ch chan<- prometheus.Metric, logger log.Logger) {
          begin := time.Now()
          err := c.Update(ch)
          ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, duration.Seconds(), name)
          ch <- prometheus.MustNewConstMetric(scrapeSuccessDesc, prometheus.GaugeValue, success, name)
      }
      
    • 具体采集指标实现Update接口(例如:meminfo.go)

      • Update方法传入一个只写(write only)的单向管道,首先通过getMemInfo获取内存信息,然后将内存信息发送到管道中。

        // Update calls (*meminfoCollector).getMemInfo to get the platform specific
        // memory metrics.
        func (c *meminfoCollector) Update(ch chan<- prometheus.Metric) error {
            var metricType prometheus.ValueType
            memInfo, err := c.getMemInfo()
            if err != nil {
                return fmt.Errorf("couldn't get meminfo: %w", err)
            }
            level.Debug(c.logger).Log("msg", "Set node_mem", "memInfo", memInfo)
            for k, v := range memInfo {
                if strings.HasSuffix(k, "_total") {
                    metricType = prometheus.CounterValue
                } else {
                    metricType = prometheus.GaugeValue
                }
                ch <- prometheus.MustNewConstMetric(
                    prometheus.NewDesc(
                        prometheus.BuildFQName(namespace, memInfoSubsystem, k),
                        fmt.Sprintf("Memory information field %s.", k),
                        nil, nil,
                    ),
                    metricType, v,
                )
            }
            return nil
        }
        
        
        func (c *meminfoCollector) getMemInfo() (map[string]float64, error) {
          ...
            return map[string]float64{
                "active_bytes":            ps * float64(vmstat.active_count),
                "compressed_bytes":        ps * float64(vmstat.compressor_page_count),
                "inactive_bytes":          ps * float64(vmstat.inactive_count),
                "wired_bytes":             ps * float64(vmstat.wire_count),
                "free_bytes":              ps * float64(vmstat.free_count),
                "swapped_in_bytes_total":  ps * float64(vmstat.pageins),
                "swapped_out_bytes_total": ps * float64(vmstat.pageouts),
                "total_bytes":             float64(total),
                "swap_used_bytes":         float64(swap.xsu_used),
                "swap_total_bytes":        float64(swap.xsu_total),
            }, nil
        }
        
  • 查看结果:

    # HELP node_memory_active_bytes Memory information field active_bytes.
    # TYPE node_memory_active_bytes gauge
    node_memory_active_bytes 5.08428288e+09
    
    # HELP node_memory_swapped_in_bytes_total Memory information field swapped_in_bytes_total.
    # TYPE node_memory_swapped_in_bytes_total counter
    node_memory_swapped_in_bytes_total 3.73191360512e+11
    

http://www.niftyadmin.cn/n/98108.html

相关文章

python的所有知识点+代码+注释,不看就亏死了

目录 简介 特点 搭建开发环境 版本 hello world 注释 文件类型 变量 常量 数据类型 运算符和表达式 控制语句 数组相关 函数相关 字符串相关 文件处理 对象和类&#xff0c;注&#xff1a;不是那个对象&#xff01;&#xff01;&#xff01;&#xff01;&…

[SCOI2005]骑士精神(C++,启发式搜索)

题目描述 输入格式 第一行有一个正整数 TTT&#xff08;T≤10T \le 10T≤10)&#xff0c;表示一共有 TTT 组数据。 接下来有 TTT 个 555 \times 555 的矩阵&#xff0c;0 表示白色骑士&#xff0c;1 表示黑色骑士&#xff0c;* 表示空位。两组数据之间没有空行。 输出格式 …

nginx配置https证书

甲方发来的nginx证书解压后有一个 key类型及pem类型的文件 1. 在nginx conf文件夹创建cert文件夹,把这2个文件复制进去 2. 打开nginx.conf文件配置证书 修改的代码就圈中的几句, ssl_protocols SSLv3 TLSv1; # SSL协议 这个注释掉了,否则谷歌高版本浏览器 可能提示 ERR_SSL_…

2023年全国最新机动车签字授权人精选真题及答案5

百分百题库提供机动车签字授权人考试试题、机动车签字授权人考试预测题、机动车签字授权人考试真题、机动车签字授权人证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 四、多选题 1.以下哪些气体属于排放污染物&#xff08…

秒杀系统设计方案的思考

前言 秒杀系统是我们电商系统中常见的一种业务模式&#xff0c;用于吸引用户&#xff0c;刺激留存级消费所做的一种活动。 秒杀系统的特点&#xff1a; 1.瞬时流量极大&#xff0c;过了秒杀时间点流量结束。所以不能单纯用机器对QPS。 2.秒杀商品库存很少&#xff0c;例如有1…

规则引擎与风控系统05:其他规则引擎

上一节给大家展示了笔者之前实际开发过的项目的核心风控系统代码,其实更复杂的基于规则的风控系统也都是从简单到复杂慢慢演化出来的。虽然Drools很强大,但它也不是唯一的规则引擎,还有另外两个也同样出色,它们是Groovy和Aviator。 Groovy是Apache的35个顶级开源项目之一(…

Python带你制作一个属于自己的多功能音乐播放器

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 就是用Python做一个简易的音乐播放器&#xff0c;废话不多说&#xff0c;咱们直接开干 当然&#xff0c;今天做这个肯定不是最简单的&#xff0c;最简单的音乐播放器&#xff0c;9行代码足以 完整源码等直接在文末名片领…

gof23 设计模式 各个模式代码demo

Gof23 设计模式&#xff0c;也叫Gang of Four&#xff08;GoF&#xff09;设计模式&#xff0c;是由四位设计模式大师&#xff08;Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides&#xff09;撰写的一本书——《设计模式&#xff1a;可复用面向对象软件的基础》所…