通过prometheus的架构图可以看出,prometheus提供了本地存储,即 tsdb时序数据库。
使用本地存储可以降低Prometheus部署和管理的复杂度同时减少高可用(HA)带来的复杂性。在默认情况下,用户只需要部署多套Prometheus,采集相同的Targets即可实现基本的HA。同时由于Promethus高效的数据处理能力,单个Prometheus Server基本上能够应对大部分用户监控规模的需求。
当然本地存储也带来了一些不好的地方,首先就是 数据持久化 的问题,特别是在像Kubernetes这样的动态集群环境下,如果Promthues的实例被重新调度,那所有历史监控数据都会丢失。 其次本地存储也意味着Prometheus不适合保存大量历史数据(一般Prometheus推荐只保留几周或者几个月的数据)。
本地存储也导致Prometheus无法进行弹性扩展。为了适应这方面的需求,Prometheus提供了 remote_write
和 remote_read
的特性,支持将数据存储到远端和从远端读取数据。通过将监控与数据分离,Prometheus能够更好地进行弹性扩展。
除了本地存储方面的问题,由于Prometheus基于Pull模型,当有大量的Target需要采样本时,单一Prometheus实例在数据抓取时可能会出现一些性能问题,联邦集群的特性可以让Prometheus将样本采集任务划分到不同的Prometheus实例中,并且通过一个统一的中心节点进行聚合,从而可以使Prometheuse可以根据规模进行扩展。
Prometheus 2.x 采用自定义的存储格式将样本数据保存在本地磁盘当中。
按照两个小时为一个时间窗口,将两小时内产生的数据存储在一个块(Block)中,每一个块中包含该时间窗口内的所有样本数据(chunks)
,元数据文件(meta.json)
以及索引文件(index)
。
当前时间窗口内正在收集的样本数据,Prometheus则会直接将数据保存在内存当中。为了确保此期间如果Prometheus发生崩溃或者重启时能够恢复数据,Prometheus启动时会从写入日志(WAL)进行重播,从而恢复数据。此期间如果通过API删除时间序列,删除记录也会保存在单独的逻辑文件当中(tombstone)。
/usr/local/prometheus/data/ # 数据存储目录
./data
|- 01BKGV7JBM69T2G1BGBGM6KB12 # 块
|- meta.json # 元数据
|- wal # 写入日志
|- 000002
|- 000001
|- 01BKGTZQ1SYQJTR4PB43C8PD98 # 块
|- meta.json #元数据
|- index # 索引文件
|- chunks # 样本数据
|- 000001
|- tombstones # 逻辑数据
|- 01BKGTZQ1HHWHV8FBJXW1Y3W0K
|- meta.json
|- wal
|-000001
通过时间窗口的形式保存所有的样本数据,可以明显提高Prometheus的查询效率,当查询一段时间范围内的所有样本数据时,只需要简单的从落在该范围内的块中查询数据即可。
同时该存储方式可以简化历史数据的删除逻辑。只要一个块的时间范围落在了配置的保留范围之外,直接丢弃该块即可。
用户可以通过命令行启动参数的方式修改本地存储的配置。
启动参数 | 默认值 | 含义 |
---|---|---|
–storage.tsdb.path | data/ | 指标存储的基本路径 |
–storage.tsdb.retention | 15d | 样本在仓库中保留多长时间 |
–storage.tsdb.min-block-duration | 2h | 头块的时间戳范围,之后它们被持久化 |
–storage.tsdb.max-block-duration | 36h | 压缩块的最大时间戳范围,它是任何持久块的最小持续时间。 |
–storage.tsdb.no-lockfile | false | 不要在数据目录中创建锁文件 |
在一般情况下,Prometheus中存储的每一个样本大概占用1-2字节大小。如果需要对Prometheus Server的本地磁盘空间做容量规划时,可以通过以下公式计算:
needed_disk_space = retention_time_seconds * ingested_samples_per_second * bytes_per_sample
需要的磁盘空间=保留时间 * 每秒获取样本数 * 每个样本的字节数
从上面公式中可以看出在保留时间(retention_time_seconds)
和样本大小(bytes_per_sample)
不变的情况下,如果想减少本地磁盘的容量需求,只能通过减少每秒获取样本数(ingested_samples_per_second)的方式。
因此有两种手段,一是减少时间序列的数量,二是增加采集样本的时间间隔。考虑到Prometheus会对时间序列进行压缩效率,减少时间序列的数量效果更明显。
如果本地存储由于某些原因出现了错误,最直接的方式就是停止Prometheus并且删除data目录中的所有记录。当然也可以尝试删除那些发生错误的块目录,不过相应的用户会丢失该块中保存的大概两个小时的监控记录。
Prometheus的本地存储设计可以减少其自身运维和管理的复杂度,同时能够满足大部分用户监控规模的需求。但是本地存储也意味着Prometheus无法持久化数据,无法存储大量历史数据,同时也无法灵活扩展和迁移。
为了保持Prometheus的简单性,Prometheus并没有尝试在自身中解决以上问题,而是通过定义两个标准接口(remote_write/remote_read
),让用户可以基于这两个接口对接将数据保存到任意第三方的存储服务中,这种方式在Promthues中称为Remote Storage。
在Prometheus配置文件中指定Remote Write(远程写)的URL地址,一旦设置了该配置项,Prometheus将采集到的样本数据通过HTTP的形式发送给适配器(Adaptor)。而用户则可以在适配器中对接外部任意的服务。外部服务可以是真正的存储系统,公有云的存储服务,也可以是消息队列等任意形式。
如下图所示,Promthues的Remote Read(远程读)也通过了一个适配器实现。在远程读的流程当中,当用户发起查询请求后,Promthues将向remote_read中配置的URL发起查询请求(matchers,ranges),Adaptor根据请求条件从第三方存储服务中获取响应的数据。同时将数据转换为Promthues的原始样本数据返回给Prometheus Server。
当获取到样本数据后,Promthues在本地使用PromQL对样本数据进行二次处理。
注意:启用远程读设置后,只在数据查询时有效,对于规则文件的处理,以及Metadata API的处理都只基于Prometheus本地存储完成。
Prometheus配置文件中添加 remote_write
和 remote_read
配置,其中url用于指定远程读/写的HTTP服务地址。如果该URL启动了认证则可以通过 basic_auth
进行安全认证配置。对于https的支持需要设定 tls_concig
。proxy_url
主要用于Prometheus无法直接访问适配器服务的情况下。
具体配置如下所示:
remote_write:
# 远程写入的地址
url: <string>
# 远程存储响应超时时间
[ remote_timeout: <duration> | default = 30s ]
# 对时间序列数据进行标签重写
write_relabel_configs:
[ - <relabel_config> ... ]
# 远程存储的标识
[ name: <string> ]
# 请求远程存储时,在header中加入Authorization认证
basic_auth:
[ username: <string> ]
[ password: <secret> ]
[ password_file: <string> ]
[ bearer_token: <string> ]
[ bearer_token_file: <filename> ]
# TLS配置
tls_config:
[ ca_file: <filename> ]
[ cert_file: <filename> ]
[ key_file: <filename> ]
[ server_name: <string> ]
[ insecure_skip_verify: <boolean> ]
# 代理配置
[ proxy_url: <string> ]
# 配置远程存储写入的队列
queue_config:
# 每个分片中最大样本缓冲数量,超过将阻塞从WAL中读取,建议适当调大
[ capacity: <int> | default = 500 ]
# 最大分片数,即并发数
[ max_shards: <int> | default = 1000 ]
# 最小分片数,即并发数
[ min_shards: <int> | default = 1 ]
# 每次发送的样本数
[ max_samples_per_send: <int> | default = 100]
# 样本在缓冲区过期时间
[ batch_send_deadline: <duration> | default = 5s ]
# 初始重试延迟,每次重试都会翻倍
[ min_backoff: <duration> | default = 30ms ]
# 最大重试延迟
[ max_backoff: <duration> | default = 100ms ]
# 远程存储读取地址
url: <string>
# 远程存储标识
[ name: <string> ]
required_matchers:
[ <labelname>: <labelvalue> ... ]
# 读取超时时间
[ remote_timeout: <duration> | default = 1m ]
# 当查询的时间范围在本地存储数据保留的时间范围内,是否还去远程存储查询
[ read_recent: <boolean> | default = false ]
# 请求远程存储时,在header中加入Authorization认证
basic_auth:
[ username: <string> ]
[ password: <secret> ]
[ password_file: <string> ]
[ bearer_token: <string> ]
[ bearer_token_file: <filename> ]
# TLS配置
tls_config:
[ ca_file: <filename> ]
[ cert_file: <filename> ]
[ key_file: <filename> ]
[ server_name: <string> ]
[ insecure_skip_verify: <boolean> ]
# 代理配置
[ proxy_url: <string> ]
官方文档
#我这里和Grafana安装在一台机器上了
[root@grafana ~]# cat <
[root@prometheus prometheus]# vim /usr/lib/systemd/system/prometheus.service
--storage.tsdb.path=/usr/local/prometheus/data
[root@prometheus prometheus]# systemctl daemon-reload
[root@prometheus prometheus]# systemctl restart prometheus
[root@prometheus prometheus]# pwd
/usr/local/prometheus
[root@prometheus prometheus]# vim prometheus.yml
#远程写入地址
remote_write:
- url: "http://192.168.153.205:8086/api/v1/prom/write?db=prometheus"
#远程读取地址
remote_read:
- url: "http://192.168.153.205:8086/api/v1/prom/read?db=prometheus"
[root@prometheus prometheus]# systemctl restart prometheus
查看influxdb是否有数据写入
[root@grafana ~]# influx
> use prometheus
> show measurements #会显示出来很多表
> select * from redis_memory_max_bytes; 查看这张表中是否有监控数据
验证数据可靠性:
停止 Prometheus 服务。同时删除 Prometheus 的 data 目录,重启 Prometheus。打开 Prometheus
UI 如果配置正常,Prometheus 可以正常查询到本地存储以删除的历史数据记录。
进入influxdb数据库,数据仍然存在;
通过Remote Storage可以分离监控样本采集和数据存储,解决Prometheus的持久化问题。这一部分会重点讨论如何利用联邦集群特性对Promthues进行扩展,以适应不同监控规模的变化。
对于大部分监控规模而言,我们只需要在每一个数据中心(例如:EC2可用区,Kubernetes集群)安装一个Prometheus Server实例,就可以在各个数据中心处理上千规模的集群。同时将Prometheus Server部署到不同的数据中心可以避免网络配置的复杂性。
如上图所示,在每个数据中心部署单独的Prometheus Server,用于采集当前数据中心监控数据。并由一个中心的Prometheus Server负责聚合多个数据中心的监控数据。这一特性在Promthues中称为联邦集群。
联邦集群的核心在于每一个Prometheus Server都包含额一个用于获取当前实例中监控样本的接口/federate。对于中心Prometheus Server而言,无论是从其他的Prometheus实例还是Exporter实例中获取数据实际上并没有任何差异。
scrape_configs:
- job_name: 'federate'
scrape_interval: 15s
honor_labels: true
metrics_path: '/federate'
params:
'match[]':
- '{job="prometheus"}'
- '{__name__=~"job:.*"}'
- '{__name__=~"node.*"}'
static_configs:
- targets:
- '192.168.77.11:9090'
- '192.168.77.12:9090'
为了有效的减少不必要的时间序列,通过params参数可以用于指定只获取某些时间序列的样本数据,例如
"http://192.168.77.11:9090/federate?match[]={job%3D"prometheus"}&match[]={__name__%3D~"job%3A.*"}&match[]={__name__%3D~"node.*"}"
通过URL中的 match[]
参数指定我们可以指定需要获取的时间序列。match[]
参数必须是一个瞬时向量选择器,例如 up
或者 {job="api-server"}
。配置多个 match[]
参数,用于获取多组时间序列的监控数据。
horbor_labels
配置true可以确保当采集到的监控指标冲突时,能够自动忽略冲突的监控数据。如果为false时,prometheus会自动将冲突的标签替换为”exported_“的形式。
联邦集群的特性可以帮助用户根据不同的监控规模对Promthues部署架构进行调整。例如如下所示,可以在各个数据中心中部署多个Prometheus Server实例。每一个Prometheus Server实例只负责采集当前数据中心中的一部分任务(Job),例如可以将不同的监控任务分离到不同的Prometheus实例当中,再有中心Prometheus实例进行聚合。
功能分区,即通过联邦集群的特性在任务级别对Prometheus采集任务进行划分,以支持规模的扩展。
Prometheus的本地存储给Prometheus带来了简单高效的使用体验,可以让Promthues在单节点的情况下满足大部分用户的监控需求。但是本地存储也同时限制了Prometheus的可扩展性,带来了数据持久化等一系列的问题。通过Prometheus的Remote Storage特性可以解决这一系列问题,包括Promthues的动态扩展,以及历史数据的存储。
而除了数据持久化问题以外,影响Promthues性能表现的另外一个重要因素就是数据采集任务量,以及单台Promthues能够处理的时间序列数。因此当监控规模大到Promthues单台无法有效处理的情况下,可以选择利用Promthues的联邦集群的特性,将Promthues的监控任务划分到不同的实例当中。
这一部分将重点讨论Prometheus的高可用架构,并且根据不同的使用场景介绍了一种常见的高可用方案。
由于Promthues的Pull机制的设计,为了确保Promthues服务的可用性,用户只需要部署多套Prometheus Server实例,并且采集相同的Exporter目标即可。
基本的HA模式只能确保Promthues服务的可用性问题,但是不解决Prometheus Server之间的数据一致性问题以及持久化问题(数据丢失后无法恢复),也无法进行动态的扩展。因此这种部署方式适合监控规模不大,Promthues Server也不会频繁发生迁移的情况,并且只需要保存短周期监控数据的场景。
在基本HA模式的基础上通过添加Remote Storage存储支持,将监控数据保存在第三方存储服务上。
在解决了Promthues服务可用性的基础上,同时确保了数据的持久化,当Promthues Server发生宕机或者数据丢失的情况下,可以快速的恢复。 同时Promthues Server可能很好的进行迁移。因此,该方案适用于用户监控规模不大,但是希望能够�将监控数据持久化,同时能够确保Promthues Server的可迁移性的场景。
当单台Promthues Server无法处理大量的采集任务时,用户可以考虑基于Prometheus联邦集群的方式将监控采集任务划分到不同的Promthues实例当中即在任务级别功能分区。
这种部署方式一般适用于两种场景:
场景一:单数据中心 + 大量的采集任务
这种场景下Promthues的性能瓶颈主要在于大量的采集任务,因此用户需要利用Prometheus联邦集群的特性,将不同类型的采集任务划分到不同的Promthues子服务中,从而实现功能分区。例如一个Promthues Server负责采集基础设施相关的监控指标,另外一个Prometheus Server负责采集应用监控指标。再有上层Prometheus Server实现对数据的汇聚。
场景二:多数据中心
这种模式也适合与多数据中心的情况,当Promthues Server无法直接与数据中心中的Exporter进行通讯时,在每一个数据中部署一个单独的Promthues Server负责当前数据中心的采集任务是一个不错的方式。这样可以避免用户进行大量的网络配置,只需要确保主Promthues Server实例能够与当前数据中心的Prometheus Server通讯即可。 中心Promthues Server负责实现对多数据中心数据的聚合。
这时在考虑另外一种极端情况,即单个采集任务的Target数也变得非常巨大。这时简单通过联邦集群进行功能分区,Prometheus Server也无法有效处理时。这种情况只能考虑继续在实例级别进行功能划分。
如上图所示,将统一任务的不同实例的监控数据采集任务划分到不同的Prometheus实例。通过relabel设置,我们可以确保当前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__]
modulus: 4
target_label: __tmp_hash
action: hashmod
- source_labels: [__tmp_hash]
regex: ^1$
action: keep
并且通过当前数据中心的一个中心Prometheus Server将监控数据进行聚合到任务级别。
- scrape_config:
- job_name: slaves
honor_labels: true
metrics_path: /federate
params:
match[]:
- '{__name__=~"^slave:.*"}' # Request all slave-level time series
static_configs:
- targets:
- slave0:9090
- slave1:9090
- slave3:9090
- slave4:9090
上面的部分,根据不同的场景演示了3种不同的高可用部署方案。当然对于Promthues部署方案需要用户根据监控规模以及自身的需求进行动态调整,下表展示了Promthues和高可用有关3个选项各自解决的问题,用户可以根据自己的需求灵活选择。
选项\需求 | 服务可用性 | 数据持久化 | 水平扩展 |
---|---|---|---|
主备HA | v | x | x |
远程存储 | x | v | x |
联邦集群 | x | x | v |
在上一小节中我们主要讨论了Prometheus Server自身的高可用问题。而接下来,重点将放在告警处理也就是Alertmanager部分。如下所示。
为了提升Promthues的服务可用性,通常用户会部署两个或者两个以上的Promthus Server,它们具有完全相同的配置包括Job配置,以及告警配置等。当某一个Prometheus Server发生故障后可以确保Promthues持续可用。
同时基于Alertmanager的告警分组机制即使不同的Prometheus Sever分别发送相同的告警给Alertmanager,Alertmanager也可以自动将这些告警合并为一个通知向receiver发送。
但不幸的是,虽然Alertmanager能够同时处理多个相同的Prometheus Server所产生的告警。但是由于单个Alertmanager的存在,当前的部署结构存在明显的单点故障风险,当Alertmanager单点失效后,告警的后续所有业务全部失效。
如下所示,最直接的方式,就是尝试部署多套Alertmanager。但是由于Alertmanager之间不存在并不了解彼此的存在,因此则会出现告警通知被不同的Alertmanager重复发送多次的问题。
为了解决这一问题,如下所示。Alertmanager引入了Gossip机制。Gossip机制为多个Alertmanager之间提供了信息传递的机制。确保及时在多个Alertmanager分别接收到相同告警信息的情况下,也只有一个告警通知被发送给Receiver。
Gossip是分布式系统中被广泛使用的协议,用于实现分布式节点之间的信息交换和状态同步。Gossip协议同步状态类似于流言或者病毒的传播,如下所示:
一般来说Gossip有两种实现方式分别为 Push-based
和 Pull-based
。在Push-based当集群中某一节点A完成一个工作后,随机的从其它节点B并向其发送相应的消息,节点B接收到消息后在重复完成相同的工作,直到传播到集群中的所有节点。而Pull-based的实现中节点A会随机的向节点B发起询问是否有新的状态需要同步,如果有则返回。
在简单了解了Gossip协议之后,我们来看Alertmanager是如何基于Gossip协议实现集群高可用的。如下所示,当Alertmanager接收到来自Prometheus的告警消息后,会按照以下流程对告警进行处理:
因此如下所示,Gossip机制的关键在于两点:
Alertmanager基于Gossip实现的集群机制虽然不能保证所有实例上的数据时刻保持一致,但是实现了CAP理论中的AP系统,即可用性和分区容错性。同时对于Prometheus Server而言保持了配置了简单性,Promthues Server之间不需要任何的状态同步。
为了能够让Alertmanager节点之间进行通讯,需要在Alertmanager启动时设置相应的参数。其中主要的参数包括:
--cluster.listen-address string: 当前实例集群服务监听地址
--cluster.peer value: 初始化时关联的其它实例的集群服务地址
机器:192.168.197.128
这里我为了节约资源,在一台机器启三个实例。生产环境不建议都装在一台机器
定义Alertmanager实例01,其中Alertmanager的服务运行在9093端口,集群服务地址运行在8001端口。
定义Alertmanager实例02,其中主服务运行在9094端口,集群服务运行在8002端口。
定义Alertmanager实例03,其中主服务运行在9095端口,集群服务运行在8002端口。为了将01,02,03组成集群
注:02
03
启动时需要定义—cluster.peer
参数并且指向01实例的集群服务地址:8001。
实例 | 集群端口 | 服务端口 |
---|---|---|
Alertmanager-01 | 8001 | 9093 |
Alertmanager-02 | 8002 | 9094 |
Alertmanager-03 | 8003 | 9095 |
[root@alert-manager-dev-1 src]# cd alertmanager
[root@alert-manager-dev-1 alertmanager]# ls
global:
resolve_timeout: 5m
smtp_from: '[email protected]'
smtp_smarthost: 'smtp.163.com:25'
smtp_auth_username: '[email protected]'
smtp_auth_password: 'ZQWHFEFFALRVKOTP' #这里要开启邮箱SMTP/POP3/IMAP认证,记录授权码
smtp_require_tls: false
route:
group_by: ['alertname']
group_wait: 5s
group_interval: 5s
repeat_interval: 5m
receiver: 'email'
routes:
- match:
severity: warning
receiver: 'email'
continue: true
- match_re:
severity: ^(warning|error)$
receiver: 'PrometheusAlert'
continue: true
receivers:
- name: 'PrometheusAlert'
webhook_configs:
- url: 'http://192.168.197.130:8080/prometheusalert?type=fs&tpl=prometheus-fs&fsurl=https://open.feishu.cn/open-apis/bot/v2/hook/[email protected]'
- name: 'email'
email_configs:
- to: '[email protected]'
send_resolved: true
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'dev', 'instance']
启动实例01:
nohup ./alertmanager --web.listen-address=":9093" --cluster.listen-address="127.0.0.1:8001" --config.file="alertmanager.yml" --log.level=debug 2>&1 > alert1.log &
启动实例02:
nohup ./alertmanager --web.listen-address=":9094" --cluster.listen-address="127.0.0.1:8002" --cluster.peer=127.0.0.1:8001 --config.file="alertmanager.yml" --storage.path=/data/9094/ --log.level=debug 2>&1 > alert2.log &
启动实例03:
nohup ./alertmanager --web.listen-address=":9095" --cluster.listen-address="127.0.0.1:8003" --cluster.peer=127.0.0.1:8001 --config.file="alertmanager.yml" --storage.path=/data/9095/ --log.level=debug 2>&1 > alert3.log &
重新加载Prometheus
curl -X POST http://localhost:9090/-/reload
或者
systemctl restart prometheus
如果报错如下
[root@monitor prometheus]# curl -XPOST localhost:9090/-/reload
Lifecycle API is not enabled.添加–web.enable-lifecycle
完成后访问任意Alertmanager节点http://localhost:9093/#/status
,可以查看当前Alertmanager集群的状态。
由于 Gossip 机制的实现,在Promthues和Alertmanager实例之间不要使用任何的负载均衡,需要确保Promthues将告警发送到所有的Alertmanager实例中:
alerting:
alertmanagers:
- static_configs:
- targets:
- 127.0.0.1:9093
- 127.0.0.1:9094
- 127.0.0.1:9095
创建Promthues集群配置文件/usr/local/prometheus/prometheus.yml
,完整内容如下:
global:
scrape_interval: 15s
scrape_timeout: 10s
evaluation_interval: 15s
rule_files:
- /usr/local/prometheus/rules/*.rules
alerting:
alertmanagers:
- static_configs:
- targets:
- 127.0.0.1:9093
- 127.0.0.1:9094
- 127.0.0.1:9095
scrape_configs:
- job_name: prometheus
static_configs:
- targets:
- localhost:9090
- job_name: 'node'
static_configs:
- targets: ['localhost:9100']
同时定义告警规则文件/usr/local/prometheus/rules/host_monitor.rules
,如下所示:
[root@prometheus]# vim /usr/local/prometheus/rules/host_monitor.rules
groups:
- name: hostStatsAlert
rules:
- alert: hostCpuUsageAlert
expr: (1- ((sum(increase(node_cpu_seconds_total{mode="idle"}[1m])) by(instance)) / (sum(increase(node_cpu_seconds_total[1m])) by(instance)))) > 0.85
for: 1m
labels:
severity: error
team: node1
annotations:
summary: "Instance {{ $labels.instance }} CPU usgae high"
description: "{{ $labels.instance }} CPU usage above 85% (current value: {{ $value }})"
- alert: hostMemUsageAlert
expr: (node_memory_MemTotal_bytes- node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes > 0.2
for: 1m
labels:
severity: error
team: node2
annotations:
summary: "Instance {{ $labels.instance }} MEM usgae high"
description: "{{ $labels.instance }} MEM usage above 85% (current value: {{ $value }})"