Life is too bug

新式监控系统 — prometheus

2018.09.14

Prometheus 是一套开源的系统监控报警框架。它启发于 Google 的 borgmon 监控系统,由工作在 SoundCloud 的 google 前员工在 2012 年创建,作为社区开源项目进行开发,并于 2015 年正式发布。2016 年,Prometheus 正式加入 Cloud Native Computing Foundation,成为受欢迎度仅次于 Kubernetes 的项目。

Prometheus简介

作为新一代的监控框架,Prometheus 具有以下特点:

  • 强大的多维度数据模型:

    1. 时间序列数据通过 metric 名和键值对来区分。
    2. 所有的 metrics 都可以设置任意的多维标签。
    3. 数据模型更随意,不需要刻意设置为以点分隔的字符串。
    4. 可以对数据模型进行聚合,切割和切片操作。
    5. 支持双精度浮点类型,标签可以设为全 unicode。
  • 灵活而强大的查询语句(PromQL):在同一个查询语句,可以对多个 metrics 进行乘法、加法、连接、取分数位等操作。

  • 易于管理: Prometheus server 是一个单独的二进制文件,可直接在本地工作,不依赖于分布式存储。

  • 高效:平均每个采样点仅占 3.5 bytes,且一个 Prometheus server 可以处理数百万的 metrics。

  • 使用 pull 模式采集时间序列数据,这样不仅有利于本机测试而且可以避免有问题的服务器推送坏的 metrics。

  • 可以采用 push gateway 的方式把时间序列数据推送至 Prometheus server 端。

  • 可以通过服务发现或者静态配置去获取监控的 targets。

  • 有多种可视化图形界面。

  • 易于伸缩。

需要指出的是,由于数据采集可能会有丢失,所以 Prometheus 不适用对采集数据要 100% 准确的情形。但如果用于记录时间序列数据,Prometheus 具有很大的查询优势,此外,Prometheus 适用于微服务的体系架构。这样做非常适合虚拟化环境比如 VM 或者 Docker ,故其为为数不多的适合 Docker、Mesos 、Kubernetes 环境的监控系统之一,被很多人称为下一代监控系统。

pull 方式

Prometheus 采集数据用的是 pull 也就是拉模型,通过 HTTP 协议去采集指标,只要应用系统能够提供 HTTP 接口就可以接入监控系统,相比于私有协议或二进制协议来说开发简单。

push 方式

对于定时任务这种短周期的指标采集,如果采用 pull 模式,可能造成任务结束了 Prometheus 还没有来得及采集的情况,这个时候可以使用加一个中转层,客户端推数据到 Push Gateway 缓存一下,由 Prometheus 从 push gateway pull 指标过来。

组成及架构

Prometheus 生态圈中包含了多个组件,其中许多组件是可选的:

  • Prometheus Server: 用于收集和存储时间序列数据。
  • Client Library: 客户端库,为需要监控的服务生成相应的 metrics 并暴露给 Prometheus server。当 Prometheus server 来 pull 时,直接返回实时状态的 metrics。
  • Push Gateway: 主要用于短期的 jobs。由于这类 jobs 存在时间较短,可能在 Prometheus 来 pull 之前就消失了。为此,这次 jobs 可以直接向 Prometheus server 端推送它们的 metrics。这种方式主要用于服务层面的 metrics,对于机器层面的 metrices,需要使用 node exporter。
  • Exporters: 用于暴露已有的第三方服务的 metrics 给 Prometheus。
  • Alertmanager: 从 Prometheus server 端接收到 alerts 后,会进行去除重复数据,分组,并路由到对收的接受方式,发出报警。常见的接收方式有:电子邮件,pagerduty,OpsGenie, webhook 等。
  • 一些其他的工具。

图 1 为 Prometheus 官方文档中的架构图:

从上图可以看出,Prometheus 的主要模块包括:Prometheus server, exporters, Pushgateway, PromQL, Alertmanager 以及图形界面。

其大概的工作流程是:

  1. Prometheus server 定期从配置好的 jobs 或者 exporters 中拉 metrics,或者接收来自 Pushgateway 发过来的 metrics,或者从其他的 Prometheus server 中拉 metrics。
  2. Prometheus server 在本地存储收集到的 metrics,并运行已定义好的 alert.rules,记录新的时间序列或者向 Alertmanager 推送警报。
  3. Alertmanager 根据配置文件,对接收到的警报进行处理,发出告警。
  4. 在图形界面中,可视化采集数据。

Prometheus 相关概念

下面将对 Prometheus 中的数据模型,metric 类型以及 instance 和 job 等概念进行介绍,以便读者在 Prometheus 的配置和使用中可以有一个更好的理解。

数据模型

Prometheus 中存储的数据为时间序列,是由 metric 的名字和一系列的标签(键值对)唯一标识的,不同的标签则代表不同的时间序列。

  • metric 名字:该名字应该具有语义,一般用于表示 metric 的功能,例如:http_requests_total, 表示 http 请求的总数。其中,metric 名字由 ASCII 字符,数字,下划线,以及冒号组成。
  • 标签:使同一个时间序列有了不同维度的识别。例如 http_requests_total{method=“Get”} 表示所有 http 请求中的 Get 请求。当 method=“post” 时,则为新的一个 metric。标签中的键由 ASCII 字符,数字,以及下划线组成。
  • 样本:实际的时间序列,每个序列包括一个 float64 的值和一个毫秒级的时间戳。
  • 格式:{=, …},例如:http_requests_total{method=“POST”,endpoint="/api/tracks"}。

四种 Metric 类型

Prometheus 客户端库主要提供四种主要的 metric 类型:

Counter 计数器

  • 一种累加的 metric,典型的应用如:请求的个数,结束的任务数, 出现的错误数等等。

例如,查询 http_requests_total{method=“get”, job=“Prometheus”, handler=“query”} 返回 8,10 秒后,再次查询,则返回 14。

Gauge 瞬时值

  • 一种常规的 metric,典型的应用如:温度,运行的 goroutines 的个数。
  • 可以任意加减。

例如:go_goroutines{instance=“172.17.0.2”, job=“Prometheus”} 返回值 147,表示有147个goroutine

Histogram 直方图

这种主要用来统计百分位的,什么是百分位?英文叫做quantiles。比如你有100条访问请求的耗时时间,把它们从小到大排序,第90个时间是200ms,那么我们可以说90%的请求都小于200ms,这也叫做”90分位是200ms”,能够反映出服务的基本质量。当然,也许第91个时间是2000ms,这就没法说了。实际情况是,我们每天访问量至少几个亿,不可能把所有访问数据都存起来,然后排序找到90分位的时间是多少。因此,类似这种问题都采用了一些估算的算法来处理,不需要把所有数据都存下来,这里面数学原理比较高端,我们就直接看看prometheus的用法好了。

  • 可以理解为柱状图,典型的应用如:请求持续时间,响应大小。
  • 可以对观察结果采样,分组及统计。

Summary

  • 类似于 Histogram, 典型的应用如:请求持续时间,响应大小。
  • 提供观测值的 count 和 sum 功能。
  • 提供百分位的功能,即可以按百分比划分跟踪结果。

比如go_gc_duration_seconds可以得到go gc 25%,50%,75%的持续时间

summary已经完成了计算,由prom的服务端完成,如下所示:

# HELP prometheus_engine_query_duration_seconds Query timings
# TYPE prometheus_engine_query_duration_seconds summary
prometheus_engine_query_duration_seconds{slice="inner_eval",quantile="0.5"} 0.000300242
prometheus_engine_query_duration_seconds{slice="inner_eval",quantile="0.9"} 0.016269568
prometheus_engine_query_duration_seconds{slice="inner_eval",quantile="0.99"} 0.027198073
prometheus_engine_query_duration_seconds_sum{slice="inner_eval"} 0.080493713
prometheus_engine_query_duration_seconds_count{slice="inner_eval"} 21
prometheus_engine_query_duration_seconds{slice="prepare_time",quantile="0.5"} 7.2256e-05
prometheus_engine_query_duration_seconds{slice="prepare_time",quantile="0.9"} 0.000894565
prometheus_engine_query_duration_seconds{slice="prepare_time",quantile="0.99"} 0.001640657
prometheus_engine_query_duration_seconds_sum{slice="prepare_time"} 0.004723942
prometheus_engine_query_duration_seconds_count{slice="prepare_time"} 21

与summary不同的是histogram只负责存储数据,客户端读取之后进行计算。

# HELP prometheus_http_request_duration_seconds Histogram of latencies for HTTP requests.
# TYPE prometheus_http_request_duration_seconds histogram
prometheus_http_request_duration_seconds_bucket{handler="/api/v1/query",le="0.1"} 0  # 小于0.1的有0个
prometheus_http_request_duration_seconds_bucket{handler="/api/v1/query",le="0.2"} 11
prometheus_http_request_duration_seconds_bucket{handler="/api/v1/query",le="0.4"} 11
prometheus_http_request_duration_seconds_bucket{handler="/api/v1/query",le="1"} 11
prometheus_http_request_duration_seconds_bucket{handler="/api/v1/query",le="3"} 11
prometheus_http_request_duration_seconds_bucket{handler="/api/v1/query",le="8"} 11
prometheus_http_request_duration_seconds_bucket{handler="/api/v1/query",le="20"} 11
prometheus_http_request_duration_seconds_bucket{handler="/api/v1/query",le="60"} 11
prometheus_http_request_duration_seconds_bucket{handler="/api/v1/query",le="120"} 11
prometheus_http_request_duration_seconds_bucket{handler="/api/v1/query",le="+Inf"} 11 # 无穷大的时候近似等于请求总数
prometheus_http_request_duration_seconds_sum{handler="/api/v1/query"} 0.033323976000000005
prometheus_http_request_duration_seconds_count{handler="/api/v1/query"} 11 # 一共有11个请求

histogram_quantile(0.95, sum(rate(prometheus_http_request_duration_seconds_bucket{handler="/api/v1/query_range"}[5m])) by (le))

Prometheus配置

配置文件可以分为6个部分,如下所示:

# 全局配置
global:
  scrape_interval:     15s # 默认抓取间隔, 15秒向目标抓取一次数据。
  evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
  # scrape_timeout is set to the global default (10s).

  # 这个标签是在本机上每一条时间序列上都会默认产生的,主要可以用于联合查询、远程存储、Alertmanger时使用。
  external_labels:
      monitor: 'codelab-monitor'
      cluster:  'prod-k8s'

# 加载ruls文件
rule_files:
  # - "first.rules"
  # - "second.rules"

# 这里就表示抓取对象的配置
# 这里是抓去promethues自身的配置
scrape_configs:
# job name 这个配置是表示在这个配置内的时间序例,每一条都会自动添加上这个{job_name:"prometheus"}的标签。
  - job_name: 'prometheus'

    # metrics_path defaults to '/metrics'
    # scheme defaults to 'http'.

    # 重写了全局抓取间隔时间,由15秒重写成5秒。
    scrape_interval: 5s

    static_configs:
      - targets: ['localhost:9090']
# 与Alertmanager相关的设置。
alerting:
  alert_relabel_configs:
    [ - <relabel_config> ... ]
  alertmanagers:
    [ - <alertmanager_config> ... ]

# 将数据写到远程
remote_write:
  [ - <remote_write> ... ]

# 读取远程的数据
remote_read:
  [ - <remote_read> ... ]      

scrape_configs 主要用于配置拉取数据节点,每一个拉取配置主要包含以下参数:

  • job_name:任务名称
  • honor_labels: 用于解决拉取数据标签有冲突,当设置为 true, 以拉取数据为准,否则以服务配置为准
  • params:数据拉取访问时带的请求参数
  • scrape_interval: 拉取时间间隔
  • scrape_timeout: 拉取超时时间
  • metrics_path: 拉取节点的 metric 路径
  • scheme: 拉取数据访问协议
  • sample_limit: 存储的数据标签个数限制,如果超过限制,该数据将被忽略,不入存储;默认值为0,表示没有限制
  • relabel_configs: 拉取数据重置标签配置
  • metric_relabel_configs:metric 重置标签配置

rules规则

当某些metrics不正常时,就需要发出告警,而rules正是做这个事情的,比如CPU连续1小时超过99%等。如下是一个request响应时间过高的rule:

groups:
- name: example
  rules:
  - alert: HighErrorRate
    expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5
    for: 10m
    labels:
      severity: page
    annotations:
      summary: High request latency
      description: description info
  - alert: ExampleAlert
    expr: vector(1)

四种数据类型

  1. Instant vector : 每个指标返回一个值,且指标集合里面的时间戳都相同.类似于即时数据.
  2. Range vector: 每个指标包含多个时序数据.
  3. Scalar: 只有一个浮点值
  4. String: 未开放使用.

Promql

二元操作

+ - * / % ^ 加减乘除 取余

二元操作符使用范围:

  1. scalar/scalar
  2. vector/scalar
  3. vector/vector

聚合操作

  1. sum (calculate sum over dimensions)
  2. min (select minimum over dimensions)
  3. max (select maximum over dimensions)
  4. avg (calculate the average over dimensions)
  5. stddev (calculate population standard deviation over dimensions)
  6. stdvar (calculate population standard variance over dimensions)
  7. count (count number of elements in the vector)
  8. count_values (count number of elements with the same value)
  9. bottomk (smallest k elements by sample value)
  10. topk (largest k elements by sample value)
  11. quantile (calculate φ-quantile (0 ≤ φ ≤ 1) over dimensions)

函数

  1. rate(v range-vector)

    根据每个点计算每秒的平均变化率.

  2. irate(v range-vector)

    根据最后两个数据点计算变化率.irate()适合于变化较快的数据,rate()适合变化较慢的数据.

常用例子:

查询CPU使用率:

100 - (avg by (job) (irate(node_cpu{mode="idle"}[5m])) * 100)

查询网卡即时速率

irate(node_network_receive_bytes[2m])
vector(1)  #始终为真,可以用来测试告警

AlertManager配置

Prometheus只会产生告警并把告警发送给AlertManager,至于什么等级的告警发送给谁,还是要在AlertManager中配置的。目前官方支持slack,企业微信和Email,钉钉可以用这个webhook的容器实现。

docker run –network host -d timonwong/prometheus-webhook-dingtalk –ding.profile=“webhook1=https://oapi.dingtalk.com/robot/send?access_token=xxxx”

配置文件示例:

global:
  # ResolveTimeout is the time after which an alert is declared resolved
  # if it has not been updated.
  [ resolve_timeout: <duration> | default = 5m ]

  # The default HTTP client configuration
  [ http_config: <http_config> ]

# Files from which custom notification template definitions are read.
# The last component may use a wildcard matcher, e.g. 'templates/*.tmpl'.
templates:
  [ - <filepath> ... ]

# The root node of the routing tree.
route: <route>
  routes:
  - receiver: 'database-pager'
    group_wait: 10s
    match_re:
      service: mysql|cassandra
  - receiver: 'frontend-pager'
    group_by: [product, environment]
    match:
      team: frontend
# A list of notification receivers.
receivers:
  - <receiver> ...

# A list of inhibition rules.
inhibit_rules:
  [ - <inhibit_rule> ... ]
--- #具体示例
global: 
  "resolve_timeout": "5m"
route: 
  group_by: ['job']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 12h
  receiver: 'slack_general'
  routes:
  - receiver: "slack_general"
    match:  
      alertname: DeadMansSwitch
  - receiver: "send_to_dingding_webhook1"
    match:  
      severity: warning
receivers: 
- name: slack_general
  slack_configs:
  - api_url: https://hooks.slack.com/services/TCXP8SPD4/BCW9QDP8T/
    channel: '#general'
- name: send_to_dingding_webhook1
  webhook_configs:
  - send_resolved: false
    url: http://localhost:8060/dingtalk/webhook1/send

Ref

Prometheus 入门

https://www.hi-linux.com/posts/25047.html

Prometheus 实战

https://songjiayang.gitbooks.io/prometheus/content/

https://yunlzheng.gitbook.io/prometheus-book/part-iii-prometheus-shi-zhan/references

comments powered by Disqus