Prometheus是什么?
Prometheus 是由 SoundCloud 开源监控告警解决方案,从 2012 年开始编写代码,再到 2015 年 github 上开源以来,已经吸引了 9k+ 关注,以及很多大公司的使用;2016 年 Prometheus 成为继 k8s 后,第二名 CNCF(Cloud Native Computing Foundation) 成员。
主要功能
- 多维 数据模型(时序由 metric 名字和 k/v 的 labels 构成)。
- 灵活的查询语句(PromQL)。
- 无依赖存储,支持 local 和 remote 不同模型。
- 采用 http 协议,使用 pull 模式,拉取数据,简单易懂。
- 监控目标,可以采用服务发现或静态配置的方式。
- 支持多种统计数据模型,图形化友好。
核心组件
- Prometheus Server, 主要用于抓取数据和存储时序数据,另外还提供查询和 Alert Rule 配置管理。
- client libraries,客户端库,为需要监控的服务生成相应的 metrics 并暴露给 Prometheus server。当 Prometheus server 来 pull 时,直接返回实时状态的 metrics,比如Java Client。
- push gateway ,用于批量,短期的监控数据的汇总节点,主要用于业务数据汇报等。
- 各种汇报数据的 exporters ,下面是两种解释:
- 用于暴露已有的第三方服务的 metrics 给 Prometheus,例如汇报机器数据的 node_exporter, 汇报 MongoDB 信息的 MongoDB exporter 等等。
- 在 Prometheus 中负责数据汇报的程序统一叫做 Exporter, 而不同的 Exporter 负责不同的业务。 它们具有统一命名格式,即 xx_exporter, 例如负责主机信息收集node_exporter。Prometheus 社区已经提供了很多 exporter, 详情请参考这里。
- Alertmanager:从 Prometheus server 端接收到 alerts 后,会进行去除重复数据,分组,并路由到对收的接受方式,发出报警。常见的接收方式有:电子邮件 ,OpsGenie, webhook 等。
Prometheus基础架构
架构逻辑
- Prometheus server 定期从静态配置的 targets 或者服务发现的 targets 拉取数据。
- 当新拉取的数据大于配置内存缓存区的时候,Prometheus 会将数据持久化到磁盘(如果使用 remote storage 将持久化到云端)。
- Prometheus 可以配置 rules,然后定时查询数据,当条件触发的时候,会将 alert 推送到配置的 Alertmanager。
- Alertmanager 收到警告的时候,可以根据配置,聚合,去重,降噪,最后发送警告。
- 可以使用 API, Prometheus Console 或者 Grafana 查询和聚合数据。
Metric类型
Prometheus定义了4中不同的指标类型(metric type):Counter(计数器)、Gauge(仪表盘)、Histogram(直方图)、Summary(摘要)。
Counter:只增不减的计数器
Counter类型的指标其工作方式和计数器一样,只增不减(除非系统发生重置)。常见的监控指标,如http_requests_total。一般在定义Counter类型指标的名称时推荐使用_total作为后缀。
Gauge:可增可减的仪表盘
auge类型的指标侧重于反应系统的当前状态。因此这类指标的样本数据可增可减。常见指标如:node_memory_MemFree(主机当前空闲的内容大小)、node_memory_MemAvailable(可用内存大小)都是Gauge类型的监控指标。
Summary 摘要
- 类似于 Histogram, 典型的应用如:请求持续时间,响应大小;
- 提供观测值的 count 和 sum 功能;
- 提供百分位的功能,即可以按百分比划分跟踪结果。
举例:
# 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
Histogram指标直接反应了在不同区间内样本的个数,区间通过标签len进行定义,sum是总数,count是次数。
Summary与Histogram不同
不同在于Histogram通过histogram_quantile函数是在服务器端计算的分位数。 而Sumamry的分位数则是直接在客户端计算完成。因此对于分位数的计算而言,Summary在通过PromQL进行查询时有更好的性能表现,而Histogram则会消耗更多的资源。反之对于客户端而言Histogram消耗的资源更少。在选择这两种方式时用户应该按照自己的实际场景进行选择。
PromQL
PromQL是Prometheus内置的数据查询语言,其提供对时间序列数据丰富的查询,聚合以及逻辑运算能力的支持。并且被广泛应用在Prometheus的日常应用当中,包括对数据查询、可视化、告警处理当中。
有兴趣可以PromQL学习。
Prometheus报警
Prometheus报警简单可以分为:
- 定义AlertRule(告警规则),告警规则实际上主要由PromQL进行定义,其实际意义是当表达式(PromQL)查询结果持续多长时间(During)后出发告警;
- AlertManager(报警管理),Alertmanager作为一个独立的组件,负责接收并处理来自Prometheus Server(也可以是其它的客户端程序)的告警信息。
自定义Prometheus告警规则
groups:
- name: example
rules:
- alert: HighErrorRate # 告警规则的名称
expr: job:request_latency_seconds:mean5m{job="myjob"} > 0.5 # 基于PromQL表达式告警触发条件,用于计算是否有时间序列满足该条件。
for: 10m # 评估等待时间,可选参数。用于表示只有当触发条件持续一段时间后才发送告警。在等待期间新产生告警的状态为pending。
labels:
severity: page # 自定义标签,允许用户指定要附加到告警上的一组附加标签
annotations:
summary: High request latency
description: description info
AlertManager
- 分组,分组机制可以将详细的告警信息合并成一个通知。在某些情况下,比如由于系统宕机导致大量的告警被同时触发,在这种情况下分组机制可以将这些被触发的告警合并为一个告警通知,避免一次性接受大量的告警通知,而无法对问题进行快速定位。
- 抑制,抑制是指当某一告警发出后,可以停止重复发送由此告警引发的其它告警的机制。
- 静默,静默提供了一个简单的机制可以快速根据标签对告警进行静默处理。如果接收到的告警符合静默的配置,Alertmanager则不会发送告警通知。
集群及高可用
功能分区(高并发)
考虑另外一种极端情况(网上有人说一台prometheus可以支持一千个左右节点),即单个采集任务的Target数也变得非常巨大。这时简单通过联邦集群进行功能分区,Prometheus Server也无法有效处理时。这种情况只能考虑继续在实例级别进行功能划分。
global:
external_labels:
slave: 1 # This is the 2nd slave. This prevents clashes between slaves.
scrape_configs:
- job_name: some_job
relabel_configs:
- source_labels: [__address__] # IP
modulus: 4 # 取模
target_label: __tmp_hash # 临时的label
action: hashmod # 使用取模方法
- source_labels: [__tmp_hash]
regex: ^1$ # 取哪个模
action: keep # 可用
在微服务平台,我们使用IP取模奇偶数的形式来过去微服务系统的监控数据。
联邦集群
这种部署方式一般适用于两种场景:
场景一:单数据中心 + 大量的采集任务
这种场景下Promthues的性能瓶颈主要在于大量的采集任务,因此用户需要利用Prometheus联邦集群的特性,将不同类型的采集任务划分到不同的Promthues子服务中,从而实现功能分区。例如一个Promthues Server负责采集基础设施相关的监控指标,另外一个Prometheus Server负责采集应用监控指标。再有上层Prometheus Server实现对数据的汇聚。
场景二:多数据中心(微服务使用)
这种模式也适合与多数据中心的情况,当Promthues Server无法直接与数据中心中的Exporter进行通讯时,在每一个数据中部署一个单独的Promthues Server负责当前数据中心的采集任务是一个不错的方式。这样可以避免用户进行大量的网络配置,只需要确保主Promthues Server实例能够与当前数据中心的Prometheus Server通讯即可。 中心Promthues Server负责实现对多数据中心数据的聚合。
Prometheus的服务发现
在 Prometheus 的配置中,一个最重要的概念就是数据源 target,而数据源的配置主要分为静态配置和动态发现, 大致为以下几类:
- static_configs: 静态服务发现
- dns_sd_configs: DNS 服务发现
- file_sd_configs: 文件服务发现
- consul_sd_configs: Consul 服务发现
- serverset_sd_configs: Serverset 服务发现
- nerve_sd_configs: Nerve 服务发现
- marathon_sd_configs: Marathon 服务发现
- kubernetes_sd_configs: Kubernetes 服务发现
- gce_sd_configs: GCE 服务发现
- ec2_sd_configs: EC2 服务发现
- openstack_sd_configs: OpenStack 服务发现
- azure_sd_configs: Azure 服务发现
- triton_sd_configs: Triton 服务发现
使用Consul进行服务发现
Prometheus exporter配置部分:
scrape_configs:
# The job name is added as a label `job=` to any timeseries scraped from this config.
- job_name: 'prometheus'
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ['localhost:9090']
- job_name: 'consul-prometheus'
consul_sd_configs:
#consul 地址
- server: '127.0.0.1:8500'
services: []
微服务平台服务发现优化
我们微服务使用Eureka并不在Prometheus的自动配置范围内,于是我们对Eureka进行二次开发,让它伪装成Consul的API接口提供给Prometheus接口。
参见源码。
使用Java自定义Export
添加拦截器,为监控埋点做准备
继承WebMvcConfigurerAdapter类,复写addInterceptors方法,对所有请求/**添加拦截器
@SpringBootApplication
@EnablePrometheusEndpoint
public class SpringApplication extends WebMvcConfigurerAdapter implements CommandLineRunner {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new PrometheusMetricsInterceptor()).addPathPatterns("/**");
}
}
PrometheusMetricsInterceptor集成HandlerInterceptorAdapter,通过复写父方法,实现对请求处理前/处理完成的处理。
public class PrometheusMetricsInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return super.preHandle(request, response, handler);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
super.afterCompletion(request, response, handler, ex);
}
}
以Gauge为例
对于Gauge指标的对象则包含两个主要的方法inc()以及dec(),用户添加或者减少计数。在这里我们使用Gauge记录当前正在处理的Http请求数量。
public class PrometheusMetricsInterceptor extends HandlerInterceptorAdapter {
...省略的代码
static final Gauge inprogressRequests = Gauge.build()
.name("io_namespace_http_inprogress_requests").labelNames("path", "method", "code")
.help("Inprogress requests.").register();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...省略的代码
// 计数器+1
inprogressRequests.labels(requestURI, method, String.valueOf(status)).inc();
return super.preHandle(request, response, handler);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
...省略的代码
// 计数器-1
inprogressRequests.labels(requestURI, method, String.valueOf(status)).dec();
super.afterCompletion(request, response, handler, ex);
}
}
参考:
Prometheus 入门与实践
Prometheus 实战
Prometheus 官网文档
Consul+Prometheus系统监控之服务发现
自定义Metrics:让Prometheus监控你的应用程序(Spring版)