读:本文由梯度科技云管研发部高级工程师周宇明撰写,共分为7章,紧密围绕Prometheus的基本原理与开发指南展开介绍:
1.1.监控的作用 ★
为了构建稳定性保障体系,核心就是在干一件事:减少故障。
减少故障有两个层面的意思,一个是做好常态预防,不让故障发生;另一个是如果故障发生,要能尽快止损,减少故障时长,减小故障影响范围。监控的典型作用就是帮助我们发现及定位故障。
监控的其他作用:日常巡检、性能调优的数据佐证、提前发现一些不合理的配置。
之所以能做到这些,是因为所有优秀的软件,都内置了监控数据的暴露方法,让用户可以对其进行观测,了解其健康状况:比如各类开源组件,有的是直接暴露了 Prometheus metrics 接口,有的是暴露了 HTTP JSON 接口,有的是通过 JMX 暴露监控数据,有的则需要连上去执行命令。另外,所有软件都可以使用日志的方式来暴露健康状况。
因此,可被监控和观测,也是我们开发软件时必须考虑的一环。
1.2.监控架构分类 ★
定义:可进行聚合计算的原子型数据,通常通过多个标签值来确定指标唯一性。
特点:指标数据仅记录时间戳和对应的指标数值,通常存储在时间序列数据库中(TSDB),存储成本低,可用于渲染趋势图或柱状图,可灵活配置告警规则,对故障进行快速发现和响应;但是不适合用于定位故障原因。
实现方案:Zabbix、Open-Falcon、Prometheus
定义:离散事件,是系统运行时发生的一个个事件的记录。
特点:日志数据可以记录详细的信息(请求响应参数、自定义文字描述、异常信息、时间戳等),一般用于排查具体问题;日志通常存储在文件中,数据非结构化,存储成本高,不适合作为监控数据的来源。
实现方案:ELK、Loki
定义:基于特定请求作用域下的所有调用信息。
特点:一般需要依赖第三方存储,在微服务中一般有多个调用信息,如从网关开始,A服务调用B服务,调用数据库、缓存等。在链路系统中,需要清楚展现某条调用链中从主调方到被调方内部所有的调用信息。不仅有利于梳理接口及服务间调用的关系,还有助于排查慢请求或故障产生的原因。
实现方案:jaeger、zipkin、Skywalking
企业级的开源解决方案,擅长设备、网络、中间件的监控。
优点:
缺点:
Open-Falcon 最初来自小米,14 年开源,当时小米有 3 套 Zabbix,1 套业务性能监控系统 perfcounter。Open-Falcon 的初衷是想做一套大一统的方案,来解决这个乱局。
优点:
缺点:
Prometheus 的设计思路来自Borgmon,就像 Borgmon 是为 Borg 而生的,而 Prometheus 就是为 Kubernetes 而生的。提供了多种服务发现机制,大幅简化了 Kubernetes 的监控。
Prometheus2.0版本开始,重新设计了时序库,性能和可靠性都大幅提升。
优点:
缺点:
有两种典型的部署方式,一种是跟随监控对象部署,比如所有的机器上都部署一个采集器,采集机器的 CPU、内存、硬盘、IO、网络相关的指标;另一种是远程探针式,比如选取一个中心机器做探针,同时探测很多个机器的 PING 连通性,或者连到很多 MySQL 实例上去,执行命令采集数据。
Telegraf:
InfluxData 公司的产品,主要配合 InfluxDB 使用。Telegraf 也可以把监控数据推给 Prometheus存储;
Telegraf 是指标领域的 All-In-One 采集器,支持各种采集插件,只需要使用这一个采集器,就能解决绝大部分采集需求;
Telegraf 采集的很多数据都是字符串类型,但如果把这类数据推给 Prometheus 生态的时序库,比如 VictoriaMetrics、M3DB、Thanos 等,它就会报错。
Exporter
Exporter是专门用于Prometheus 生态的采集器,但是比较零散,每个采集目标都有对应的 Exporter 组件,比如 MySQL 有 mysqld_exporter,Redis 有 redis_exporter,JVM 有 jmx_exporter。
Exporter 的核心逻辑,就是去这些监控对象里采集数据,然后暴露为 Prometheus 协议的监控数据。比如 mysqld_exporter,就是连上 MySQL,执行一些类似于 show global status 、show global variables 、show slave status 这样的命令,拿到输出,再转换为 Prometheus 协议的数据;
很多中间件都内置支持了 Prometheus,直接通过自身的 /metrics 接口暴露监控数据,不用再单独伴生 Exporter;比如 Kubernetes 中的各类组件,比如 etcd,还有新版本的 ZooKeeper、 RabbitMQ;
不管是Exporter还是支持Prometheus协议的各类组件,都是提供 HTTP 接口(通常是 /metrics )来暴露监控数据,让监控系统来拉,这叫做 PULL 模型。
Grafana-Agent
Grafana 公司推出的一款 All-In-One 采集器,不但可以采集指标数据,也可以采集日志数据和链路数据。
如何快速集成各类采集能力的呢?Grafana-Agent 写了个框架,方便导入各类 Exporter,把各个 Exporter 当做 Lib 使用,常见的 Node-Exporter、Kafka-Exporter、Elasticsearch-Exporter、Mysqld-Exporter 等,都已经完成了集成。对于默认没有集成进去的 Exporter,Grafana-Agent 也支持用 PULL 的方式去抓取其他 Exporter 的数据,然后再通过 Remote Write 的方式,将采集到的数据转发给服务端。
很多时候,一个采集器可能搞不定所有业务需求,使用一款主力采集器,辅以多款其他采集器是大多数公司的选择。
Prometheus TSDB
Prometheus本地存储经历过多个迭代:V1.0(2012年)、V2.0(2015年)、V3.0(2017年)。最初借用第三方数据库LevelDB,1.0版本性能不高,每秒只能存储5W个样本;2.0版本借鉴了Facebook Gorilla压缩算法,将每个时序数据以单个文件方式保存,将性能提升到每秒存储8W个样本;2017年开始引入时序数据库的3.0版本,并成立了Prometheus TSDB开源项目,该版本在单机上提高到每秒存储百万个样本。
3.0版本保留了2.0版本高压缩比的分块保存方式,并将多个分块保存到一个文件中,通过创建一个索引文件避免产生大量的小文件;同时为了防止数据丢失,引入了WAL机制。
InfluxDB
InfluxDB 针对时序存储场景专门设计了存储引擎、数据结构、存取接口,国内使用范围比较广泛,可以和 Grafana、Telegraf 等良好整合,生态是非常完备的。
不过 InfluxDB 开源版本是单机的,没有开源集群版本。
M3DB
M3DB 是 Uber 的时序数据库,M3 在 Uber 抗住了 66 亿监控指标,这个量非常庞大。其主要包括4个组件:M3DB、M3 Coordinator、M3 Query、M3 Aggregator,我们当前产品中Prometheus的远程存储就是通过M3DB实现的。
用户会配置数百甚至数千条告警规则,一些超大型的公司可能要配置数万条告警规则。每个规则里含有数据过滤条件、阈值、执行频率等,有一些配置丰富的监控系统,还支持配置规则生效时段、持续时长、留观时长等。
告警引擎通常有两种架构,一种是数据触发式,一种是周期轮询式。
数据触发式,是指服务端接收到监控数据之后,除了存储到时序库,还会转发一份数据给告警引擎,告警引擎每收到一条监控数据,就要判断是否关联了告警规则,做告警判断。因为监控数据量比较大,告警规则的量也可能比较大,所以告警引擎是会做分片部署的,这样的架构,即时性很好,但是想要做指标关联计算就很麻烦,因为不同的指标哈希后可能会落到不同的告警引擎实例。
周期轮询式,通常是一个规则一个协程,按照用户配置的执行频率,周期性查询判断即可,做指标关联计算就会很容易。像 Prometheus、Nightingale、Grafana 等,都是这样的架构。
生成事件之后,通常是交给一个单独的模块来做告警发送,这个模块负责事件聚合、收敛,根据不同的条件发送给不同的接收者和不同的通知媒介。
监控数据的可视化也是一个非常通用且重要的需求,业界做得最成功的是Grafana,采用插件式架构,可以支持不同类型的数据源,图表非常丰富,基本是开源领域的事实标准。
2.1.主要特点
2.2. 局限性
2.3. 架构剖析 ★
prometheus监控探针,共收录有上千种Exporter,用于对第三方系统进行监控,方式是获取第三方系统的监控数据然后按照Prometheus的格式暴露出来;没有Exporter探针的第三方系统也可以自己定制开发。
不过Exporter种类繁多会导致维护压力大,也可以考虑用Influx Data公司开源的Telegraf统一进行管理。使用Telegraf集成Prometheus比单独使用Prometheus拥有更低的内存使用率和CPU使用率。
是支持临时性job主动推送指标的中间网关。
使用场景:临时/短作业、批处理作业、应用程序与Prometheus之间有防火墙或不在同一个网段;
不过该解决方案存在单点故障问题、必须使用PushGateway的API从推送网关中删除过期指标。
可以使用Kubernetes的API获取容器信息的变化来动态更新监控对象;
从Job、Exporter、PushGateway3个组件中通过HTTP轮询的形式拉取指标数据;
本地存储:直接保存到本地磁盘,从性能上考虑,建议使用SSD且不要保存超过一个月的数据;
远程存储:适用于存储大量监控数据,支持的远程存储包括OpenTSDB、InfluxDB、M3DB、PostgreSQL等;需要配合中间层适配器进行转换;
多维度数据模型的灵活查询。
实际工作中使用Grafana,也可以调用HTTP API发送请求获取数据;
独立的告警组件,可以将多个AlertManager配置成集群,支持集群内多个实例间通信;
按照一定的时间间隔产生的一个个数据点,这些数据点按照时间戳和值的生成顺序存放,得到了向量(vector)。
每条时间序列是通过指标名称和一组标签集来命名的。
矩阵中每一个点都可以称之为一个样本(Sample),主要有3方面构成:
指标(Metrics):包括指标名称(__name__)和一组标签集名称;
时间戳(TimeStamp):默认精确到毫秒;
样本值(Value):默认使用64位浮点类型。
时间序列的指标可以基于Bigtable设计为Key-Value存储的方式:
Prometheus的Metrics可以有两种表现方式(指标名称只能由ASCII字符、数字、下划线、冒号组成,冒号用来表示用户自定义的记录规则):
分别对应的查询形式(以下两个查询语句等价):
所有PromQL语句必须包含至少一个有效表达式(至少一个不会匹配到空字符串的标签过滤器),因此,以下三种示例是非法的:
用于返回在指定时间戳之前查询到的最新样本的瞬时向量。
高级应用:
瞬时偏移向量
高级应用:
只增不减,一般配合rate(统计时间区间内增长速率)、topk(统计top N的数据)、increase等函数使用;
increase(v range-vector)获取区间向量的第一个和最后一个样本并返回其增长量:
为什么increase函数算出来的值非33?真实计算公式:(360 - 327) / (1687922987 - 1687922882) * 120=37.71428571428571
irate(v range-vector)是针对长尾效应专门提供的用于计算区间向量的增长速率的函数,反应的是瞬时增长率,敏感度更高。长期趋势分析或告警中更推荐rate函数。
rate(v range-vector) 求取的是每秒变化率,也有数据外推的逻辑,increase 的结果除以 range-vector 的时间段的大小,就是 rate 的值。
rate(jvm_memory_used_bytes{id="PS Eden Space"}[1m]) 与 increase(jvm_memory_used_bytes{id="PS Eden Space"}[1m])/60.0 是等价的
表示样本数据可任意增减的指标;实际更多用于求和、取平均值、最大值、最小值。
without可以让sum函数根据相同的标签进行求和,但是忽略掉without函数覆盖的标签;如上图,可以忽略掉id,只按照堆/非堆的区别进行内存空间求和。
用于描述数据分布,最典型的应用场景就是监控延迟数据,计算 90 分位、99 分位的值。
有些服务访问量很高,每秒几百万次,如果要把所有请求的延迟数据全部拿到,排序之后再计算分位值,这个代价就太高了。使用 Histogram 类型,可以用较小的代价计算一个大概值。
Prometheus的Histogram类型计算原理:bucket桶排序+假定桶内数据均匀分布。
Histogram 这种做法性能有了巨大的提升,但是要同时计算成千上万个接口的分位值延迟数据,还是非常耗费资源的,甚至会造成服务端 OOM。
数据解析:http://ip:9090/metrics prometheus_http_request_duration_seconds
在客户端计算分位值,然后把计算之后的结果推给服务端存储,展示的时候直接查询即可。
Summary 的计算是在客户端计算的,也就意味着不是全局(整个服务)的分位值,分位值延迟数据是进程粒度的。
负载均衡会把请求均匀地打给后端的多个实例。一个实例内部计算的分位值,理论上和全局计算的分位值差别不会很大。另外,如果某个实例有故障,比如硬盘问题,导致落在这个实例的请求延迟大增,我们在实例粒度计算的延迟数据反而更容易发现问题。
数据解析:http://ip:9090/metrics go_gc_duration_seconds
上述两个查询是等价的;同一个聚合语句中不可同时使用by和without,by的作用类似于sql语句中的分组(group by),without语义是:除开XX标签,对剩下的标签进行分组。
数学中称为方差,用于衡量一组数据的离散程度:数据分布得越分散,各个数据与平均数的差的平方和越大,方差就越大。
标准差:用方差开算术平方根。
count:对分组中时间序列的数目进行求和
count_values:表示时间序列中每一个样本值(value)出现的次数,实践中一般用于统计版本号。
用于计算当前样本数据值的分布情况,例如计算一组http请求次数的中位数:
仅用于瞬时向量之间,and(并且)、or(或者)、unless(排除)
内置函数absent扮演了not的角色;
absent应用场景,配置告警规则:
提供了对时间序列标签的自定义能力。
label_replace(input_vector, "dst", "replacement", "src", "regex")
label_join(input_vector, "dst", "separator", "src_1", "src_2" ...)
resets函数用于统计计数器重置次数,两个连续的样本之间值减少,被认为是一次计数器重置,相当于是进程重启次数。
预测时间序列v在t秒后的值
使用简单的线性回归计算区间向量v中各个时间序列的导数
计算区间向量内第一个元素和最后一个元素的差值:
最新的两个样本值之间的差值,如上图例子,返回值为整数
要求sf > 0, tf <=1
霍尔特-温特双指数平滑算法,暂时想不到应用场景
返回区间向量中每个样本数据值变化的次数,如果样本值没有变化就返回1;
prometheus提供度量标准process_start_time_seconds记录每一个targets的启动时间,该时间被更改则意味着进程重启,可以发出循环崩溃告警:
expr: avg wtihout(instance) (changes{process_start_time_seconds [1h]}) > 3
调用成功会返回2XX状态码
resultType: matrix(区间向量) | vector(瞬时向量) | scalar | string
入参:PromQL表达式、时间戳、超时设置(可全局设置)
入参:PromQL表达式、起始时间戳、结束时间戳、查询时间步长(不能超过11000)、超时设置(可全局设置)
启动参数带上--web.enable-admin-api
执行以下命令可对数据库进行备份:
curl -X POST http://your_ip:9090/api/v1/admin/tsdb/snapshot
备份目录:/prometheus/data/snapshot
删除序列:http:// your_ip:9090/api/v1/admin/tsdb/delete_series
释放空间:http:// your_ip:9090/api/v1/admin/tsdb/clean_tombstones
预先计算经常需要用到的或计算量较大的表达式,并将结果保存为一组新的时间序列。
抓取前依赖服务发现,通过relabel_configs的方式自动对服务发现的目标进行重新标记;
抓取后主要指标保存在存储系统之前,依赖作业内的metrics_relabel_configs实现。
重写instance实现告警同时显示服务名+IP端口
将监控不需要的数据直接丢掉,不在prometheus保存,配置方法类似。
从左上开始,Prometheus 发送的警报到 Alertmanager;
(word不方便添加代码块,如果公众号可以添加代码块,上图内容可以单独提供代码)
关键配置参数:
prometheus.yml
alertmanager.yml
主要包括4个组件:M3DB、M3 Coordinator、M3 Query、M3 Aggregator
前有网友提出,想要用代码监控一个接口,定时访问它,如果接口返回值发生某些变化就提醒用户。
于是,我写了个简单的脚本。
脚本编写时,考虑的是放在目标网站的控制台来执行。之所以这样做,是因为如果放在页面外部执行,往往需要补环境,费时费力。
// 变量声明&定时器
let _timer=null;
let _subjects=[];
_timer=setInterval(()=> {
getData()
}, 60000);
// 接口访问
function getData () {
return fetch("https://mail.126.com/xxxx", {
"xxx": {},
}).then((res)=> {
res.text().then((str)=> {
str=(str || '').replace(/\n/g, '')
let subjects=str.match(/('subject':'.*?',)/g)
if (_subjects.length===0) {
_subjects=subjects
} else if (subjects[0] !==_subjects[0]) {
notificationHandler();
}
})
}).catch(err=> {
console.log('err:', err)
});
}
// 发送通知
function notificationHandler () {
let notification;
if (window.Notification && Notification.permission==='granted') {
notification=new Notification('收到新邮件啦', {
dir: 'ltr',
body: `收到新邮件啦,快去看看吧`
});
} else if (Notification.permission !=='denied') {
Notification.requestPermission().then(function(permission) {
if (permission==='granted') {
notification=new Notification('收到新邮件啦', {
dir: 'ltr',
body: `收到新邮件啦,快去看看吧`
});
}
});
}
if (notification) {
notification.onclick=()=> {
window.focus();
};
}
}
上述代码中的相关逻辑是以邮箱收件箱为例。
将代码放到控制台执行后,每隔1分钟访问一次接口,当响应内容有变化时,会向用户发送系统通知;当用户点击通知面板时,系统就会将焦点给到受监控的网页,方便用户对网页进行查看。
1)要求浏览器支持Notification这个API,这个API对应的就是系统右下角的通知功能,目前主流浏览器都是支持的。
系统通知
2)在控制台运行代码前,需要用户已经授权浏览器可以使用Notification权限,如果不知道是否允许,可以在控制台执行代码:
Notification.requestPermission().then(function(permission) {console.log(permission)})
返回值为 granted 说明是已授权的;
如果不是,那么浏览器会弹出提示询问我们是否允许;
如果权限是denied,而且看不到弹窗,那么可以重启浏览器再做尝试。
申请权限
前一直觉得监控告警是件很神奇的事情,仿佛可望不可及,今天我们就一起来解开它神秘的面纱哈哈~~
我的电脑是win10系统,我在电脑上安装了Docker Desktop软件,使用docker部署的
prometheus、pushgateway和alertmanager。
一、拉取prometheus、pushgateway和alertmanager的镜像,命令很简单:
docker pull prom/prometheus
docker pull prom/pushgateway
docker pull prom/alertmanager
可以通过docker images命令查看镜像是否拉取成功。
二、配置prometheus,创建prometheus.yml
global:
scrape_interval: 60s
evaluation_interval: 60s
alerting:
alertmanagers:
- static_configs:
- targets: ["ip:9093"] #这里是alertmanager的地址,注意这个地址是宿主机的地址,不可以写localhost或者127.0.0.1,下面的ip同理
rule_files:
- "rule/*.yml" #alertmanager的告警规则文件
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ['ip:9090']
labels:
instance: prometheus
- job_name: pushgateway
static_configs:
- targets: ['ip:9091']
labels:
instance: pushgateway
可以在rule文件夹下创建一个test_rule.yml
groups:
- name: test_rule
rules:
- alert: chat接口超时1500ms个数 # 告警名称
expr: test_metric > 0 # 告警的判定条件,参考Prometheus高级查询来设定
for: 1m # 满足告警条件持续时间多久后,才会发送告警
labels: #标签项
team: node
annotations: # 解析项,详细解释告警信息
summary: "异常服务节点:{{$labels.exported_instance}}"
description: "异常节点:{{$labels.exported_instance}}: 异常服务名称: {{$labels.Service_Name}} "
value: "chat接口超时1500ms个数:{{$value}}"
其中exported_instance和Service_Name都是我们往pushgateway中推送的数据,通过{{$labels.xxx}}的形式可以获取到
三、配置alertmanager.yml,创建alertmanager.yml
global:
resolve_timeout: 5m #处理超时时间,默认为5min
smtp_smarthost: 'smtp.126.com:25' # 邮箱smtp服务器代理,我这里是126的
smtp_from: '###@126.com' # 发送邮箱名称=邮箱名称
smtp_auth_username: '###@126.com' # 邮箱名称=发送邮箱名称
smtp_auth_password: '#####' # 授权码
smtp_require_tls: false
smtp_hello: '126.com'
templates:
- 'test.tmpl' #告警信息模板
route:
group_by: ['alertname'] #报警分组依据
group_wait: 10s #最初即第一次等待多久时间发送一组警报的通知
group_interval: 10m # 在发送新警报前的等待时间
repeat_interval: 1h # 发送重复警报的周期 对于email配置中,此项不可以设置过低,>否则将会由于邮件发送太多频繁,被smtp服务器拒绝
receiver: 'email' # 发送警报的接收者的名称,以下receivers name的名称
receivers:
- name: 'email'
email_configs: # 邮箱配置
- to: '###@126.com' # 接收警报的email配置
headers: { Subject: "[WARN] 报警邮件"} # 接收邮件的标题
send_resolved: true
html: '{{ template "test.html" .}}' #这个test.html就是模板中define的名称,需要对应起来
webhook_configs:
- url: 'http://ip:9093/alertmanager/hook' #这个是alertmanager的地址,ip也是宿主机的ip
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
再配置test.tmpl
{{ define "test.html" }}
<table border="1">
<tr>
<td>报警项</td>
<td>实例</td>
<td>报警阀值</td>
<td>开始时间</td>
</tr>
{{ range $i, $alert :=.Alerts }}
<tr>
<td>{{ index $alert.Labels "alertname" }}</td>
<td>{{ index $alert.Labels "instance" }}</td>
<td>{{ index $alert.Annotations "value" }}</td>
<td>{{ $alert.StartsAt }}</td>
</tr>
{{ end }}
</table>
{{ end }}
配置结束,下面开始启动容器
一、启动pushgateway:docker run -d --name=pushgateway -p 9091:9091 prom/pushgateway
可以登录http://localhost:9091/查看:
二、启动alertmanager
docker run -d -p 9093:9093 --restart=always --name=alertmanager -v 宿主机的alertmanager.yml地址:/etc/alertmanager/alertmanager.yml -v 宿主机的test.tmpl地址:/etc/alertmanager/test.tmpl prom/alertmanager:latest
登录http://localhost:9093/查看:
三、启动prometheus容器
docker run -d -p 9090:9090 --restart=always --name=prometheus -v 宿主机的prometheus.yml地址:/etc/prometheus/prometheus.yml -v 宿主机的rule文件夹路径:/etc/prometheus/rule/ prom/prometheus
登录 http://localhost:9090/查看:
查看监控情况:
此时环境已经全部部署完毕,下面可以往pushgateway中推送数据,查看告警情况
echo "test_metric 2" | curl --data-binary @- http://127.0.0.1:9091/metrics/job/test_job/instance/192.168.0.1/Service_Name/ai_sym_interface
这个命令的意思是向job为test_job中推送数据,instance为192.168.0.1,Service_Name为ai_sym_interface,值为2
可登录pushgateway查看推送情况:
由于prometheus监控了pushgateway的数据指标,所以在prometheus中也可以看到这条数据:
由于我们配置了test_rule.yml,当test_metric大于0并持续一秒会发送,报警邮件:
至此,邮箱告警就完成了,其他的告警也是同样的道理,可以继续研究,加油~!
原文链接:记忆旅途
*请认真填写需求信息,我们会在24小时内与您取得联系。