Prometheus Doc 结构良好、条理清晰,这篇博客相当于概览,希望深入时建议扩展阅读官方文档。博客写作时 Prometheus 的版本为 2.51.0 。
简介
Prometheus 是 SoundCloud 于 2012 年开源的监控和告警工具,以独立开源项目的形式运行,为强调这一点,在 2016 年成为 CNCF 的第二个托管项目,是目前最流行的开源监控和告警工具,除技术实现以外,它还包含数据模型、指标类型等概念,为当下的监控系统建设提供了指导。
关键特性为:
- 多维数据模型,包含指标名称和键值对标签的时序数据;
- PromQL ,支持维度的查询语言;
- 不依赖分布式存储,单服务节点自治;
- 基于 HTTP 的 PULL 模式采集时序数据;
- 支持 PUSH 模式采集数据;
- 通过服务发现或静态配置发现目标;
- 多种图形和仪表盘支持模式;
以上特性可以看作是监控系统设计的简单描述,从数据定义、采集扩展、架构设计、查询展示方面做了指导,后面的章节会专门提到这些内容。
概念
概念指出了 Prometheus 引入的关键设计,理解它们非常重要。
数据模型
即关键特性中提到的多维数据模型,它描述了指标数据的结构和存储方法:
- 指标由指标名和可变的标签集合组成;
- 指标以时间序列的方式存储;
- 指标名和标签集合唯一标识了一个时间序列;
- 查询时服务端可以生成临时的时间序列;
简单总结一下,指标描述了一类数据,而标签集合描述了这类数据的一个实例,标签集合变化就意味着有新的实例产生,也就有了新的时间序列产生。
举例来说,指标 api_http_requests_total
表示 HTTP API 被请求的总次数,这是一个抽象的、概括性的描述,因为具体来看还有请求方法、端点等很多细节上的差异,下面的例子形象表达了这些差异,它们分属于不同的时间序列:
# 使用 POST 方法请求 /messages 端点的次数
api_http_requests_total{method="POST", handler="/messages"}
# 使用 POST 方法请求 /users 端点的次数
api_http_requests_total{method="POST", handler="/users"}
通过以上举例就能更容易理解标签集合的描述能力以及如何基于标签做过滤和聚合,也能想象到实际应用时一定有非常多的时间序列产生,这涉及到时序数据的高基问题,属于存储实现的细节。在数据结构与表示方面 Prometheus 可能参考了 OpenTSDB 。
另外,可以看到这里涉及了指标、标签的命名,最佳实践部分包含了相关规范,相当于引入了数据治理的方法,可以参考 Metric and label naming 。
指标类型
Prometheus 客户端库提供了四种基本类型,这种类型区分只存在于客户端库和 wire protocol (上层的、用于应用在网络中通信的协议)中,存储层是无类型的,未来可能改变。四种基本类型分别是:
- Counter,用于描述一个单调递增的值,它在某些情况下复位归零,例如接收字节数,通过时间戳和差值可以做相关计算;
- Gauge,用于描述一个可以增加或减少的值,例如内存使用率;
- Histogram,用于描述分 Bucket (桶)统计的结果,Bucket 可配置,输出多个时间序列,包含每个桶内数据的计数、总计数和数据和,在服务端完成百分位数计算,桶设置不合理的情况下引入的误差相对较大;
- Summary,与 Histogram 类似,可以替代 Histogram ,但它是根据配置在滑动时间窗口中由客户端完成百分位数计算,合理配置的情况下更加精确,不过会更加消耗客户端资源;
以上类型中 Counter 和 Gauge 比较容易理解,在某个时间点取值即可,相比之下 Histogram 和 Summary 更难理解,某些情况下需要从二者中选取一个使用, 文档 Histograms and summaries 为这种选择提供了最佳实践,更加直观的参考可以阅读博客 prometheus的summary和histogram指标的简单理解 ,这里不做深入。
除四种基本类型外,可以在客户端库看到 Info 和 Enum 类型,参考 client_python/Instrumenting 。
指标部分还列出了官方支持的多语言客户端库( Go、Python、Java、Ruby、.Net),这里展示一个修改后的 Python 客户端库示例,用来观察如何暴露指标:
import random
import time
from prometheus_client import start_http_server, Histogram
from prometheus_client.utils import INF
# Create a metric to track time spent and requests made.
REQUEST_TIME = Histogram('request_processing_seconds',
'Time spent processing request',
['function'],
buckets=(.1, .3, .5, 1.0, INF))
# Decorate function with metric.
@REQUEST_TIME.labels('process_request').time()
def process_request(t):
"""A dummy function that takes some time."""
print('sleep: %s', t)
time.sleep(t)
if __name__ == '__main__':
# Start up the server to expose the metrics.
start_http_server(8000)
# Generate some requests.
while True:
process_request(random.random())
访问 http://localhost:8000/
端点将输出指标信息,以下示例包含了我添加的注释(由 ---
包裹):
--- 以下是解释器相关的指标 ---
# HELP python_gc_objects_collected_total Objects collected during gc
# TYPE python_gc_objects_collected_total counter
python_gc_objects_collected_total{generation="0"} 527.0
python_gc_objects_collected_total{generation="1"} 110.0
python_gc_objects_collected_total{generation="2"} 0.0
# HELP python_gc_objects_uncollectable_total Uncollectable objects found during GC
# TYPE python_gc_objects_uncollectable_total counter
python_gc_objects_uncollectable_total{generation="0"} 0.0
python_gc_objects_uncollectable_total{generation="1"} 0.0
python_gc_objects_uncollectable_total{generation="2"} 0.0
# HELP python_gc_collections_total Number of times this generation was collected
# TYPE python_gc_collections_total counter
python_gc_collections_total{generation="0"} 39.0
python_gc_collections_total{generation="1"} 3.0
python_gc_collections_total{generation="2"} 0.0
# HELP python_info Python platform information
# TYPE python_info gauge
python_info{implementation="CPython",major="3",minor="11",patchlevel="4",version="3.11.4"} 1.0
--- 以下是 request_processing_seconds 指标,携带在代码中定义的标签 ---
# HELP request_processing_seconds Time spent processing request
# TYPE request_processing_seconds histogram
--- 响应时间区间分布计数,例如响应时间小于 1s 的请求有 6 个 ---
request_processing_seconds_bucket{function="process_request",le="0.1"} 6.0
request_processing_seconds_bucket{function="process_request",le="0.3"} 17.0
request_processing_seconds_bucket{function="process_request",le="0.5"} 26.0
request_processing_seconds_bucket{function="process_request",le="1.0"} 56.0
request_processing_seconds_bucket{function="process_request",le="+Inf"} 56.0
--- 截至收集时间点共有 56 个请求 ---
request_processing_seconds_count{function="process_request"} 56.0
--- 总耗时 27.415047799004242s ---
request_processing_seconds_sum{function="process_request"} 27.415047799004242
# HELP request_processing_seconds_created Time spent processing request
# TYPE request_processing_seconds_created gauge
--- 时间戳,可以通过配置禁止这个指标 ---
request_processing_seconds_created{function="process_request"} 1.7114456133324618e+09
值得关注的另外一个点是 Histogram 和 Summary 设计在可观测方面的考量,它们支持两类统计:
- Apdex 评分, Apdex 标准从用户的角度出发,将对应用响应时间的表现,转为用户对于应用性能的可量化范围为 0-1 的满意度评价;
- 百分位数,可表示为一组 n 个观测值按数值大小排列,处于p%位置的值称第p百分位数,例如50%的请求响应时间在 200ms 以下;
作为对比, ElasticSearch 能够通过聚合每个数据点得出百分位数,但是在指标设计层面没有特殊设计,类似于 {"value": 10.24}
,不过我不确定现在的情况。
这里可能还会引出对于什么是可观测性的讨论,可观测性( Observability )这个词来自于控制论,是指系统具备的一种特性,这种特性使得能够通过系统的输出推断系统内部的状态,如果转换到具体的语境,就必然会提出几个问题:
- 系统有哪些输出?
- 系统内部状态指什么?
- 怎样从输出推断内部状态?
- 内部状态有什么用?
对于 IT 系统来说,尝试回答第一个问题时,就会发现系统的输出不仅有指标,也就是说采用 Prometheus 让系统具备了不全面的可观测性,而 OpenTelemetry 作为目前可观测领域的事实标准,定义了系统输出的三大支柱:指标、日志、追踪,并为不同类型数据的联合提供了可行的方法和实现。
似乎可观测也只是数据的采集、展示和告警,是一种更细微、更时髦的监控,这里的差别主要体现在应对现代云原生应用的弹性和微服务架构的方式。监控诞生于确定性更高的时代:应用发布次数屈指可数,部署架构也保持在一个相对稳定的状态,故障多通过施加于操作系统指标的固定阈值规则实现,根因分析依赖各类专家对操作系统、应用程序的剖析,这种情况下组织更倾向于积累经验,根本上还是低复杂度系统下经验有效。在云原生时代,应用和部署都处于频繁的变化状态中,支撑这种变化能力的又是大量的分层和抽象,如果一个现象背后可能有大量的全新的待排查分支,那就意味着经验开始失效,我们需要更加全面、直接和细致的数据以及一个更加智能的分析工具来共同构成系统的可观测性,摆脱对经验的依赖,具体来说应该是顺着工具提供的工作流深入能快速准确地锁定根因,而不是“这种情况网络问题的可能性比较大,先看看网络”。此时,重新看 Prometheus 的设计和实践就能发现一些可观测性的端倪。
上面阐述的可观测的核心实际在于原生设计和全面入微,如果系统的可观测性输出都是原生的,数据的导出似乎会成为一个公共的部分,基于数据的应用将是各类可观测产品主要的竞争点。
这里也提供了一些其他观点:
- 可观测在于应对动态、弹性的环境,而监控是对已知对象和模式的检查,依赖预先配置的仪表盘,这个观点来自顶级干货:国际一线厂商 Dynatrace 如何看待可观测,这是一篇2022年的翻译文章;
- 可观测的建设臣服于事实标准 OTEL ,从而被其限制了心智,认为对系统的洞察只能在三大支柱类似的分类数据中诞生,在 Meta 的实践中观测似乎类似于使用 Kibana 探索数据的体验,可以参考原文你唯一需要的是 Wide Events ,而非 Metrics 、 Logs 、 Traces ,这篇文章的观点比较有趣,而我也参与过类似的项目,当时我认为旧系统很难拿到三大支柱才使用折衷的方式来解析并探索日志,但是后来接触到参考 OTEL 设计的产品时才发现探索数据是多么美好的体验,比如发现整体的耗时增加了,是全部渠道还是特定渠道,继续下钻呢?这几乎可以立刻着手分析,这些数据自身就富含非常多维度,而不是依赖一个集成的标签页窗口:“指标 | 日志 | 追踪 | 事件”;
- 运维部门费力建设庞大的指标库,他们自己又用不到,应该去使用这些指标的研发部门也不去用,详情可以参考解惑“可观测性”与“监控”的不同,这似乎是一个涉及 DevOps 的更高层面的问题,我倒是认为 Prometheus 就是这样设计的,谁接入的指标谁关心,而不会有一些非常适合集中责任制的功能,比如指标中心之类的。
关于指标的讨论和发散就到这里。
任务和实例
任务和实例是一个常见的抽象,实例( INSTANCES )对应一个采集端点,通常是一个进程,一般的架构方法中,将一组相同的进程挂接在负载均衡后提供服务,这样的一组进程就是任务( JOBS )。在具备自动伸缩能力的环境中,任务的实例数是变化的,为了区分实例, Prometheus 具备自动添加标签的能力,通常是 <host>:<port>
,任务的标签依赖配置的任务名称。
远程写入规范
规范中主要描述了 Prometheus 和兼容 Agent 如何将数据发送至 Prometheus 和兼容的接收器,这是一份遵循 RFC2119 的标准文件,它指出了远程写入过程中的协议、顺序、重试等方面的考量点,打算深入系统实现时必须了解这些内容,更多内容查看 Prometheus Remote-Write Specification 。
架构
从上面的架构图上看,有以下几个重要组件:
- Prometheus server ,负责收集和存储时间序列,执行数据分析和应用规则;
- Push gateway ,用于接收主动推送的数据;
- Exporter ,导出应用依赖组件指标的代理,是一个暴露 HTTP 接口的进程;
- Alert manager ,处理事件,例如去重、分组、通知路由等;
- Client libraries ,从应用程序中导出指标,对应架构图中的 Job ;
从数据流的角度描述架构会更加流畅,架构图中描述了三种数据源:
- 常驻进程, Prometheus 倡导目标程序以 HTTP 接口的形式暴露指标,并由服务端拉取,对于应用来说可以通过引入 Client libraries 实现,对于无法控制的代码,例如数据库、中间件等,由 Exporter 进行适配并暴露指标接口;
- 短时进程,对于不确定何时产生和消亡的进程,例如跑批,服务端坚持采用拉取模式必然会增加复杂性,例如与短时任务的管理器产生耦合,显然是不可取的,因此通过 Push gateway 支持了推模式来适应这种情况,它不能代替拉模式,从而将 Prometheus 变为一个推模式的监控系统;
- 水平伸缩的进程,典型的场景就是 K8s 中副本集下 Pod 扩缩容,使用拉模式,服务端必须清楚的知道采集端点,此时就需要自动发现,可以理解为自动生成配置,比如查询名字服务(服务发现组件,例如 Nacos )、查询 CMDB API 或者通过监听文件变更等;
数据处理部分从大的方面看比较简单,在本地存储和处理数据、单节点自治,相比采用分布式存储的系统更加脆弱,但胜在简单,从而降低了大规模监控系统建设的复杂性和成本,本质上还是这类数据的临时性驱动的取舍。服务端内部模块划分比较清晰:拉取数据、存储和分析、对外提供服务。
另外就是告警事件处理和用户查询界面,值得一提的是提供了专用的 DSL 语言 PromQL。
从全局来看, Prometheus 是基于拉模式的系统,它降低了客户端和服务端设计的复杂性,否则就要考虑维护会话、缓存、队列等一系列问题,这种简单取向的设计适合通过联邦的方式构建大规模的监控系统,而不是依赖一个强大的中心服务,官方以联邦集群的方式构建跨数据中心或跨服务的监控系统, Prometheus server 以子节点和父节点的角色参与构建树状拓扑的系统。有必要介绍提到的两种场景:
- 跨数据中心,每个数据中心的子节点收集本中心的细粒度数据,父节点从子节点收集数据,并存储聚合后的数据;
- 跨服务,例如应用通过客户端库暴露的指标和运行应用的基础设施指标可能被不同的节点收集,而在分析时需要统一两个数据源,此时可以在父节点实现数据集成;
联邦集群是对水平扩展的支持,并没有解决高可用,了解这个主题请阅读Prometheus高可用,这篇文章来自于开源书籍 prometheus-book (感谢作者)。
横向对比
这部分可以直接阅读 Comparison to alternatives ,文档从功能、数据模型、存储、架构等方面比较了以下几种替代方案:
- Graphite
- InfluxDB
- OpenTSDB
- Nagios
- Sensu
这些内容能够帮助调研和选型,相较于这篇博客的目的来说不做详细展开,只需要强调 Prometheus 的一些特点:
- 对动态环境有针对性设计,这在前面描述数据模型、架构设计时都有涉及,非常适合 K8s;
- 不适合用于整合数据模型不同的多个监控平台;
- 具备突出的查询和分析能力;
- 完全开源,不存在闭源的商业部分;
还有在 Overview 中指出的, Prometheus 面向高可靠性设计,正确性不足,所以不能用于计费这种要求准确性的场景。
开始使用
这部分将从 First steps with Prometheus 开始,部署一个实例进行演示,还会包括 Alertmanager 、 PromLens 、 Grafana 项目,这是认识项目的关键一环,大体步骤如下:
-
下载
prometheus-2.51.2.linux-amd64.tar.gz
文件后解压, Prometheus server 是一个独立的二进制文件,集成了 Retrieval 、 TSDB 、 HTTP Server ; -
修改目录下的配置文件
prometheus.yml
,完整配置参考查看 Configuration , Prometheus 能够接收SIGHUP
信号重载配置,无需重启进程,安装包自带了简单的样例配置:
global: # 服务端配置
scrape_interval: 15s # 从目标拉取指标的频率
evaluation_interval: 15s # 应用规则进行计算的频率,以生成新的时间序列或告警
rule_files: # 需要服务端加载进行计算的规则文件
# - "first.rules"
# - "second.rules"
scrape_configs: # 配置采集目标
- job_name: prometheus # prometheus 通过 HTTP 端点暴露自身指标,因此可以监控自身
static_configs:
- targets: ['localhost:9090'] # 真实的采集端点是 http://localhost:9090/metrics
- 执行命令
./prometheus --config.file=prometheus.yml
启动服务端,启动速度很快,默认监听在0.0.0.0
上,通过http://<ip>:9090
访问控制台,概览下几个主要页面,告警页显示所有的告警:
图表页能够应用表达式查询数据并生成图表:
状态页能够查询服务端的指标、状态、配置、监控目标列表等:
4. 使用指标浏览器,在输入框中输入表示服务端 /metrics
端点请求总数的指标 promhttp_metric_handler_requests_total
可查询到指定时间范围内的时间序列和最新值:
可以看到该指标由于响应码 code
标签值的差异生成了3个时间序列,更改查询语句:
promhttp_metric_handler_requests_total{code="200"}
对指定标签做查询count(promhttp_metric_handler_requests_total)
统计了该指标的时间序列数量rate(promhttp_metric_handler_requests_total{code="200"}[1m])
统计了指定返回码的请求每秒发生的速率,rate()
是一个函数,[1m]
表示使用过去一分钟的数据做统计,切换到图形页可以看到时序图表示
表达式语法参考 Querying Prometheus ,有一些似乎不高的学习成本;
- 下载并安装
Alertmanager
,它向接收者发出报警,支持告警分组、抑制、静默,也是一个携带示例配置的独立二进制文件,执行命令./alertmanager --config.file=alertmanager.yml
启动,通过http://<ip>:9093
访问控制台,修改 Prometheus 的配置文件,将其指向启动的 Alertmanager 实例:
alerting:
alertmanagers:
- static_configs:
- targets:
- localhost:9093
方便起见重启进程以启用配置。从 Alertmanager 界面可以发现只能通过配置文件启用添加各类规则:
此组件的细节参考 Alertmanager ;
- 下载并安装
PromLens
,它是一个 PromeQL 语言的构建、分析、可视化工具,仍然是一个独立的二进制文件,执行命令./promlens
启动,通过http://<ip>:8080
访问,它能够格式化查询语句,给出查询结果和语句分析,还能以表单的方式编辑查询语句,此外能支持 Grafana 集成和链接分享,进一步降低了 PromeQL 的使用成本:
按照现在的 AI 趋势,可能很快就能通过自然语言做数据查询了。此组件的细节参考 promlens ;
- 至此所有启用的工具仅提供基本的可视化支持,现在我们需要启用非常重要的组件 Grafana ,这里使用二进制 tar 包的方式安装,解压后执行命令
./bin/grafana server
启动,通过http://<ip>:3000
访问,初次访问的用户名和密码均是admin
,登录后在Connections - Data sources
菜单中添加 Prometheus 数据源并导入 Dashboard ,就可以看到这样的仪表盘了:
扩展、集成、安全和其他
扩展方面参考 Instrumenting 文档;集成方面参考 Integrations 文档,之前的概念和架构部分涉及了扩展和集成的核心,文档中给出了更具体的建议。
安全方面可能是大多数开源项目比较孱弱的部分,因为对安全的定义决定了对安全的阐述,除通用的数据传输层面外,权限控制是一些组织非常关注的功能,表现上可能存在非常大的差异性, Security Model 文档比较全面地阐述了 Prometheus 体系的安全设计和风险情况。
从外还包括报警和最佳实践方面的内容,都能够通过官方文档获得帮助。
总结
但愿这篇博客能够更加直观的帮助你了解 Prometheus 体系,显然这里说的不仅仅是如何启动二进制文件,自己动手尝试一下显然是更好的, Prometheus 缺少适合集中管控组织的的功能设计,比如权限、指标中心、告警路由、拓扑分析之类的,与 CMDB 等系统的集成也需要做一些定制,但它仍然是非常优秀的,似乎更加适合 DevOps 类型的组织,由保持统一目标的融合团队使用。一些增强类的项目,比如国内的夜莺,开源版做了一些初级的集成和增强,也降低了一些使用 Prometheus 的心智负担,例如界面化的告警规则配置,商业版则增强了一针对 SLO 的设计、更好的采集体验等,可能还有一些个性化的 SRE 建设方案,但总体上还是保持简单的理念。完全商业的产品似乎都非常复杂,以至于厂商和客户需要绞尽脑汁发掘数据的应用场景来争取建设预算。我觉得可观测的未来一定是趋向简单的、数据应用解耦以及更加智能化的。
这里补充一些场景,在 Prometheus UI 中查找指标:
恐怕只有完全记住指标的名称才可以,集成 Grafana 后有所好转,可以在界面看到指标名称和描述信息,但也存在过滤器简单的情况:
目前很多企业在运维工具领域选择二开和自研,希望能够看到更多集成后的实用方案,或者能够有理论来支持在组织中使用一个开源项目。