您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
开源监控系统Prometheus的前世今生
 
作者: 郭振
  500  次浏览      50
 2020-12-10
 
编辑推荐:
本文主要介绍Prometheus,从他的来源,架构以及一个具体的例子等方面来说明,以及沃趣围绕Prometheus做了哪些工作。
本文来自于CSDN,由火龙果软件Alice编辑、推荐。

Prometheus是SoundCloud公司开源的监控系统,同时也是继Kubernetes之后,第二个加入CNCF的项目。Prometheus是一个优秀的监控系统,沃趣围绕着Prometheus先后开发了多个组件,包括基础告警组件,服务发现组件、各种采集的Exporters等,这些组件结合Prometheus支撑了沃趣大部分的监控业务。本文主要介绍Prometheus,从他的来源,架构以及一个具体的例子等方面来说明,以及沃趣围绕Prometheus做了哪些工作。

起源

SoundCloud公司的之前的应用架构是巨石架构,也就是所有的功能放在一个大的模块里,各个功能之间没有明显的界线。巨石架构的应用主要存在两方面的问题,一方面在于很难对其进行水平扩展,只能垂直扩展,但是单台机器的能力毕竟是有限的;另外一方面在于各个功能耦合在一块,新增一个功能需要在已有的技术栈上进行开发,并且要确保不会对已有的功能造成影响。于是他们转向了微服务架构,将原有的功能拆分成了几百个独立的服务,整个系统运行上千个实例。迁移到微服务架构给监控带来一定的挑战,现在不仅需要知道某个组件的运行的情况,还要知道服务的整体运行情况。他们当时的监控方案是:StatsD + Graphite + Nagios,StatsD结合Graphite构建监控图表,各个服务将样本数据推送给StatsD,StatsD将推送来的样本数据聚合在一起,定时地推送给Graphite,Graphite将样本数据保存在时序数据库中,用户根据Graphite提供的API,结合自身监控的需求,构建监控图表,通过图表分析服务的指标(例如,延迟,每秒的请求数,每秒的错误数等)。

那么这样一种方案能满足微服务架构对监控的要求么?什么要求呢:既能知道服务整体的运行情况,也能够保持足够的粒度,知道某个组件的运行情况。答案是很难,为什么呢?例如,我们要统计api-server服务响应POST /tracks请求错误的数量,指标的名称为api-server.tracks.post.500,这个指标可以通过http状态码来测量,服务响应的状态码为500就是错误的。Graphite指标名称的结构是一种层次结构,api-server指定服务的名称,tracks指定服务的handler,post指定请求的方法,500指定请求响应的状态码,api-server服务实例将该指标推送给StatsD,StatsD聚合各个实例推送来的指标,然后定时推送给Graphite。查询api-server.tracks.post.500指标,我们能获得服务错误的响应数,但是,如果我们的api-server服务跑了多个实例,想知道某个实例错误的响应数,该怎么查询呢?问题出在使用这样一种架构,往往会将各个服务实例发送来的指标聚合到一块,聚合到一起之后,实例维度的信息就丢失掉了,也就无法统计某个具体实例的指标信息。

StatsD与Graphite的组合用来构建监控图表,告警是另外一个系统-Nagios-来做的,这个系统运行检测脚本,判断主机或服务运行的是否正常,如果不正常,发送告警。Nagios最大的问题在于告警是面向主机的,每个告警的检查项都是围绕着主机的,在分布式系统的环境底下,主机down掉是正常的场景,服务本身的设计也是可以容忍节点down掉的,但是,这种场景下Nagios依然会触发告警。

对比Prometheus,你会发现这两个系统非常相似。实际上,Prometheus深受Borgmon系统的影响,并且当时参与构建Google监控系统的员工加入了SoundCloud公司。总之,种种因素的结合,促使了Prometheus系统的诞生。

Prometheus的解决方案

那么,Prometheus是如何解决上面这些问题的?之前的方案中,告警与图表的构建依赖于两个不同的系统,Prometheus采取了一种新的模型,将采集时序数据作为整个系统的核心,无论是告警还是构建监控图表,都是通过操纵时序数据来实现的。Prometheus通过指标的名称以及label(key/value)的组合来识别时序数据,每个label代表一个维度,可以增加或者减少label来控制所选择的时序数据,前面提到,微服务架构底下对监控的要求:既能知道服务整体的运行情况,也能够保持足够的粒度,知道某个组件的运行情况。借助于这种多维度的数据模型可以很轻松的实现这个目标,还是拿之前那个统计http错误响应的例子来说明,我们这里假设api_server服务有三个运行的实例,Prometheus采集到如下格式的样本数据(其中intance label是Prometheus自动添加上去的):

api_server_http_requests_total{method="POST",
handler="/tracks",status="500",instance="sample1"} -> 34
api_server_http_requests_total{method="POST",
handler="/tracks",status="500",instance="sample2"} -> 28
api_server_http_requests_total{method="POST",
handler="/tracks",status="500",instance="sample3"} -> 31

如果我们只关心特定实例的错误数,只需添加instance label即可,例如我们想要查看实例名称为sample1的错误的请求数,那么我就可以用apiserverhttprequeststotal { method="POST",handler="/tracks" , status="500",instance="sample1" } 这个表达式来选择时序数据,选择的数据如下:

api_server_http_requests_total{method="POST",
handler="/tracks",status="500",instance="sample1"} -> 34

如果我们关心整个服务的错误数,只需忽略instance label去除,然后将结果聚合到一块,即可,例如 sum without(instance) ( apiserverhttprequeststotal {method = "POST", handler =" /tracks",status="500" }) 计算得到的时序数据为:

api_server_http_requests_total{method="POST",
handler="/tracks",status="500"} -> 93

告警是通过操纵时序数据而不是运行一个自定义的脚本来实现的,因此,只要能够采集到服务或主机暴露出的指标数据,那么就可以告警。

架构

我们再来简单的分析一下Prometheus的架构,看一下各个组件的功能,以及这些组件之间是如何交互的。

Prometheus Server是整个系统的核心,它定时地从监控目标(Exporters)暴露的API中拉取指标,然后将这些数据保存到时序数据库中,如果是监控目标是动态的,可以借助服务发现的机制动态地添加这些监控目标,另外它还会暴露执行PromQL(用来操纵时序数据的语言)的API,其他组件,例如Prometheus Web,Grafana可以通过这个API查询对应的时序数据。Prometheus Server会定时地执行告警规则,告警规则是PromQL表达式,表达式的值是true或false,如果是true,就将产生的告警数据推送给alertmanger。告警通知的聚合、分组、发送、禁用、恢复等功能,并不是Prometheus Server来做的,而是Alertmanager来做的,Prometheus Server只是将触发的告警数据推送给Alertmanager,然后Alertmanger根据配置将告警聚合到一块,发送给对应的接收人。

如果我们想要监控定时任务,想要instrument任务的执行时间,任务执行成功还是失败,那么如何将这些指标暴露给Prometheus Server?例如每隔一天做一次数据库备份,我们想要知道每次备份执行了多长时间,备份是否成功,我们备份任务只会执行一段时间,如果备份任务结束了,Prometheus Server该如何拉取备份指标的数据呢?解决这种问题,可以通过Prometheus的pushgateway组件来做,每个备份任务将指标推送pushgateway组件,pushgateway将推送来的指标缓存起来,Prometheus Server从Pushgateway中拉取指标。

例子

前面都是从比较大的层面——背景、架构——来介绍Prometheus,现在,让我们从一个具体的例子出发,来看一下如何借助Prometheus来构建监控图表、分析系统性能以及告警。

我们有个服务,暴露出四个API,每个API只返回一些简单的文本数据,现在,我们要对这个服务进行监控,希望借助监控能够查看、分析服务的请求速率,请求的平均延迟以及请求的延迟分布,并且当应用的延迟过高或者不可访问时能够触发告警,代码示例如下:

package main
import (
"math/rand"
"net/http"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
Latency = promauto.NewHistogramVec(prometheus.HistogramOpts{
Help: "latency of sample app",
Name: "sample_app_latency_milliseconds",
Buckets: prometheus.ExponentialBuckets(10, 2, 9),
}, []string{"handler", "method"})
)
func instrumentationFilter(f http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
now := time.Now()
f(writer, request)
duration := time.Now().Sub(now)
Latency.With(prometheus.Labels{"handler": request.URL.Path, "method": request.Method}).
Observe(float64(duration.Nanoseconds()) / 1e6)
}
}
// jitterLatencyFilter make request latency between d and d*maxFactor
func jitterLatencyFilter(d time.Duration, maxFactor float64,
f http.HandlerFunc) http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) {
time.Sleep(d + time.Duration(rand.Float64()*maxFactor*float64(d)))
f(writer, request)
}
}
func main() {
rand.Seed(time.Now().UnixNano())
http.Handle("/metrics", promhttp.Handler())
http.Handle("/a", instrumentationFilter(jitterLatencyFilter
(10*time.Millisecond, 256, func(w http.ResponseWriter,
r *http.Request) { w.Write([]byte("success"))
})))
http.Handle("/b", instrumentationFilter(jitterLatencyFilter
(10*time.Millisecond, 128, func(w http.ResponseWriter,
r *http.Request) { w.Write([]byte("success"))
}))) http.Handle("/c", instrumentationFilter(jitterLatencyFilter
(10*time.Millisecond, 64, func(w http.ResponseWriter,
r *http.Request) { w.Write([]byte("success"))
})))
http.Handle("/d", instrumentationFilter(jitterLatencyFilter
(10*time.Millisecond, 32, func(w http.ResponseWriter,
r *http.Request) { w.Write([]byte("success"))
})))
http.ListenAndServe(":5001", nil)
}

我们按照instrumentation、exposition、collection、query这样的流程构建监控系统,instrumentation关注的是如何测量应用的指标,有哪些指标需要测量;exposition关注的是如何通过http协议将指标暴露出来;collection关注的是如何采集指标;query关注的是如何构建查询时序数据的PromQL表达式。我们首先从instrumentation这里,有四个指标是我们关心的:

1.请求速率

2.请求的平均延迟

3.请求的延迟分布

4.访问状态

var (
Latency = promauto.NewHistogramVec(prometheus.HistogramOpts{
Help: "latency of sample app",
Name: "sample_app_latency_milliseconds",
Buckets: prometheus.ExponentialBuckets(10, 2, 9),
}, []string{"handler", "method"})
)

首先将指标注册进来,然后追踪、记录指标的值。用Prometheus提供的golang客户端库可以方便的追踪、记录指标的值,我们将instrumentation code放到应用的代码里,每次请求,对应的指标状态的值就会被记录下来。

client golang提供了四种指标类型,分别为Counter, Gauge, Histogram, Summary,Counter类型的指标用来测量只会增加的值,例如服务的请求数;Gauge类型的指标用来测量状态值,即可以变大,也可以变小的值,例如请求的延迟时间;Histogram与Summary指标类似,这两个指标取样观察的值,记录值的分布,统计观察值的数量,累计观察到的值,可以用它来统计样本数据的分布。为了采集请求速率、平均延迟以及延迟分布指标,方便起见用Histogram类型的指标追踪、记录每次请求的情况,Histogram类型的指标与普通类型(Counter、Gauge)不同的地方在于会生成多条样本数据,一个是观察样本的总数,一个是观察样本值的累加值,另外是一系列的记录样本百分位数的样本数据。访问状态可以使用up指标来表示,每次采集时,Prometheus会将采集的健康状态记录到up指标中。

http.Handle("/metrics", promhttp.Handler())

instrumentation完成之后,下一步要做的就是exposition,只需将 Prometheus http handler 添加进来,指标就可以暴露出来。访问这个Handler返回的样本数据如下(省略了一些无关的样本数据):

sample_app_latency_milliseconds_bucket{handler="/d",
method="GET",le="10"} 0
sample_app_latency_milliseconds_bucket{handler="/d",
method="GET",le="20"} 0
sample_app_latency_milliseconds_bucket{handler="/d",
method="GET",le="40"} 0
sample_app_latency_milliseconds_bucket{handler="/d",
method="GET",le="80"} 0
sample_app_latency_milliseconds_bucket{handler="/d",
method="GET",le="160"} 0
sample_app_latency_milliseconds_bucket{handler="/d",
method="GET",le="320"} 0
sample_app_latency_milliseconds_bucket{handler="/d",
method="GET",le="640"} 1
sample_app_latency_milliseconds_bucket{handler="/d",
method="GET",le="1280"} 1
sample_app_latency_milliseconds_bucket{handler="/d",
method="GET",le="2560"} 1
sample_app_latency_milliseconds_bucket{handler="/d",
method="GET",le="+Inf"} 1
sample_app_latency_milliseconds_sum{handler="/d",
method="GET"} 326.308075
sample_app_latency_milliseconds_count{handler="/d",
method="GET"} 1

仅仅将指标暴露出来,并不能让prometheus server来采集指标,我们需要进行第三步collection,配置prometheus server发现我们的服务,从而采集服务暴露出的样本数据。我们简单地看下prometheus server的配置,其中,global指定采集时全局配置, scrape_interval指定采集的间隔, evaluation_interval指定 alerting rule(alerting rule是PromQL表达式,值为布尔类型,如果为true就将相关的告警通知推送给Alertmanager)也就是告警规则的求值时间间隔,scrape_timeout指定采集时的超时时间;alerting指定Alertmanager服务的地址;scrape_configs指定如何发现监控对象,其中jobname指定发现的服务属于哪一类,staticconfigs指定服务静态的地址,前面我们也提到,Prometheus支持动态服务发现,例如文件、kubernetes服务发现机制,这里我们使用最简单的静态服务发现机制。

# my global config
global:
scrape_interval: 2s # 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).
rule_files:
- rule.yaml
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets:
- localhost:9093
scrape_configs:
- job_name: sample-app
scrape_interval: 3s
static_configs:
- targets:
- sample:5001

采集完指标,就可以利用Prometheus提供的PromQL语言来操纵采集的时序数据,例如,我们想统计请求的平均速率,可以用这个表达式 irate(sampleapplatencymillisecondssum[1m]) / irate(sampleapplatencymillisecondscount[1m])来计算。

有了时序数据之后,就可以借助Grafana来构建监控图表,具体怎么配置Grafana图表在这里就不展开了,核心点是利用PromQL表达式选择、计算时序数据。

Prometheus的告警是通过对Alerting Rule求值来实现的,alerting rule是一系列的PromQL表达式,alerting rule保存在配置文件中。我们想要对应用的延迟以及可用状态进行告警,当应用过高或者不可访问时就触发告警,规则可以如下这样定义:

- name: sample-up
rules:
- alert: UP
expr: up{instance="sample:5001"} == 0
for: 1m
labels:
severity: page
annotations:
summary: Service health
- alert: 95th-latency
expr: histogram_quantile(0.95, rate(sample_app_latency
_milliseconds_bucket[1m])) > 1000
for: 1m
labels:
severity: page
annotations:
summary: 95th service latency

其中UP指定服务的可用状态,95th-latency指定95%的请求大于1000毫秒就触发告警。Prometheus定时的对这些规则进行求值,如果条件满足,就将告警通知发送给Alertmanger,Alertmanger会根据自身路由配置,对告警进行聚合,分发到指定的接收人,我们想通过邮箱接收到告警,可以如下进行配置:

global:
smtp_smarthost: <your_smtp_server>
smtp_auth_username: <your_username>
smtp_from: <from>
smtp_auth_password: <secret>
smtp_require_tls: false
resolve_timeout: 5m
route:
receiver: me
receivers:
- name: me
email_configs:
- to: example@domain.com
templates:
- '*.tmpl'

这样,我们就可以通过邮箱收到告警邮件了。

相关的工作

无论是监控图表相关的业务,还是告警相关的业务,都离不开相关指标的采集工作,沃趣是一家做数据库产品的公司,我们花费了很多的精力去采集数据库相关的指标,从Oracle到MySQL,再到SQL Server,主流的关系型数据库的指标都有采集。对于一些通用的指标,例如操作系统相关的指标,我们主要是借助开源的Exporters来采集的。沃趣的产品是软、硬一体交付的,其中有大量硬件相关的指标需要采集,因此,我们也有专门采集硬件指标的Expoters。

沃趣大部分场景中,要监控的服务都是动态的。比如,用户从平台上申请了一个数据库,需要增加相关的监控服务,用户删除数据库资源,需要移除相关的监控服务,要监控的数据库服务处于动态的变化之中。沃趣每个产品线的基础架构都不相同,数据库服务有跑在Oracle RAC上的,有跑在ZStack的,有跑在Kubernetes上的。对于跑在Kubernetes上的应用来说,并需要担心Prometheus怎么发现要监控的服务,只需要配置相关的服务发现的机制就可以了。对于其他类型的,我们主要借助Prometheus的file_sd服务发现机制来实现,基于文件的服务发现机制是一种最通用的机制,我们将要监控的对象写到一个文件中,Prometheus监听这个文件的变动,动态的维护要监控的对象,我们在file_sd基础上构建了专门的组件去负责服务的动态更新,其他应用调用这个组件暴露的API来维护自身想要监控的对象。

Prometheus本身的机制的并不能满足我们业务上对告警的要求,一方面我们需要对告警通知进行统计,但是Alertmanager本身并没有对告警通知做持久化,服务重启之后告警通知就丢失掉了;另外一方面用户通过Web页面来配置相关的告警,告警规则以及告警通知的路由需要根据用户的配置动态的生成。为了解决这两方面的问题,我们将相关的业务功能做成基础的告警组件,供各个产品线去使用。针对Alertmanager不能持久化告警通知的问题,基础告警组件利用Alertmanager webhook的机制来接收告警通知,然后将通知保存到数据库中;另外用户的告警配置需要动态的生成,我们定义了一种新的模型来描述我们业务上的告警模型。

总结

Promtheus将采集时序数据作为整个系统的核心,无论是构建监控图表还是告警,都是通过操纵时序数据来完成的。Prometheus借助多维度的数据模型,以及强大的查询语言满足了微服务架构底下对监控的要求:既能知道服务整体的运行情况,也能够保持足够的粒度,知道某个组件的运行情况。沃趣站在巨人的肩旁上,围绕Prometheus构建了自己的监控系统,从满足不同采集要求的Exporters到服务发现,最后到基础告警组件,这些组件结合Prometheus,构成了沃趣监控系统的核心。

 
   
500 次浏览       50
相关文章

DevOps转型融入到企业文化
DevOps 能力模型、演进及案例剖析
基于 DevOps 理念的私有 PaaS 平台实践
微软开发团队的DevOps实践启示
相关文档

DevOps驱动应用运维变革与创新
运维管理规划
如何实现企业应用部署自动化
运维自动化实践之路
相关课程

自动化运维工具(基于DevOps)
互联网运维与DevOps
MySQL性能优化及运维培训
IT系统运维管理
 
最新课程计划
 
最新文章
DevOps 道法术器,立体化实施框架
DevOps 中高效测试基础架构的最佳实践
DevOps 在公司项目中的实践落地
如何基于 Kubernetes 构建完整的 DevOps 流水线
阿里云Kubernetes实战
最新课程
DevOps体系实践、工具与平台
基于Kubernetes的DevOps实践
互联网运维与DevOps
基于Kubernetes构建企业容器云
企业级DevOps工作体系与平台
更多...   
成功案例
北京 DevOps体系实践、工具与平台
神龙汽车 DevOps体系实践、工具与平台
中国移动通信 网络规划与管理
某航空公司 IT规划与企业架构
某金融公司 IT服务管理(ITIL V3)
更多...