4、指标采集体系:Prometheus架构详解、Exporter开发实战、ServiceMonitor与PodMonitor配置、指标采集性能优化
好,咱们进入指标采集这个核心环节。说实话,整个可观测性体系里,指标是最基础也是最关键的一环。没有可靠的指标采集,后面所有的告警、分析、可视化都是空中楼阁。今天我就把Prometheus这套东西掰开揉碎了讲清楚。
4.1 Prometheus架构:不只是拉取那么简单
很多人觉得Prometheus就是个时序数据库,其实它的架构设计很有讲究。我刚开始接触时也犯过迷糊,以为它跟Zabbix一样是推模式。后来踩了坑才明白,Prometheus的核心是拉取(Pull)模式。
它的架构可以拆成几个关键组件:
- Prometheus Server:负责拉取指标、存储数据、执行查询
- Exporter:把业务或系统指标转换成Prometheus能理解的格式
- Pushgateway:处理短生命周期任务的指标推送
- Alertmanager:处理告警路由和去重
- Service Discovery:动态发现目标,这在K8s里特别重要
为什么Prometheus坚持用拉取模式?你想想看,如果每个服务都往中心推数据,一旦网络抖动或者中心挂了,数据就丢了。拉取模式的好处是:你随时可以知道目标是否存活。我在项目中遇到过,某个服务推模式下的指标突然断了,排查了半天才发现是客户端挂了,但Prometheus那边完全不知道。
核心要点:Prometheus的拉取模式天然支持健康检查。如果拉取失败,说明目标有问题,这本身就是一条重要指标。
4.2 Exporter开发实战:从零写一个自定义Exporter
说实话,市面上现成的Exporter已经很多了,但总有业务场景需要自己写。我记得有一次,客户需要一个监控Redis集群中每个分片连接数的Exporter,官方Exporter做不到那么细,只能自己动手。
写一个Exporter其实不复杂,核心就三步:
- 暴露一个HTTP端点(通常是
/metrics) - 在端点处理函数中采集指标数据
- 用Prometheus客户端库注册并返回指标
我拿Python举个例子,这是最常用的方式:
from prometheus_client import start_http_server, Gauge
import time
import random
# 定义一个Gauge类型的指标
app_requests_total = Gauge('app_requests_total', 'Total number of requests', ['method', 'endpoint'])
def collect_metrics():
while True:
# 模拟采集业务指标
app_requests_total.labels(method='GET', endpoint='/api/v1/users').set(random.randint(100, 500))
app_requests_total.labels(method='POST', endpoint='/api/v1/orders').set(random.randint(50, 200))
time.sleep(15)
if __name__ == '__main__':
start_http_server(8000)
collect_metrics()
嗯,这里要注意几个坑:
- 指标命名规范:用下划线分隔,不要用驼峰。比如
app_requests_total而不是appRequestsTotal - 标签不要太多:每个指标标签组合太多会导致基数爆炸。我曾经见过一个Exporter,标签组合超过10万种,直接把Prometheus内存打爆了
- 采集频率别太高:默认15秒一次就够了,别设成1秒,除非你确定业务能扛住
个人经验:写Exporter时,一定要加上_info或_created这类元数据指标。比如app_build_info{version="1.2.3"},这样查问题时能快速知道当前跑的是哪个版本。
4.3 ServiceMonitor与PodMonitor配置:K8s下的动态发现
在Kubernetes里,手动配置每个目标是不现实的。Pod随时可能重建,IP也会变。这时候就需要ServiceMonitor和PodMonitor出场了。
说白了,这两个资源就是告诉Prometheus Operator:你要监控哪些Pod,怎么找到它们。
先看ServiceMonitor的配置示例:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: my-app-monitor
namespace: monitoring
spec:
selector:
matchLabels:
app: my-app
endpoints:
- port: metrics
interval: 30s
path: /metrics
namespaceSelector:
matchNames:
- production
这个配置的意思是:在production命名空间里,找到所有带app: my-app标签的Service,然后通过名为metrics的端口,每30秒拉取一次/metrics路径。
PodMonitor跟ServiceMonitor的区别是什么?我简单说一下:
- ServiceMonitor:通过Service的标签选择器来发现目标,适合有Service的场景
- PodMonitor:直接通过Pod的标签选择器来发现目标,适合不需要Service的场景(比如Job或CronJob)
我曾经踩过一个坑:某个服务没有创建Service,直接用PodMonitor配置了,但Prometheus怎么都拉不到数据。排查了半天才发现,PodMonitor需要Pod的端口有名字,而且名字必须跟配置里的port字段一致。
避坑指南:PodMonitor的端口名必须跟Pod spec里定义的端口名完全匹配,大小写敏感。我曾经因为端口名写成了Metrics(首字母大写),而Pod里定义的是metrics,结果浪费了两个小时。
4.4 指标采集性能优化:别让监控拖垮业务
指标采集本身也会消耗资源。我见过最夸张的情况,一个Prometheus实例采集了50万个时间序列,查询响应时间超过10秒,CPU跑满。所以性能优化是必须的。
我总结了几条核心优化策略:
| 优化方向 | 具体做法 | 效果 |
|---|---|---|
| 减少时间序列 | 控制标签基数,避免高基数标签(如user_id、request_id) | 大幅降低存储和查询压力 |
| 调整采集频率 | 非关键指标从15秒改为30秒或60秒 | 减少网络和CPU开销 |
| 使用Relabeling | 丢弃不需要的指标,比如__meta_*标签 |
减少存储量 |
| 分片采集 | 用多个Prometheus实例分担采集任务 | 线性扩展采集能力 |
| 启用压缩 | 开启Prometheus的snappy压缩 | 减少网络传输量 |
这里重点说说Relabeling,这是个很实用的技巧。比如你只想保留app_requests_total这个指标,其他都丢掉:
scrape_configs:
- job_name: 'my-app'
metric_relabel_configs:
- source_labels: [__name__]
regex: 'app_requests_total'
action: keep
嗯,这个配置的意思是:只保留指标名匹配app_requests_total的指标,其他的全部丢弃。我在项目中用过这个技巧,把采集量从20万降到了3万,效果立竿见影。
我的建议:不要一开始就追求全量采集。先采集核心指标,运行一段时间后,根据实际需求逐步增加。我曾经见过一个团队,上来就采集了所有JVM指标,结果发现90%的指标根本没人看,白白浪费资源。
最后说一句,指标采集性能优化是个持续的过程。你可以在Prometheus的/status页面看到当前的时间序列数量,如果超过100万,就该考虑优化了。我个人的经验是,单个Prometheus实例管理50万以内的时间序列是比较舒服的,超过这个数,建议做分片或者升级硬件。