何为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.deploymentsfor 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_numberrabbitmq_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_numberyield consumers_rabbitmq_message_numberyield messages_rabbitmq_message_numberyield messages_ready_rabbitmq_message_numberyield 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)