Prometheus 第一篇:快速上手

157 阅读10分钟

metrics 类型

Prometheus 总共提供了四种类型的 metrics 类型,分别为下面四种类型

Counter

Counter 类型的指标其工作方式和计数器一样,只增不减(除非系统发生重置)

常见的监控指标,如 http_requests_total,node_cpu 都是 Counter 类型的监控指标

一般在定义 Counter 类型指标的名称时推荐使用 _total 作为后缀

Gauge

与Counter不同,Gauge类型的指标侧重于反应系统的当前状态。因此这类指标的样本数据可增可减。

常见指标如:

node_memory_MemFree(主机当前空闲的内容大小)

node_memory_MemAvailable(可用内存大小)都是Gauge类型的监控指标

Histogram & Summary

Q:为什么需要这两个指标?

A:在大多数情况下人们都倾向于使用某些量化指标的平均值,例如CPU的平均使用率、页面的平均响应时间。这种方式的问题很明显,以系统API调用的平均响应时间为例:如果大多数API请求都维持在100ms的响应时间范围内,而个别请求的响应时间需要5s,那么就会导致某些WEB页面的响应时间落到中位数的情况,而这种现象被称为****长尾问题

为了区分是平均的慢还是长尾的慢,最简单的方式就是按照请求延迟的范围进行分组。例如,统计延迟在0-10ms之间的请求数有多少而10-20ms之间的请求数又有多少。通过这种方式可以快速分析系统慢的原因。Histogram和Summary都是为了能够解决这样问题的存在,通过Histogram和Summary类型的监控指标,我们可以快速了解监控样本的分布情况

那么这两个指标的区别是什么?

我们针对下面这个指标来看一个具体的例子

summary 类型

# HELP prometheus_tsdb_wal_fsync_duration_seconds Duration of WAL fsync.
# TYPE prometheus_tsdb_wal_fsync_duration_seconds summary
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.5"} 0.012352463
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.9"} 0.014458005
prometheus_tsdb_wal_fsync_duration_seconds{quantile="0.99"} 0.017316173
prometheus_tsdb_wal_fsync_duration_seconds_sum 2.888716127000002
prometheus_tsdb_wal_fsync_duration_seconds_count 216

从上面的样本中可以得知当前Prometheus Server进行wal_fsync操作的总次数为216次,耗时2.888716127000002s。其中中位数(quantile=0.5)的耗时为0.012352463,9分位数(quantile=0.9)的耗时为0.014458005s

histogram 类型

# HELP prometheus_tsdb_compaction_chunk_range Final time range of chunks on their first compaction
# TYPE prometheus_tsdb_compaction_chunk_range histogram
prometheus_tsdb_compaction_chunk_range_bucket{le="100"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="400"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="1600"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="6400"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="25600"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="102400"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="409600"} 0
prometheus_tsdb_compaction_chunk_range_bucket{le="1.6384e+06"} 260
prometheus_tsdb_compaction_chunk_range_bucket{le="6.5536e+06"} 780
prometheus_tsdb_compaction_chunk_range_bucket{le="2.62144e+07"} 780
prometheus_tsdb_compaction_chunk_range_bucket{le="+Inf"} 780
prometheus_tsdb_compaction_chunk_range_sum 1.1540798e+09
prometheus_tsdb_compaction_chunk_range_count 780

可以看到这两个指标的区别如下:

  1. Summary 指标统计的是区间范围内的某个范围的数值,比如常见的请求的 P99,P90 等延迟
  2. Histogram 指标直接反应了在不同区间内样本的个数,区间通过标签len进行定义

快速入门

wget https://github.com/prometheus/prometheus/releases/download/v2.54.1/prometheus-2.54.1.linux-amd64.tar.gz
 
解压后完成后直接启动即可
./prometheus --config.file=prometheus.yml

查看 metrics 路由和 graph 路由

配置文件 prometheus.yml 解析

# my global config
global:
  # 全局默认的数据拉取间隔
  scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
  # 全局默认的报警规则拉取间隔
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).
 
# Alertmanager configuration
alerting:
  alertmanagers:
    - static_configs:
        - targets:
          # - alertmanager:9093
 
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
  # 规则相关的配置文件
  # - "first_rules.yml"
  # - "second_rules.yml"
 
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.# prometheus 配置规则相关的参数
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  # 任务名称
  - job_name: "prometheus"
 
    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.
    
    # 配置 server 的暴露访问端口
    static_configs:
      - targets: ["localhost:9090"]
 

整体架构

参考上面这个图我们可以发现,prometheus 其实就是一个时序数据的中转站

输入源是配置有metrics 时序数据的各种 exporter

输出源就是将时序数据进行展示的终端组件 Grafana 以及可配置的报警组件 AlertManager

快速入门

下面我们结合 node_export 来演示如何接入 prometheus 以及如何配置相关的报警 AlertManager

下载 node_exporter 和 grafana 以及报警配置 alertmanager 并进行解压

 wget https://github.com/prometheus/node_exporter/releases/download/v1.8.2/node_exporter-1.8.2.linux-amd64.tar.gz
 
 wget https://dl.grafana.com/enterprise/release/grafana-enterprise-11.2.2.linux-amd64.tar.gz
 
 wget https://github.com/prometheus/alertmanager/releases/download/v0.27.0/alertmanager-0.27.0.linux-amd64.tar.gz

配置对应的 metrics 新增一个 node_exporter 相关的 job

# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
  # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config.
  - job_name: "prometheus"
    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.
    static_configs:
      - targets: ["localhost:9090"]
    
# 新增的 node_exporter 的 job
  - job_name: "node_exporter"
    static_configs:
      - targets: ["localhost:9100"]

启动完成之后会发现对应的 node_exporter 的 metrics 已经能够被 prometheus 捕获到

任意选取一个指标进行观察可看到对应的时序曲线图

我们配置 grafana 来进行更加人性化的可视化展示

启动上面下载好的 grafana-server 之后,导入 node_exporter 的 dashboard 配置,即可获取如下所以的 dashboard 图形化展示

便可以看到所有的可视化时序数据的变化

我们以右上角的 CPU 核数来看看具体的查询语句是什么样子的

其实就是将从 node_exporter 里面暴露出去的指标进行了适当的聚合得出了 CPU 的总数

instance 就代表当前的实例的名称,宿主机的 ip:port 组成
job 就代表了当前运行的一个任务

Exporter 详解

记住下面这几个相关的端口

prometheus     9090
node_exporter  9100
grafana        3000

node_exporter

我们知道喂给 prometheus 的数据源必须都暴露一个相关的 /metrics 接口来吐出对应的 metrics 时序数据,node_exporter 自然也不例外

查看机器的 9100/metrics 端口会发现暴露出很多的指标

仔细观察 metrics 指标的数据格式,指标构成规则如下

指标名称 + label + 数值

代码解析

// 存储所有的 Collector
initiatedCollectors    = make(map[string]Collector)
 
// NewNodeCollector creates a new NodeCollector.
func NewNodeCollector(logger *slog.Logger, filters ...string) (*NodeCollector, error) {
        f := make(map[string]bool)
    // 判断是否 filters 中的 collector 是否存在
        for _, filter := range filters {
                enabled, exist := collectorState[filter]
                if !exist {
                        return nil, fmt.Errorf("missing collector: %s", filter)
                }
                if !*enabled {
                        return nil, fmt.Errorf("disabled collector: %s", filter)
                }
                f[filter] = true
        }
        collectors := make(map[string]Collector)
        initiatedCollectorsMtx.Lock()
        defer initiatedCollectorsMtx.Unlock()
        for key, enabled := range collectorState {
                if !*enabled || (len(f) > 0 && !f[key]) {
                        continue
                }
        // 这里会按照State注册对应的collectors
                if collector, ok := initiatedCollectors[key]; ok {
                        collectors[key] = collector
                } else {
                        collector, err := factories[key](logger.With("collector", key))
                        if err != nil {
                                return nil, err
                        }
                        collectors[key] = collector
                        initiatedCollectors[key] = collector
                }
        }
        return &NodeCollector{Collectors: collectors, logger: logger}, nil
}
 

各种各样的 Collector 通过 init 函数注册到上面的 initiatedCollectors 里面

func init() {
        registerCollector("meminfo", defaultEnabled, NewMeminfoCollector)
}
 
func init() {
        registerCollector("os", defaultEnabled, NewOSCollector)
}
 

我们以一个内存采集的具体例子溯源 node_export 是怎么采集到这个指标的

func NewMeminfoCollector(logger *slog.Logger) (Collector, error) {
    // 构建一个fs函数,procPath 的默认路径值是 /proc
        fs, err := procfs.NewFS(*procPath)
    ...
}
 
func (c *meminfoCollector) getMemInfo() (map[string]float64, error) {
    // 这里构建一个Meminfo的结构体
    meminfo, err := c.fs.Meminfo()
 
        metrics := make(map[string]float64)
 
        if meminfo.ActiveBytes != nil {
                metrics["Active_bytes"] = float64(*meminfo.ActiveBytes)
        }
        if meminfo.ActiveAnonBytes != nil {
                metrics["Active_anon_bytes"] = float64(*meminfo.ActiveAnonBytes)
    }
    ....
}
 
func (fs FS) Meminfo() (Meminfo, error) {
    // 看这个函数名就是读取一个路径下面的 meminfo 的文件
        b, err := util.ReadFileNoStat(fs.proc.Path("meminfo"))
   ....
        m, err := parseMemInfo(bytes.NewReader(b))
}

所以总结起来就是获取 /porc/meminfo 里面的信息再转化成 metrics 这个map里面的数据即可

自定义 exporter

理解了 prometheus 之后,我们可以编写一个最简单的 exporter 服务,服务必须暴露对应的 /metrics 接口供 prometheus 采集指标

// 定义一个 counter 类型的指标用来记录接口被请求的次数
var pingCounter = prometheus.NewCounter(prometheus.CounterOpts{
        Name: "ping_request_counter",
        Help: "No of request handled by Ping handler",
})
 
func ping(w http.ResponseWriter, req *http.Request) {
    // 每当这个接口被请求时就将对应的metri指标递增一个值
        pingCounter.Inc()
        fmt.Fprintf(w, "pong")
}
 
func main() {
        prometheus.MustRegister(pingCounter)
        http.HandleFunc("/ping", ping)
    // 这里必须暴露 /metrics 接口供 prometheus 进行监控接入
        http.Handle("/metrics", promhttp.Handler())
        http.ListenAndServe(":8090", nil)
}

启动上述服务之后,我们访问对应的 /ping 接口就能在看到对应的 metrics 的指标

配置prometheus文件,在 scrape_configs 参数底下新增如下的配置

scrape_configs:
    .....
  - job_name: simple_server
    static_configs:
      - targets: ["localhost:8090"]

在 prometheus 的 web-ui 里面就能看到对应的配置信息以及指标的变化情况


配置报警

目前 prometheus 支持配置的告警目标方有 email, webhook, pagerduty, slack 这四种,我们这里实用 webhook 来进行告警配置

参考这里获取 webhook 相关的地址 Webhook.site - Test, transform and automate Web requests and emails

配置报警相关参数

global:
  resolve_timeout: 5m
route:
  receiver: webhook_receiver
receivers:
    - name: webhook_receiver
      webhook_configs:
        - url: '<INSERT-YOUR-WEBHOOK>' # 这里替换成上面的webhook-url
          send_resolved: false

启动 alertmanager

alertmanager --config.file=alertmanager.yml

访问 9093 端口如果能看到下述相关信息则代表配置成功

修改 prometheus 配置文件来对接 alertmanager

# Alertmanager configuration
alerting:
  alertmanagers:
    - static_configs:
        - targets:
           - localhost:9093
rule_files:
  - rules.yml

新增 rules.yaml 文件

groups:
# 代表当指标 ping_request_count 超过 8 时便触发报警
 - name: Count greater than 8
   rules:
   - alert: CountGreaterThan8
     expr: ping_request_counter > 8
     for: 10s

重新启动 prometheus,查看 rules 如下即代表配置成功

尝试触发报警,不断尝试访问 /ping 接口,查看报警页面已经是 pending 状态

AlertManager 也收到了报警

查看配置的 web-hook 报警页面,可看到也收到了相关的报警信息

集群部署

参考:GitHub - prometheus-operator/kube-prometheus: Use Prometheus to monitor Kubernetes and applications running on Kubernetes

安装之前需要先确认集群的版本

➜  ~ k version                                                                                        
Client Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.8", GitCommit:"a12b886b1da059e0190c54d09c5eab5219dd7acf", GitTreeState:"clean", BuildDate:"2022-06-16T05:57:43Z", GoVersion:"go1.17.11", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"23", GitVersion:"v1.23.17", GitCommit:"953be8927218ec8067e1af2641e540238ffd7576", GitTreeState:"clean", BuildDate:"2023-02-22T13:27:46Z", GoVersion:"go1.19.6", Compiler:"gc", Platform:"linux/amd64"}

比如我安装的k8s的版本是1.23,需要下载 release-0.11 的版本

查看 monitoring 空间的 deployment,如果全部能够全部 Ready 即代表部署成功

➜  ~ k get deployments.apps -n monitoring                                                             
NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
blackbox-exporter     1/1     1            1           2m45s
grafana               1/1     1            1           2m44s
kube-state-metrics    1/1     1            1           2m44s
prometheus-adapter    2/2     2            2           2m44s
prometheus-operator   1/1     1            1           2m44s

部署完成之后我们将 prometheus-k8s 的 service 暴露到主机上的一个特定端口

➜  ~ k port-forward --address 0.0.0.0 service/prometheus-k8s 39090:9090 -n monitoring               
Forwarding from 0.0.0.0:39090 -> 9090

直接访问对应的 39090 端口便能看到对应的 prometheus 的 UI 界面

同样我们查看暴露 Grafana 对应的 service 也能查看到具体的 web-UI 界面

➜  ~ k port-forward --address 0.0.0.0 service/grafana 33000:3000 -n monitoring
Forwarding from 0.0.0.0:33000 -> 3000

内置了很多的 Dashboard

我们查看一个具体的 kubelet 相关的监控看板

由于我 k8s 是部署的单个节点,所以只会有一个 kubelet 节点

查看总的pod的数量,确实只有24个,指标采集符合预期

参考资料

10分钟教你在k8s中部署Prometheus全家桶前言 K8s本身不包含内置的监控工具,所以市场上有不少这样监控工具来 - 掘金chronosphere.io/learn/an-in…

Prometheus - Monitoring system & time series database