本文目标:通过讲解 Prometheus 架构设计,让大家了解在 k8s场景下的指标监控体系的构成
可观测性和监控的基本概念
可观测性和监控的概念
可观测性(observability)可以理解为 "一个系统可以通过输入和输出推断其内部状态的能力"。
可观测性并不等于监控(monitoring)。监控指的是更具体的一套行为,通常涉及采集、处理、聚合、和展示实时的、可量化的数据,比如 API 接口的响应延迟,节点的内存余量,服务的异常状态等。
监控告诉你一个系统是否正常运作,是达到良好可观测性的必要手段;可观测性除了告诉你系统是否正常运作,还帮你理解一个系统为什么如此运作。

监控数据的形态
指标/ 时序数据
指标(metrics)指带有一些系列元数据的时序(timeseries)数据。时序数据相对其他数据形态来说,最轻量、使用成本最低,但和其他数据形态相比,它基本忽视了上下文,只表达沿时间维度统计出的趋势。
换句话说,指标只能表达 "某一指标在某一段时间内经历的趋势" 或者 "一批具有一部分公共维度的事件在一段时间内的某种统计结构",而无法准确地描述某一次独立的事件。举一个具体的例子,日志和 trace 可以描述 A 服务对 B 服务的某一次具体调用的耗时和结果,但指标只能表达 A 服务对 B 服务在一段时间内的请求量和错误率。指标一般被运用在告警场景和趋势观察场景。
在绝大多数监控系统中,指标可以被分为以下三类:
Counter
特点是只增不减。
常用指标:网络流量、请求量、CPU 用量(CPU用量被视为Counter类型的指标,因为它是一个连续递增的数字,实际上衡量了CPU自启动以来空闲的时间量)
以QPS举例,使用 counter 统计服务接收的请求总数,使用 rate 函数计算出请求数量每秒的增量,即为服务的 QPS:

Gauge
特点是可增可减。gauge 可以表达时序数据如何随时间增加或减少。一个常见用法是用 gauge 表达资源状态,把所有可能的状态对应到一个固定的数字上。
常用指标:内存用量,硬盘空间,服务运行状态

Histogram
Counter 指标的一个主要问题是,它只能被计算为均值。而对于类似接口请求延迟的数据,仅仅有平均值还不够。我们往往还需要看到数据的分布情况,甚至计算百分位数(quantile)。对于这类数据,我们需要更复杂的计量方式:histogram。Histogram 的原理是提前定义多个 buckets,覆盖所有可能的样本;采集到新样本时,这个样本会落入某个 bucket 内。使用时,我们可以利用样本在各个 bucket 的分布情况计算百分位。
常用指标:各类延迟、耗时类指标

事件型数据(Events、Logs)
事件型数据值的特点是,一个或一组带有相同元数据的数据样本可以相对完整地描述一个事件。日志是最常见也是形态最简单(除元数据外只是一个字符串)的事件型数据。更复杂的例子有 Kubernetes 的 Event。事件型数据一般带有一些基本的上下文信息。
事件型数据的储存成本高于指标,但如果使用得当,成本一般仍能控制在可大规模采集的范围内。传统上,事件型数据一般被用于调试场景,而近几年,随着性能和成本的问题被解决得越来越好,也被越来越多的运用到告警和趋势观察场景。基于事件的引擎必然成本更高,但可以解决时序数据无法放大到具体事件的这一关键短板。
Tracing
Tracing 可以被认为是一组元数据不同,但关联对象上存在共性,并且强调时间上的先后顺序的事件性数据的合集。它可以是组件内的,比如沿某个线程收集的一组带时间戳的日志,也可以是跨组件的,比如一个前端请求在最终返回前触发的一些列后端请求。
Tracing 能提供非常丰富的上下文,但也有明显缺点。首先,tracing 的采集和储存成本一般很高。tracing 数据的采集在生产环境中一般是默认关闭或仅小规模采样的,仅在发生问题时才按需打开。总的来说,tracing 一般只适合调试场景。

监控与 Prometheus
监控的重要意义
监控系统在信息技术领域起着至关重要的作用,并为各种类型的组织提供了多项关键优点:
-
问题发现与预警:我们能够及时地监控系统的运行状态,并且在问题发生时快速发现并进行相应的处理。凭借预警机制,我们可以在问题发生之前发现并修复它们,以避免业务中断。
-
故障排除:通过监控,我们可以回溯历史数据,找出造成问题的原因。持续的、精确的数据回溯可以帮助我们更好地定位和解决问题,减少业务的停机时间。
-
性能优化:监控系统可以提供关于系统运行的详细信息,如哪些组件的性能更优,哪些更糟。这些信息可用于优化资源分配并提高系统的整体性能。
-
市场竞争力:一个优秀的监控系统可以提供与业务挽救性需求相符的关键数据,从而提升效率、提供快速反应,以及提供卓越的用户体验。
-
预见未来需求:通过收集和分析历史数据,我们可以预见到未来的需求,如资源需求、性能需求等,并进行相应的计划和预备。
Prometheus简介

Prometheus 是一个开源的指标监控引擎。它的特点是专门为监控场景优化,用大胆的功能取舍换取了极佳的性能。尽管 Prometheus 的功能并不丰富,但在它选择做的事情上,它做得都非常好。 这一特点帮它在开源社区里打下了很高的地位,很多它不能做的事,都能在社区中找到成熟的插件或拓展方案。
Prometheus 最初由 Sound Cloud 开发,在 2012 年以开源形式发布。发布后人气快速积累,并脱离 Sound Cloud 成为一个独立的项目。Prometheus 的 GitHub repo 截止 2024 年 3 月拥有52.2k star、8.7k forks。2016 年时成为云原生计算基金会(Cloud Native Computing Foundation, CNCF)的成员,是 CNCF 的第二个毕业项目,奠定了其 CNCF 项目默认监控引擎的地位。
Kubernetes下的监控挑战
在Kubernetes环境下,实现有效的监控可能会遇到一些特殊的挑战:
- 动态环境:Kubernetes环境是高度动态的,pods不断地被创建和销毁。因此,监控工具需要能快速地对这些变化做出反应,并能够在所有实例中准确地跟踪性能。
- 多维度监控:一个全面的Kubernetes监控系统需要能监控各种维度的数据,包括容器、pods、服务、集群和整个基础设施。
- 分布式系统的复杂性:由于Kubernetes的分布式特性,监控系统需要能够捕获和分析分布在各个节点上的pods和容器的数据。
- 日志管理:由于运行在Kubernetes集群上的应用可能会产生大量的日志,这就需要一个强大的工具来处理日志的采集、存储、搜索和分析。
- 资源消耗:对Kubernetes集群进行监控意味着需要消耗额外的计算资源。合理地平衡监控的需求和资源的消耗是一个挑战。
- 集成第三方系统:许多企业有自己的日志、报警或其他系统,监控工具需要灵活地与这些系统集成。
这些挑战说明了需要一个强大、灵活并且可以在动态环境下进行一致监控的工具。这也是为什么许多组织选择使用像Prometheus这样的工具来管理他们的Kubernetes监控。
Prometheus的核心组件和架构

数据源
图中绿色部分的组件和数据采集有关。Prometheus 的数据都是从 metrics API 拉取的。前文已经讲解过 Prometheus 一般会对接一个外部控制面,通过服务发现机制来找到这些 API 的 endpoint。这里我们关注提供 API 的服务本身。所有数据源都要按它定义的格式来暴露这样的一个 API。而提供这个 API 的服务大致可以分两类:直接提供 API 的服务和 exporter 服务。

Client Library
过大部分 CNCF 项目都自带 metrics API。这些项目一般使用 client library 来实现 metrics API。Prometheus 自己提供了四种语言的 client library:Golang,Java/Scala,Python,和 Ruby。社区则补齐了例如 C++、Rust、Node.js等其他语言(完整列表请见官方文档)。Client library 一般包含了对应语言的运行时指标,仅引用一下并暴露 HTTP 接口即可使用。服务一般会通过 /metrics 这个路径暴露指标,例如:

Exporters
Client library 有很多不适用的场景,比如不提供指标接口的第三方服务,虚拟机,或者使用了其他指标 protocal 的服务。对于这些服务来说 exporter 会更合适。Exporter 又名探针或导出器,特指本身并不是监控对象、专门通过各种手段从不提供 metrics API 的服务那里获取数据、然后转换成 metrics API 的格式暴露出来的服务。开源社区提供了大量的 exporter,官方文档里列出了一部分。
典型的例子包括:
-
node_exporter:通过读取 Linux 操作系统写在
/sys
和/proc
里的内容来暴露虚拟机指标 -
kube-state-metrics:通过 list-watch kube-apiserver 来暴露 K8s 资源状态指标
-
mysqld_exporter:通过 query MySQL 元数据数据库来暴露 MySQL 运行时指标
服务发现&指标采集
Prometheus 同时支持基于 pull 和 push 的数据采集方式,但主要是围绕 pull 来设计和优化的。Push 式的数据收集通过 push gateway 功能实现,一般只在一些边际情况下临时使用。
基于 pull 做指标采集必须要解决一个非常关键的问题:服务发现。监控系统要有实战价值,必须能应付海量的、随时上下线、随时故障的监控目标,换句话说,存在以下功能需求:
-
自动、实时地发现新的监控目标
-
根据数据内容和监控目标,过滤、删改原始数据
-
根据产生数据的监控目标,自动为监控数据添加元数据
-
能够区分监控目标正常下线和无法访问两种状态
Prometheus 能够满足以上所有需求。这里我们简单介绍一下思路,不展开具体使用方法。首先 Prometheus 支持对接各种流行的资源/服务管理系统,如 Kubernetes,AWS EC2,OpenStack 等,结合它们的能力提供服务发现功能。用户需要定义发现监控目标的策略,以及如何从这些目标上采集数据、采集哪些数据、以及是否做一定的预处理。满足服务发现策略、但又无法访问的目标,会被识别、标记。
在k8s的场景下,Prometheus 会通过 apiserver 查询用户需要监控的资源,访问服务暴露的 metric api 采集指标。
例如,我们配置 Prometheus 的采集 job,策略为访问 namespace=defaut label=platform 的 pod :
yaml
- job_name: iaas-platform
kubernetes_sd_configs:
- role: pod
namespaces:
names:
- default
relabel_configs:
- action: keep
source_labels: [__meta_kubernetes_pod_label_app]
regex: platform
配置后可以看到 Prometheus 自动发现的 targets:

拉 vs 推
Prometheus 拥抱拉取而不是推送的模式是一个刻意的选择。拉模式相对推模式有一些明显的好处:
- 拉取方结合服务发现能力,能够很容易地在采集阶段基于服务发现信息为服务注入额外的元数据。比如如果我们采集一个 K8s Pod 的指标数据时,可以把这个 Pod 的 Namespace, Name, 所属 Node 等信息注入进它的数据里。
- 可以轻易判断监控目标健康状态:如果采集时访问失败立刻就知道目标存在故障。如果是推模式下数据停止流入了,那无法直接判断是正常终止还是异常失联。此时就必须引入另一套系统来追踪所有监控对象的健康状态。
- 可控。中心服务可以自行决定要不要拉数据,多频繁地去拉,什么时候拉。这不光是稳定性层面的可控,也是功能性层面的可控:中心服务可以保证同一个对象的同一批数据一定是同时拿到的,不用思考如何去对齐时间戳不同的数据。
不过拉模式也有它的问题:
-
不擅长捕捉持续时间极短的事件:如果一个监控对象只存活了数秒,在一个拉取周期内就被销毁了,那么数据就有很大概率被遗漏了。
-
需要中心服务主动访问其他服务,这在一些特定的网络拓补和权限模型下会带来麻烦。
Pushgateway

Pushgateway主要用于处理短期任务的指标数据。在一些场景中,存在一些无法通过常规的指标抓取方式进行监控的短期任务,比如批处理作业、临时任务等。它的功能是作为一个服务接受别人推过来的指标数据,然后把这些指标数据暴露在自己的 metrics API 上给 Prometheus 采集。
官方建议是非特殊情况不要用 Push Gateway;我建议永远不要用 Push Gateway。
有以下原因:
- Push Gateway 没有 HA 方法,官方也不打算支持。当Pushgateway不可用或崩溃重启时,可能会导致指标数据丢失,因此需要谨慎处理推送的频率和数据的有效性。
- 指标缓存失效问题:因为pushgateway 无法支持服务发现,尽管它支持指标过期机制和主动注销指标,但基于它管理指标生命周期依然容易变成一场灾难。
除此之外,用户还需要确保指标数据的唯一性:推送给Pushgateway的指标数据需要保证唯一性,避免任务每次运行都重新推送,并且无法支持像 pull 模式那样注入额外的元数据信息。
存储:时序数据库或外部存储
采集到了数据,自然需要支持存储和查询。Prometheus 默认使用它自带的时序数据库 TSDB(是的,这个数据库就叫 TSDB)。这个 TSDB 的数据压缩方式是基于 Facebook 的 Gorilla TSDB 来开发的,能够做到样本平均大小 1.3 bytes 左右的压缩比,单实例支持每秒数百万样本的采集量。
但Prometheus 的 TSDB 有一个明显的缺点:原生不支持任何形式的多活,无法实现不同实例间的数据同步。这意味着如果不借助任何外力,Prometheus 必然存在性能瓶颈和单点故障的问题。对于这个问题,一般通过以下几种思路解决:
-
为 Prometheus 挂载一个网络存储,搭配类似 Kubernetes 的服务调度系统,允许其在各节点间漂移。
-
和上述方案类似但不用网络存储,而是通过 Remote Read/Write 功能对接一个优化得当的外部时序数据库。
- Remote Write 时 Prometheus 给 TSDB 写数据时同时给外部数据库写一份,而热点数据会选择不走 Remote Read
-
Q: 为什么 Remote Write 要同时给 TSDB 写一份?不浪费吗?
A: 只要把 retention(保留时间) 设得很短,比如 2 个小时,就不会造成显著的浪费。这些热点数据可以被用来加速查询(热点数据选择不走 Remote Read)或者被用来解析告警规则。Prometheus 的开发者也承认直接关闭 local storage 也有它的价值,但价值很低,所以不是高优功能。
-
联邦机制。运行多个 Prometheus 实例,每个实例不必采集所有监控目标,可做分工。通过 Thanos 或类似工具接管对它们发出的查询请求,在查询时做数据去重。
数据展示
数据查询语法 PromQL
PromQL(Prometheus Query Language)是 Prometheus 内置的数据查询语言,它能实现对 时间序列数据的查询、聚合、逻辑运算等。
打个比方, PromQL 就是 Prometheus 时序数据库中 SQL。 PromQL 广泛存在于以 Prometheus 为核心的监控体系中, 所以需要用到数据筛选的地方,就会用到 PromQL; 在实际应用中, 可视化面板的配置、监控告警的配置都离不开 PromQL 的使用。
一个简单的 PromQL示例:
ini
top_http_requests_total
top_http_requests_total{service="iam"}
sum(top_http_requests_total{service="iam"})
对比一下 InfluxQL(另一个时序数据库的查询语句)。。。。
sql
SELECT * FROM cpu_usage WHERE time > '2022-01-01T00:00:00Z' AND time < '2022-02-01T00:00:00Z'
PromDash和Grafana
数据可视化在监控系统中扮演着重要的角色,能够帮助用户更直观地理解和分析监控数据。两个常用的工具是Grafana和PromDash,它们都可以与 Prometheus 集成,帮助用户创建仪表盘并展示监控数据。
-
PromDash:PromDash是Prometheus自带的基本仪表盘工具,可以用于创建简单的监控仪表盘。PromDash提供了一些预定义的视图和图表类型,用户可以通过简单的配置和模板语言创建监控面板。
- 我的评价是:很简陋,不如我的男神东杰在可观测中心写的 meta dashboard。
-
Grafana:Grafana是一个流行的开源数据可视化工具,支持多种数据源,包括 Prometheus、ES、influxDB等。通过Grafana,用户可以创建各种仪表盘和图表,展示监控数据的实时变化和趋势。Grafana具有丰富的图表库和灵活的布局选项,用户可以通过编写 PromQL 配置自定义仪表盘的外观和内容。
告警模块Alertmanager
Alertmanager是一个独立的告警模块,接收Prometheus等客户端发来的警报,然后通过去重、分组、聚合、静默、抑制等处理,并将它们通过路由发送给正确的接收器;Alertmanager支持Email、Slack、webhook等告警方式。
Prometheus
生态中的警报是在 Prometheus
中计算警报规则(Alerting Rule
)产生的,而所谓计算警报规则,其实就是周期性地执行一段 PromQL
,得到的查询结果就是警报,比如:
node_load5 > 20
只是,当 Prometheus 计算出一些警报后,它自己并没有能力将这些警报通知出去,只能将警报推给 Alertmanager,由 Alertmanager 进行发送。
这个切分,一方面是出于单一职责的考虑,另一方面则是因为警报发送确实不是一件"简单"的事,需要一个专门的系统来做好它。可以这么说,Alertmanager 的目标不是简单地"发出警报",而是"发出高质量的警报"。它提供的高级功能包括但不限于:
-
渲染警报内容;
-
管理警报的重复提醒时机与消除后消除通知的发送;
-
根据标签定义警报路由,实现警报的优先级、接收人划分,并针对不同的优先级和接收人定制不同的发送策略;
-
将同类型警报打包成一条通知发送出去,降低警报通知的频率;
-
支持静默规则: 用户可以定义一条静默规则,在一段时间内停止发送部分特定的警报,比如已经确认是搜索集群问题,在修复搜索集群时,先静默掉搜索集群相关警报;
-
支持"抑制"规则: 用户可以定义一条"抑制"规则,规定在某种警报发生时,不发送另一种警报,比如在"A 机房网络故障"这条警报发生时,不发送所有"A 机房中的警报";
Prometheus 不是什么
我们已经对 Prometheus 能做什么有了一个初步了解,现在我们明确 Prometheus 不能做什么:
-
Prometheus 不适用存储非时序数据,例如日志,事件,以及 tracing。
-
对精准度要求极高的场景:Prometheus 是专门为监控场景优化的,因此用牺牲 ~0.1% 的精准度为代价换取了极佳的性能。因此,Prometheus 并不适用于金融级监控或其他涉及钱的场景。
-
对时序数据库功能的丰富性有要求的场景:很多非监控场景对时序数据库有需求时,往往需要的是一个功能相对完整的数据库。Prometheus 为了追求最佳的数据压缩比,牺牲了很多存储层的功能。比如 Prometheus 不支持修改已采集的数据,不支持批量导入非实时数据。
此处再加一个观点观点,Prometheus 不是很擅长处理大时间范围的数据(比如一个月以上的数据)。具体体现在 Prometheus 的 aggregate over time 操作(越时间聚合)的性能一般,而且基本没有针对这个场景优化的功能。这是因为 Prometheus 在设计阶段吸取了 Facebook Gorilla 项目的经验;而 Gorilla 的维护者总结过一个重要经验:一个典型的监控系统中,往往只有最近 26 个小时的监控数据受关注。所以需要权衡热数据和冷数据的性能时,Prometheus 都坚定地牺牲掉了冷数据。
为什么 Prometheus 自身架构中,既没有消息队列确保指标采集的可靠性,也没有高可用的数据存储确保指标不丢失?
Prometheus 并不是以 100% 可靠而设计的,偶尔遗失一个数据点对它影响并不大,甚至是考虑在设计中的。那么长时间(分钟级)的缺失数据呢?这当然会导致告警失效,但由于 Prometheus 是一个有服务发现功能的、基于拉而不是推的监控引擎,数据持续缺失或告警目标无法访问本身就是一个可识别、可告警的故障。所以更复杂的架构并没有给告警的实际可靠性带来太大提升,最大的意义是让我们能在故障恢复后能看到完整的监控图。而监控数据不同于日志,遗漏一些信息相对不容易为事后的故障调查带来问题。
而我们的代价呢?别忘了我们往一个系统中加入的每一个新组件都是有代价的。引入的组件本身都需要被保证可靠性。而监控系统对这样的复杂性,相比其他业务系统,会格外的敏感。因为我们最需要一个告警系统的时候,是在系统发生故障的时候;一个故障可能会引起一系列故障。我们希望在这种情况下,监控系统能面对一个四处起火的数据中心,仍然能保证一定的局部可用性。把能报的警报出来,把能采集的数据采集到。加入更多的组件和复杂度,必然会让我们在大规模故障场景下做好 Prometheus 可靠性的难度显著增加。
回到简介章节的一句话:在它选择做的事情上,它做得都非常好。