Prometheus之exporter详解
何为exporter
Prometheus 监控基于一个很简单的模型: 主动抓取目标的指标接口(HTTP 协议)获取监控指标, 再存储到本地或远端的时序数据库. Prometheus 对于指标接口有一套固定的格式要求, 格式大致如下:
# HELP http_requests_total The total number of HTTP requests.
# TYPE http_requests_total counter
http_requests_total{method="post",code="200"} 1027
http_requests_total{method="post",code="400"} 3
对于自己写的代码, 我们当然可以使用 Prometheus 的 SDK 暴露出上述格式的指标. 但对于大量现有服务, 系统甚至硬件, 它们并不会暴露 Prometheus 格式的指标. 比如说:
Linux 的很多指标信息以文件形式记录在 proc 下的各个目录中, 如 /proc/meminfo 里记录内存信息, /proc/stat 里记录 CPU 信息;
Redis 的监控信息需要通过 INFO 命令获取;
路由器等硬件的监控信息需要通过 `SNMP 协议获取;
…
要监控这些目标, 我们有两个办法, 一是改动目标系统的代码, 让它主动暴露 Prometheus 格式的指标, 当然, 对于上述几个场景这种办法完全是不现实的. 这时候就只能采用第二种办法:
编写一个代理服务, 将其它监控信息转化为 Prometheus 格式的指标
——这个代理服务就是exporter
简介
广义上讲所有可以向Prometheus提供监控样本数据的程序都可以被称为一个Exporter。而Exporter的一个实例称为target。
从Exporter的来源上来讲,主要分为两类:
社区提供的
Prometheus社区提供了丰富的Exporter实现,涵盖了从基础设施,中间件以及网络等各个方面的监控功能。这些Exporter可以实现大部分通用的监控需求。
https://exporterhub.io/用户自定义的
除了直接使用社区提供的Exporter程序以外,用户还可以基于Prometheus提供的Client Library创建自己的Exporter程序,目前Promthues社区官方提供了对以下编程语言的支持:Go、Java/Scala、Python、Ruby。同时还有第三方实现的如:Bash、C++、Common Lisp、Erlang,、Haskeel、Lua、Node.js、PHP、Rust等。
编写指导
Prometheus 官方文档中 Writing Exporter 这篇写得非常全面, 假如你要写 exporter 推荐先通读一遍, 限于篇幅, 这里只概括一下:
做到开箱即用(默认配置就可以直接开始用)
推荐使用 YAML 作为配置格式
指标使用下划线命名
为指标提供 HELP String (指标上的 # HELP 注释, 事实上这点大部分 exporter 都没做好)
为 Exporter 本身的运行状态提供指标
可以提供一个落地页
实现
Prometheus 官方提供了client library 来帮助开发者简化exporter的开发工作。
client library官方支持语言:
Go
Java or Scala
Python
Ruby
Rust
也有社区支持的其他语言库如C、C++、PHP等
下面以python简单介绍下实现过程
1. 实现collector
def collect(self):
with self._lock:
deployments = self.deployments
for value in deployments.values():
metric_labels = ["query_details"]
# rabbitmq 总体状态
if value and value.get('rabbitmq_status') is not None:
total_labels_value = {}
if value.get('list_queues_label_value') is not None:
total_labels_value['list_queues'] = value.get('list_queues_label_value')
if value.get('message_number_label_value') is not None:
total_labels_value['message_number'] = value.get('message_number_label_value')
rabbitmq_status_int = GaugeMetricFamily(
name='rabbitmq_status_int',
documentation='rabbitmq status',
labels=metric_labels,
)
rabbitmq_status_int.add_metric([str(total_labels_value)], value.get('rabbitmq_status'))
yield rabbitmq_status_int
# rabbitmq server存活状态
if value and value.get('rabbitmq_server_status') is not None:
rabbitmq_server_status_int = GaugeMetricFamily(
name='rabbitmq_server_status_int',
documentation='rabbitmq server status',
labels=['check']
)
rabbitmq_server_status_int.add_metric(['service rabbitmq-server status'],
value=value.get('rabbitmq_server_status'))
yield rabbitmq_server_status_int
# rabbitmq 消息数量
if value and value.get('message_number') is not None:
labels = ['queue', 'msg_obj']
# rabbitmq_message_number
rabbitmq_message_number = GaugeMetricFamily(
name='rabbitmq_message_number',
documentation='Number of queue message',
labels=labels,
)
# 以msg_obj为纬度的消息数量采集:方便告警规则的创建
consumers_rabbitmq_message_number = GaugeMetricFamily(
name='consumers_rabbitmq_message_number',
documentation='Number of queue message',
labels=['queue'],
)
messages_rabbitmq_message_number = GaugeMetricFamily(
name='messages_rabbitmq_message_number',
documentation='Number of queue message',
labels=['queue'],
)
messages_ready_rabbitmq_message_number = GaugeMetricFamily(
name='messages_ready_rabbitmq_message_number',
documentation='Number of queue message',
labels=['queue'],
)
messages_unacknowledged_rabbitmq_message_number = GaugeMetricFamily(
name='messages_unacknowledged_rabbitmq_message_number',
documentation='Number of queue message',
labels=['queue'],
)
queues = value.get('message_number')
# queues = {
# "queue_name": {
# "consumers": 1
# }
# }
for queue in queues.keys():
for msg_obj in queues[queue].keys():
rabbitmq_message_number.add_metric([queue, msg_obj], value=queues[queue][msg_obj])
consumers_rabbitmq_message_number.add_metric([queue], value=queues[queue]["consumers"])
messages_rabbitmq_message_number.add_metric([queue], value=queues[queue]["messages"])
messages_ready_rabbitmq_message_number.add_metric([queue], value=queues[queue]["messages_ready"])
messages_unacknowledged_rabbitmq_message_number.add_metric([queue], value=queues[queue]["messages_unacknowledged"])
yield rabbitmq_message_number
yield consumers_rabbitmq_message_number
yield messages_rabbitmq_message_number
yield messages_ready_rabbitmq_message_number
yield messages_unacknowledged_rabbitmq_message_number
2. 注册collector
REGISTRY.register(DeploymentCollector(logger, {}))
REGISTRY.register(CoreDumpCollector(logger, {}))
REGISTRY.register(ProcessCollector(logger, {}))
REGISTRY.register(RabbitmqCollector(logger, {}))
REGISTRY.register(CustomizeCollector(logger, {}))
3. 启动http服务
start_http_server(9200)
application = tornado.web.Application([
(r"/", MainHandler),
(r"/deregister/", DeregisterHandler),
(r"/upload/", UploadHandler),
(r"/alert/", AlertHandler)
])
application.listen(9201)
tornado.ioloop.IOLoop.current().start()
while True:
time.sleep(1)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统