菜鸟笔记
提升您的技术认知

Prometheus浅析

1.Prometheus是什么?

Prometheus(下文称Prom) 是由 SoundCloud 开源监控告警解决方案,与Kubernetes同属CNCF,它已经成为炙手可热的Kubernetes生态圈中的核心监控系统,越来越多的项目(如Kubernetes和etcd等 )都加入了丰富的Prometheus原生支持,

Prom提供了通用的数据模型和便捷的数据采集、存储和查询接口,同时基于Go实现也大大降低了服务端的运维成本,可以借助一些优秀的图形化工具(如Grafana)可以实现友好的图形化和报警。

1.1单词理解

  • metric—指标(名字)
  • targes–数据源
  • alert–警报
  • exporter–负责数据汇报和信息收集的程序
  • quantile–分位数
  • bucket–块
  • Summary–概略图
  • Histogram–直方图
  • labels–标签

1.2主要特点:

  • 多维数据模型【时序由 metric(指标) 名字和 k/v 的 labels 构成】。
  • 灵活的查询语句(PromQL)。
  • 无依赖存储,支持 local 和 remote 不同模型。
  • 采用 http 协议,使用 pull 模式拉取数据,简单易懂。
  • 监控目标,可以采用服务发现或静态配置的方式。
  • 支持多种统计数据模型,图形化友好。

1.3核心组件:

  • Prometheus Server:主要用于抓取数据和存储时序数据,另外还提供API供外界查询和 Alert Rule 配置管理。
  • client libraries:用于对接 Prometheus Server, 可以查询和上报数据。
  • push gateway :允许短暂和​​批量作业将其指标暴露给普罗米修斯。由于这些类型的作业可能存在时间不足以被删除,因此他们可以将其指标推送到Pushgateway。然后,Pushgateway将这些指标暴露给普罗米修斯,主要用于业务数据汇报等。
  • 各种汇报数据的 exporters :例如汇报机器数据的 node_exporter, 汇报 MongoDB 信息的 MongoDB * exporter 等等。
  • 用于告警通知管理的 alertmanager 。

1.4基础架构图

1.5模块逻辑解析:

  1. Prometheus server :定期从静态配置的 Prometheus targets (数据源)或者服务发现的 targets (数据源)拉取数据。
  2. Retrieval:检索拉去到的数据分发给TSDB进行存储。
  3. HTTP server:用于接受外界的HTTP请求。
  4. TSDB:当新拉取的数据大于配置内存缓存区的时候,Prometheus会将数据持久化到磁盘(HHD/SSD)(如果使用 remote storage 将持久化到云端)。
  5. Prometheus 可以配置 rules,然后定时查询数据,当条件触发的时候,会将 alert(警告) 推送(pushalerts)到配置的 Alertmanager(警告管理器)。
  6. Alertmanager :收到警告的时候,可以根据配置,聚合,去重,降噪,最后发送警告。
  7. API client,Grafana:API client通过使用promQL查询数据,Grafana用于聚合数据将数据可视化。
  8. Web UI: Prometheus的web接口,可用于简单可视化,及语句执行或者服务状态监控。
  9. short-lived jobs:存在时间不足以被删除的短暂和​​批量作业
  10. pushgateway:shaort-lived jobs 在推出时将数据推给pushgateway,主要用于业务数据汇报等。
注意:
  • Prometheus 的数据是基于时序的 float64 的值,如果你的数据值有更多类型,无法满足。
  • Prometheus 不适合做审计计费,因为它的数据是按一定时间采集的,关注的更多是系统的运行瞬时状态以及趋势,即使有少量数据没有采集也能容忍,但是审计计费需要记录每个请求,并且数据长期存储,这个 Prometheus 无法满足,可能需要采用专门的审计系统。

2.prometheus的数据模型

Prometheus 存储的是时序数据, 即按照相同时序(相同的名字和标签),以时间维度存储连续的数据的集合。

2.1时序索引

  • 时序(time series) 是由指标名字(Metric),以及一组 key/value 标签定义的,具有相同的名字以及标签属于相同时序。

  • 时序的名字由 ASCII 字符,数字,下划线,以及冒号组成,它必须满足正则表达式

      [a-zA-Z_:][a-zA-Z0-9_:]*, 
    

其名字应该具有语义化,一般表示一个可以度量的指标,例如: http_requests_total, 可以表示 http 请求的总数。

  • 时序的标签可以使 Prometheus 的数据更加丰富,能够区分具体不同的实例,例如:

      http_requests_total{method="POST"} 可以表示所有 http 中的 POST 请求。
    
  • 标签名称由 ASCII 字符,数字,以及下划线组成, 其中 __ 开头属于 Prometheus 保留,标签的值可以是任何 unicode 字符,支持中文。

2.2时序样本

  • 按照某个时序以时间维度采集的数据,称之为样本,其值包含:
    • 一个 float64 值
    • 一个毫秒级的 unix 时间戳

2.3格式

  • Prometheus 时序格式与 OpenTSDB 相似:

         <metric name>{<label name>=<label value>, ...}
    

其中包含时序名字以及时序的标签。

2.4时序 4 种类型

Prometheus 时序数据分为 Counter(变化的增减量), Gauge(瞬时值), Histogram(采样并统计), Summary(采样结果) 四种类型。

  • Counter

Counter 表示收集的数据是按照某个趋势(增加/减少)一直变化的,我们往往用它记录服务请求总量、错误总数等。
例如 Prometheus server 中 http_requests_total, 表示 Prometheus 处理的 http 请求总数,我们可以使用 delta, 很容易得到任意区间数据的增量,这个会在 PromQL 一节中细讲。

  • Gauge

Gauge 表示搜集的数据是一个瞬时的值,与时间没有关系,可以任意变高变低,往往可以用来记录内存使用率、磁盘使用率等。
例如 Prometheus server 中 go_goroutines, 表示 Prometheus 当前 goroutines 的数量。

  • Histogram

主要用于表示一段时间范围内对数据进行采样(通常是请求持续时间或响应大小),
并能够对其指定区间以及总数进行统计,通常它采集的数据展示为直方图

	Histogram 由 <basename>_bucket{le="<upper inclusive bound>"},
	<basename>_bucket{le="+Inf"}, <basename>_sum,<basename>_count组成,
	例如 Prometheus server中prometheus_local_storage_series_chunks_persisted, 
	表示 Prometheus 中每个时序需要存储的 chunks 数量,我们可以用它计算待持久化的数据的分位数。
  • Summary

主要用于表示一段时间内数据采样结果(通常是请求持续时间或响
应大小),它直接存储了 quantile 数据,而不是根据统计区间计算出来的。

	Summary 和 Histogram 类似,由 <basename>{quantile="<φ>"},<basename>_sum,
	<basename>_count 组成,
	例如 Prometheus server 中 prometheus_target_interval_length_seconds。
  • Histogram vs Summary

      都包含 <basename>_sum,<basename>_count
      Histogram 需要通过 <basename>_bucket 计算 quantile, 
      而 Summary 直接存储了 quantile 的值。
    

2.5实例和作业

Prometheus 中,将任意一个独立的数据源(target)称之为实例(instance)。包含相同类型的实例的集合称之为作业(job)。

####2.6自生成标签和时序
  Prometheus 在采集数据的同时,会自动在时序的基础上添加标签,作为数据源(target)的标识,以便区分:

job: The configured job name that the target belongs to.
作业:设定该作业名属于哪个数据源
instance: The <host>:<port> part of the target's URL that was scraped.
例如:这个域名端口号是该数据源的url被剪切出的一部分

如果其中任一标签已经在此前采集的数据中存在,那么将会根据 honor_labels 设置选项来决定新标签。

  • 对每一个实例而言,Prometheus 按照以下时序来存储所采集的数据样本:

      * up{job="<job-name>", instance="<instance-id>"}: 1 表示该实例正常工作
      * up{job="<job-name>", instance="<instance-id>"}: 0 表示该实例故障
      * scrape_duration_seconds{job="<job-name>", instance="<instance-id>"} 表示拉取数据的时间间隔
      * scrape_samples_scraped{job="<job-name>", instance="<instance-id>"}  表示从该数据源获取的样本数
      * scrape_samples_post_metric_relabeling{job="<job-name>", instance="<instance-id>"} 
      表示采用重定义标签(relabeling)操作后仍然剩余的样本数
    

其中 up 时序可以有效应用于监控该实例是否正常工作。

3.PromQL 基本使用

  • PromQL (Prometheus Query Language) 是 Prometheus 自己开发的数据查询 DSL 语言,语言表现力非常丰富,内置函数很多,在日常数据可视化以及rule 告警中都会使用到它。

  • 在查询语句中,字符串往往作为查询条件labels(标签)的值,和Golang 字符串语法一致,可以使用 “”, ‘’, 或者 `` 。也可以使用正数或浮点数,

3.1查询结果类型

PromQL 查询结果主要有 3 种类型:

  • 瞬时数据 (Instant vector): 包含一组时序,每个时序只有一个点,例如:

      http_requests_total
    
  • 区间数据 (Range vector): 包含一组时序,每个时序有多个点,例如:

      http_requests_total[5m]
    
  • 纯量数据 (Scalar): 纯量只有一个数字,没有时序,例如:

      count(http_requests_total)
    

3.2查询条件

Prometheus 存储的是时序数据,而它的时序是由名字和一组标签构成的,其实名字也可以写出标签的形式,

例如 http_requests_total 等价于 {name="http_requests_total"}。

一个简单的查询相当于是对各种标签的筛选,

http_requests_total{code="200"} 
// 表示查询名字为 http_requests_total,code 为 "200" 的数据

查询条件支持正则匹配,

3.2.1操作符

Prometheus 查询语句中,支持常见的各种表达式操作符。例如:

  • 算术运算符:+,-,*,/,%,
  • 比较运算符:==,!=,>,<,>=,<=
  • 逻辑运算符:and,or,unless
  • 聚合运算符: sum,min,max,avg,stddev,stdvar,count,count_values,bottomk,topk,quantile
注意,和四则运算类型,Prometheus 的运算符也有优先级,它们遵从(^)> (*, /, %) > (+, -) > (==, !=, <=, <, >=, >) > (and, unless) > (or) 的原则。

3.2.2内置函数

Prometheus 内置不少函数,方便查询以及数据格式化,详情参考内置函数

3.2.3与MySql对比

经过与MySql对比发现,常用查询和统计方面,PromQL 比 MySQL 简单和丰富很多,而且查询性能也高不少。

4.Exporter

在 Prometheus 中负责数据汇报的程序统一叫做 Exporter, 而不同的 Exporter 负责不同的业务。 它们具有统一命名格式,即

xx_exporter, 例如负责主机信息收集的 node_exporter。

Prometheus 社区已经提供了很多 exporter, 详情请参考这里

4.1文本格式

  • Exporter 本质上就是提供 http 请求并将收集的数据,转化为对应的文本格式。
  • Exporter 收集的数据转化的文本内容以行 (\n) 为单位,空行将被忽略, 文本内容最后一行为空行。
  • 文本内容,如果以 # 开头通常表示注释。
    • 以 # HELP 开头表示 metric 帮助说明。

    • 以 # TYPE 开头表示定义 metric 类型,包含 counter, gauge, histogram, summary和untyped 类型。

    • 其他表示一般注释,供阅读使用,将被 Prometheus 忽略。

    • 内容如果不以 # 开头,表示采样数据。它通常紧挨着类型定义行,满足以下格式:

        metric_name [{label_name = "label_value",label_name ="label_value"}] value [ timestamp ]
      

这里有一个完整的例子

# HELP http_requests_total The total number of HTTP requests.
# TYPE http_requests_total counter
http_requests_total{method="post",code="200"} 1027 1395066363000
http_requests_total{method="post",code="400"}    3 1395066363000

# Escaping in label values:
msdos_file_access_time_seconds{path="C:\\DIR\\FILE.TXT",error="Cannot find file:\n\"FILE.TXT\""} 1.458255915e9

# Minimalistic line:
metric_without_timestamp_and_labels 12.47

# A weird metric from before the epoch:
something_weird{problem="division by zero"} +Inf -3982045

# A histogram, which has a pretty complex representation in the text format:
# HELP http_request_duration_seconds A histogram of the request duration.
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.05"} 24054
http_request_duration_seconds_bucket{le="0.1"} 33444
http_request_duration_seconds_bucket{le="0.2"} 100392
http_request_duration_seconds_bucket{le="0.5"} 129389
http_request_duration_seconds_bucket{le="1"} 133988
http_request_duration_seconds_bucket{le="+Inf"} 144320
http_request_duration_seconds_sum 53423
http_request_duration_seconds_count 144320

# Finally a summary, which has a complex representation, too:
# HELP rpc_duration_seconds A summary of the RPC duration in seconds.
# TYPE rpc_duration_seconds summary
rpc_duration_seconds{quantile="0.01"} 3102
rpc_duration_seconds{quantile="0.05"} 3272
rpc_duration_seconds{quantile="0.5"} 4773
rpc_duration_seconds{quantile="0.9"} 9001
rpc_duration_seconds{quantile="0.99"} 76656
rpc_duration_seconds_sum 1.7560473e+07
rpc_duration_seconds_count 2693

需要特别注意的是,假设采样数据 metric 叫做 x, 如果 x 是 histogram 或 summary 类型必需满足以下条件:

  • 采样数据的总和应表示为 x_sum。
  • 采样数据的总量应表示为 x_count。
  • summary 类型的采样数据的 quantile 应表示为 x{quantile=“y”}。
  • histogram 类型的采样分区统计数据将表示为 x_bucket{le=“y”}。、
  • histogram 类型的采样必须包含 x_bucket{le=“+Inf”}, 它的值等于 x_count 的值。
  • summary 和 historam 中 quantile 和 le 必需按从小到大顺序排列。

4.2 Node_Exporter

Node_Exporter主要用于 *NIX 系统监控, 用 Golang 编写。

4.2.1Node_Exporter的安装与启动

在下载页面中下载相应的二进制安装包。下载并解压成功后,我们可以使用 ./node_exporter -h 查看运行选项,./node_exporter 运行 Node Exporter, 如果看到类似输出,表示启动成功。

INFO[0000] Starting node_exporter (version=0.14.0, branch=master, revision=840ba5dcc71a084a3bc63cb6063003c1f94435a6) source="node_exporter.go:140"
INFO[0000] Build context (go=go1.7.5, user=root@bb6d0678e7f3, date=20170321-12:13:32)  source="node_exporter.go:141"
INFO[0000] No directory specified, see --collector.textfile.directory  source="textfile.go:57"
INFO[0000] Enabled collectors:                           source="node_exporter.go:160"
.....
INFO[0000] Listening on :9100                            source="node_exporter.go:186"
4.2.2 数据存储
我们可以利用 Prometheus 的static_configs来拉取 node_exporter 的数据。
打开 prometheus.yml 文件, 在 scrape_configs 中添加如下配置:
- job_name: "node"
static_configs:
  - targets: ["127.0.0.1:9100"]

重启加载配置,然后到 Prometheus Console 查询,你会看到 node_exporter 的数据。

4.2.3Node Exporter 常用查询语句

收集到 node_exporter 的数据后,我们可以使用 PromQL 进行一些业务查询和监控,下面是一些比较常见的查询。

注意:以下查询均以单个节点作为例子,如果大家想查看所有节点,将 instance=“xxx” 去掉即可。

  • CPU 使用率

      100 - (avg by (instance) (irate(node_cpu{instance="xxx", mode="idle"}[5m])) * 100)
    
  • CPU 各 mode 占比率

      avg by (instance, mode) (irate(node_cpu{instance="xxx"}[5m])) * 100
    
  • 机器平均负载

      node_load1{instance="xxx"} // 1分钟负载
      node_load5{instance="xxx"} // 5分钟负载
      node_load15{instance="xxx"} // 15分钟负载
    
  • 内存使用率

      100 - ((node_memory_MemFree{instance="xxx"}+node_memory_Cached{instance="xxx"}+node_memory_Buffers{instance="xxx"})/node_memory_MemTotal) * 100
    
  • 磁盘使用率

      100 - node_filesystem_free{instance="xxx",fstype!~"rootfs|selinuxfs|autofs|rpc_pipefs|tmpfs|udev|none|devpts|sysfs|debugfs|fuse.*"} / node_filesystem_size{instance="xxx",fstype!~"rootfs|selinuxfs|autofs|rpc_pipefs|tmpfs|udev|none|devpts|sysfs|debugfs|fuse.*"} * 100
    
  • 网络 IO

      // 上行带宽
      sum by (instance) (irate(node_network_receive_bytes{instance="xxx",device!~"bond.*?|lo"}[5m])/128)
    
      // 下行带宽
      sum by (instance) (irate(node_network_transmit_bytes{instance="xxx",device!~"bond.*?|lo"}[5m])/128)
    
  • 网卡出/入包

      // 入包量
      sum by (instance) (rate(node_network_receive_bytes{instance="xxx",device!="lo"}[5m]))
    
      // 出包量
      sum by (instance) (rate(node_network_transmit_bytes{instance="xxx",device!="lo"}[5m]))
    

4.3 总结

  • node exporter是混合态的,不同的模块的处理方式也不尽相同。对于mdadm,我们必须手动解析文件,并获取我们想要的度量指标,所以我们可以在需要的时候获取度量指标。对于meminfo,不同的内核版本命名可能会有变化,所以我们需要创建有效的度量指标,以适应不同内核版本的内存使用情况。
  • 当导出器和应用程序一起工作时,你的导出器目标是尽量不要让用户使用自定义配置,而是尽可能地使用默认配置就ok了。你也可以给你的导出器提供过滤度量指标的支持,因为有些度量指标是非常多且琐碎,也有些需要花费很大的代价才能获取度量指标,这些都可以进行过滤(例如:haproxy exporter允许过滤每个服务器的统计信息),类似地,默认配置可以直接禁用获取代价比较的度量指标。
  • 度量指标名称永远不要程序化的生成,除非在编写自定义收集器或者导出器时。
  • 应用程序的度量指标名称应该以exporter名称为前缀,如haproxy_up。
  • 为了把度量指标推送到检测系统中,一个给定的度量指标应该在一个指定的文件中存在。根据导出器和收集器,一个度量指标名称应该应用在一个子系统中
  • 度量指标名称不应该包括他们导出的labels(如:by_type)如果标签聚在一起,就没有意义。
  • 暴露的度量指标不应包含冒号,这些用于聚合时可供用户使用。度量指标名称只有符合正则表达式[a-zA-Z0-9:]是有效的,任何其他字符串都需要被改成""下划线
  • _sum, _count, _bucket, 和_total用于Summaries,Histogram和Counters的后缀。如果你正好要使用这些统计则可以使用这些后缀,否则避免使用这些后缀。
  • 如果你正在使用COUNTER类型,则应该使用_total作为度量指标的后缀。
  • 当你统计请求的成功数量和失败数量时,最佳方法是暴露两个度量指标,一个是总的请求数,另一个度量指标是失败的请求度量指标。这使得计算失败比率变得很容易。不要使用带有failed/success的标签。类似于缓存中的hit/miss,有一个总的度量指标和另一个hits的度量指标是更好的。
  • 避免把type做为标签名称,它太通用化,而且无意义。你也应该试着尽可能地避免与目标标签冲突,例如:region, zone, cluster, availability, az, datacenter, dc, owner, customer, stage, environment和env, 尽管这是应用程序调用的东西,但是最好不要通过重命名造成混乱。
  • 标签le对于Histograms有特殊的含义。同样对于Summaries来说,quantile也是如此。避免使用这些标签。

5.配置说明

prometheus的配置文件中比较重要的是prometheus.yml,文件大致内容如下:

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.
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']

该配置文件中比较重要的是以下几个部分:

  • global:全局的配置,如果后面的任务没有对特定配置项进行覆盖,这里的选项会生效。
    • scrape_interval: 拉取 targets 的默认时间间隔。如果太频繁会导致 promethues 压力比较大,如果太久,可能会导致某些关键数据漏掉,推荐根据每个任务的重要性和集群规模分别进行配置。
    • scrape_timeout: 拉取一个 target 的超时时间。
    • evaluation_interval: 执行 rules 的时间间隔。
    • external_labels: 额外的属性,会添加到拉取的数据并存到数据库中。
  • scrape__configs:主要用于配置拉取数据节点,每一个拉取配置主要包含以下参数:
    • job_name:任务名称
    • honor_labels: 用于解决拉取数据标签有冲突,当设置为 true, 以拉取数据为准,否则以服务配置为准
    • params:数据拉取访问时带的请求参数
    • scrape_interval: 拉取时间间隔
    • scrape_timeout: 拉取超时时间
    • metrics_path: 拉取节点的 metric 路径
    • scheme: 拉取数据访问协议
    • sample_limit: 存储的数据标签个数限制,如果超过限制,该数据将被忽略,不入存储;默认值为0,表示没有限制
    • relabel_configs: 拉取数据重置标签配置

上述例子中可以配置多中抓取任务,因此是一个列表,这里我们只有一个任务,那就是抓取 promethues 本身的 metrics。配置里面最重要的是 static_configs.targets,表示要抓取任务的 HTTP 地址,默认会在 /metrics url 出进行抓取,比如这里就是 http://localhost:9090/。 这是 prometheus 本身提供的监控数据,可以在浏览器中直接查看。

  • 告警配置
      通常我们可以使用运行参数 -alertmanager.xxx 来配置 Alertmanager, 但是这样不够灵活,没有办法做到动态更新加载,以及动态定义告警属性。
      所以 alerting 配置主要用来解决这个问题,它能够更好的管理 Alertmanager, 主要包含 2 个参数:
    • alert_relabel_configs: 动态修改 alert 属性的规则配置。
    • alertmanagers: 用于动态发现 Alertmanager 的配置。

6.collector

我们先来看下采集器Collector接口的实现

type Collector interface {
    // 用于传递所有可能的指标的定义描述符
    // 可以在程序运行期间添加新的描述,收集新的指标信息
    // 重复的描述符将被忽略。两个不同的Collector不要设置相同的描述符
    Describe(chan<- *Desc)

    // Prometheus的注册器调用Collect执行实际的抓取参数的工作,
    // 并将收集的数据传递到Channel中返回
    // 收集的指标信息来自于Describe中传递,可以并发的执行抓取工作,但是必须要保证线程的安全。
    Collect(chan<- Metric)
}

有很多数据类型实现了这个接口

Gauge

type Gauge interface {
    Metric
    Collector

    // Set将标尺设置为任意值。
    Set(float64)
    //Inc将测量值增加1。
    Inc()
    // dec将测量值减1
    Dec()
    // Add adds the given value to the Gauge. (The value can be
    // negative, resulting in a decrease of the Gauge.)
    Add(float64)
    // Sub subtracts the given value from the Gauge. (The value can be
    // negative, resulting in an increase of the Gauge.)
    Sub(float64)
}

Counter

type Counter interface {
    Metric
    Collector

    // Set is used to set the Counter to an arbitrary value. It is only used
    // if you have to transfer a value from an external counter into this
    // Prometheus metric. Do not use it for regular handling of a
    // Prometheus counter (as it can be used to break the contract of
    // monotonically increasing values).
    //
    // Deprecated: Use NewConstMetric to create a counter for an external
    // value. A Counter should never be set.
    Set(float64)
    // Inc increments the counter by 1.
    Inc()
    // Add adds the given value to the counter. It panics if the value is <
    // 0.
    Add(float64)
}

###Histogram
type Histogram interface {
Metric
Collector

    // Observe adds a single observation to the histogram.
    Observe(float64)
}

###Summary
type Summary interface {
Metric
Collector

    // Observe adds a single observation to the summary.
    Observe(float64)
}

.

详细过程参见collector源码解析