prometheus监控系统

prometheus 介绍

Prometheus 是一款时序(time serier)数据库,但它的功能却并不止于 TSDB,而是一款设计用于进行目标(Target)监控的关键组件;集合生态系统内的其他组件,例如 pushGateway、Altermanager 和 Grafana 等,可构成一个完整的 IT 监控系统。

时序数据,是在一段时间内通过重复测量而获得的观察值的集合;将这些观测值绘制于图形之上,有一个数据轴和时间轴。我们服务器的指标数据、应用程序的性能检测数据、网络数据等也都是时序数据。

Prometheus 支持通过三种类型的途径从目标上抓取(scrape)指标数据;管理告警,主要是负责实现报警功能。在 Prometheus Server 中支持基于 PromQL 创建告警规则,如果满足 PromQL 定义的规则,则会产生一条告警,而告警的后续处理流程则由 AlertManager 进行管理。

pull 和 push

Prometheus 同其他时序数据库相比有一个非常典型的特性:它是主动从各 TARGET(客户端)上拉取(pull)数据,而非等待监控端的推送(push)

pull 的优势在于,集中控制有利于配置集在 Prometheus server 上完成,包括指标和采集的速率。

Prometheus 的目标是收集在 TARGET 上预先完成聚合的聚合性数据,而非事件性驱动的存储系统。

(服务的发起方,拉模型)

Prometheus 的生态组件

Prometheus Server

Prometheus 组件中的核心部分,收集和存储时间序列数据,提供 PromQL 查询语言的支持。内置的 Express Browser UI,通过这个 UI 可以直接通过 PromQL 实现数据的查询以及可视化。

Exporters

将监控数据采集的端点通过 HTTP 服务的形式暴露给 Prometheus Server,Prometheus Server 通过访问该 Exporter 提供的 Endpoint 端点,即可以获取到需要采集的监控数据。

PushGateway

主要是实现接收由 Client push 过来的指标数据,在指定的时间间隔,由主程序来抓取。由于 Prometheus 数据采集基于 Pull 模型进行设计,因此在网络环境的配置上必须要让 Prometheus Server 能够直接与 Exporter 进行通信。当这种网络需求无法直接满足时,就可以利用 PushGateway 来进行中转。可以通过 PushGateway 将内部网络的监控数据主动 Push 到 Gateway 当中。而 Prometheus Server 则可以采用同样 Pull 的方式从 PushGateway 中获取到监控数据。

Alertmanager

管理告警,主要是负责实现报警功能。在 Prometheus Server 中支持基于 PromQL 创建告警规则,如果满足 PromQL 定义的规则,则会产生一条告警,而告警的后续处理流程则由 AlertManager 进行管理。在 AlertManager 中我们可以与邮件,Slack 等等内置的通知方式进行集成,也可以通过 Webhook 自定义告警处理方式。AlertManager 即 Prometheus 体系中的告警处理中心。

Prometheus 整个工作流程大概是这样的:

  • 通过 Service discovery 知道要抓取什么指标
  • 抓取指标数据存入 TSDB
  • 客户通过 HTTP server 使用 PromQL 查询结果

Exporters 相当于 targets 的代理层,例如 mysql 本身不能提供监控服务, prometheus 通过 exporters 来 pull 到想要的 metrics

Prometheus 数据模型

  • Prometheus 仅用于以“键值”(key)形式储存时序式的聚合数,不支持存储文本信息。
  • 其中的“键”称为指标,它通常意味着 cpu 速率、内存使用率、负载等;同一指标可能会适配到多个目标,比如 cpu 使用率这个指标,我们需要对 100 台服务器设备进行使用。所以它使用“标签”(labels)作为元数据,从而为指标添加更多的信息描述;
  • 这些标签可以作为过滤器进行指标过滤及运算。

指标类型 (metric type)

  • counter
  • gauge
  • summary
  • histogram

作业(job)和实例(Instance)

  • Instance

实例可以简单的理解为就是一个 target,网络客户端,实际上在多核心的服务器上,一个 instance 就代表一个 cpu 核心;

node_cpu_seconds_total{cpu="0",instance="192.168.0.61:9100",job="prometheus",mode="idle"}    12880841.65
node_cpu_seconds_total{cpu="1",instance="192.168.0.61:9100",job="prometheus",mode="idle"}    12808726.3
node_cpu_seconds_total{cpu="2",instance="192.168.0.61:9100",job="prometheus",mode="idle"}   12857636.16
node_cpu_seconds_total{cpu="3",instance="192.168.0.61:9100",job="prometheus",mode="idle"}   12781748.83
  • job

通常,具有类似功能的 Instance 的集合称为一个 job。例如一个 nginx 集群中所有的 nginx 进程。

Alerts

抓取到异常值后,Prometheus 支持通过报警(alert)机制向用户发送反馈,以便用户能够及时采取应对措施。

Prometheus server 仅负责生成报警指示,具体的报警行为由另一个独立的应用程序 AlertManager 负责。

  • 报警指示由 Prometheus server 基于用户提供的“报警规则”周期性计算生成;
  • AlertManager 接收到 Prometheus server 发来的报警指示后,基于用户定义的报警路由(route)向接收人(receivers)发送报警信息;

源码仓库

https://github.com/prometheus/prometheus

prometheus 源码分析

各个目录内容

  • cmd 目录是 prometheus 的入口和 promtool 规则校验工具的源码
  • discovery 是 prometheus 的服务发现模块,主要是 scrape targets,其中包含 consul, zk, azure, file,aws, dns, gce 等目录实现了不同的服务发现逻辑,可以看到静态文件也作为了一种服务发现的方式,毕竟静态文件也是动态发现服务的一种特殊形式
  • config 用来解析 yaml 配置文件,其下的 testdata 目录中有非常丰富的各个配置项的用法和测试
  • model 定义的 object
  • notifier 负责通知管理,规则触发告警后,由这里通知服务发现的告警服务,之下只有一个文件,不需要特别关注
  • pkg 是内部的依赖

- relabel:根据配置文件中的 relabel 对指标的 label 重置处理

- pool:字节池

- timestamp:时间戳

- rulefmt:rule 格式的验证

- runtime:获取运行时信息在程序启动时打印

  • prompb 定义了三种协议,用来处理远程读写的远程存储协议,处理 tsdb 数据的 rpc 通信协议,被前两种协议使用的 types 协议,例如使用 es 做远程读写,需要远程端实现远程存储协议(grpc),远程端获取到的数据格式来自于 types 中,就是这么个关系
  • promql 处理查询用的 promql 语句的解析
  • rules 负责告警规则的加载、计算和告警信息通知
  • scrape 是核心的根据服务发现的 targets 获取指标存储的模块
  • storge 处理存储,其中 fanout 是存储的门面,remote 是远程存储,本地存储用的下面一个文件夹
  • tsdb 时序数据库,用作本地存储

整体架构

main 函数解析命令行参数,并读取配置文件信息(由 --config.file 参数提供)。Prometheus 特别区分了命令行参数配置(flag-based configuration)和文件配置(file-based configuration)。前者用于简单的设置,并且不支持热更新,修改需要启停 Prometheus Server 一次;后者支持热更新。

main 函数会完成初始化、启动所有的组件。这些组件包括:Termination Handler、Service Discovery Manager、Web Handler 等。各组件是独立的 Go Routine 在运行,之间又通过各种方式相互协调,包括使用 Synchronization Channel、引用对象 Reference、传递 Context

源码启动

go mod tidy // 配置 go.mod

直接启动会报错,因为没有 prometheus.yml 配置文件,初始配置文件默认放在 documentation/remote_storage 目录下,将其拷贝至根目录下。默认监控的是 prometheus 本身,可以通过配置文件自定义监控。

内置控制台

npm install
npm start
# 启动受监控应用
# 启动 prometheus 服务

prometheus 2.14.0 版本后,UI 界面渲染通过 React app 方式来生成,go build 直接编译生成的二进制文件缺少 react app 相关的依赖包。可以通过 http://localhost:3000 转发代理的方式访问 http://localhost:9090 打开 promethues 原生 UI 界面。

prometheus 服务发现组件

服务发现简介

云原生、容器场景下按需的资源使用方式对于监控系统而言就意味着没有了一个固定的监控目标,所有的监控对象(基础设施、应用、服务)都在动态的变化,这对基于 Push 模式传统监控软件带来挑战。

对于 Prometheus 这一类基于 Pull 模式的监控系统,显然也无法继续使用的 static_configs 的方式静态的定义监控目标。而对于 Prometheus 而言其解决方案就是引入一个中间的代理人(服务注册中心),这个代理人掌握着当前所有监控目标的访问信息,Prometheus 只需要向这个代理人询问有哪些监控目标即可, 这种模式被称为服务发现

通过服务发现的方式,管理员可以在不重启 Prometheus 服务的情况下动态的发现需要监控的 Target 实例信息。Prometheus 每个被控目标暴露一个 endpoint 供 server 抓取,要获知这些 endpoint 有多种方式,最简单的是在配置文件里静态配置,还有基于 k8s、consul、dns 等多种方式,基于文件的服务发现是比较灵活普遍的一种方式。

web1.0 和 2.0 数据请求模型架构

后期,随着我们的用户数渐渐变多,单台服务器的压力扛不住的时候,我们就要用到负载均衡技术,增加多台服务器来抗压,后端的数据库也可以用主从的方式来增加并发量(web2.0),模型如下图所示:

那么啥时候才需要用到服务注册和发现呢?答案是分布式微服务时代

微服务时代的服务管理

在微服务时代,我们所有的服务都被劲量拆分成最小的粒度,原先所有的服务都在混在 1 个 server 里,现在就被按照功能或者对象拆分成 N 个服务模块,这样做的好处是深度解耦,1 个模块只负责自己的事情就好,能够实现快速的迭代更新。坏处就是服务的管理和控制变得异常的复杂和繁琐,人工维护难度变大。还有排查问题和性能变差(服务调用时的网络开销)

各个微服务相互独立,每个微服务,由多台机器或者单机器不同的实例组成,各个微服务之间错综复杂的相互关联调用。

比如上面的图中,我们将原先 1 个 server 的服务进行了拆分,拆出了 User 服务,Order 服务,Goods 服务,Search 服务等等。

在不用服务注册时,我们只能手动更改配置

服务注册

每一个服务对应的机器或者实例在启动运行的时候,都去向名字服务集群注册自己,比如图中,User 服务有 6 个 docker 实例,那么每个 docker 实例,启动后,都去把自己的信息注册到名字服务模块上去

//给 User 服务申请 1 个独有的专属名字
UserNameServer = NameServer->apply('User');
//User 服务下的 6 台 docker 实例启动后,都去注册自己
UserServer1 = {ip: 192.178.1.1, port: 3445}
UserNameServer->register(UserServer1);

服务发现

把每个服务的机器实例注册到了名字服务器上之后,接下来,我们如何去发现我们需要调用的服务的信息呢?这就是服务发现了。

//服务发现,获取 User 服务的列表
list = NameServer->getAllServer('User'); 
//list 的内容
[
    {
        "ip": "192.178.1.1",
        "port": 3445
    },
    {
        "ip": "192.178.1.2",
        "port": 3445
    },
    ......
    {
        "ip": "192.178.1.6",
        "port": 3445
    }
]

目前市面上已经有了服务注册和服务发现的解决方案,代表作是:zookeeper consul 以及 etcd,他们功能强大,安全稳定,高并发高可用,强一致性,目前市面上都是用这几个来实现自己的服务注册和发现的。

其中,consul 是后起之秀,源于它安装简单,功能强大,提供健康检查,web 管理后台,支持多数据中心,暴露了方便的 HTTP 接口,使得它被更多的人所使用,唯一不足的是它不支持 sub/pub 订阅机制,所以服务发现,得使用者自己去 HTTP 轮训发现变更。

consul 服务发现原理

1、部署集群。首先需要有一个正常的 Consul 集群,有 Server,有 Leader。这里在服务器 Server1、Server2、Server3 上分别部署了 Consul Server。

2、选举 Leader 节点。假设他们选举了 Server2 上的 Consul Server 节点为 Leader。这些服务器上最好只部署 Consul 程序,以尽量维护 Consul Server 的稳定。

3、注册服务。然后在服务器 Server4 和 Server5 上通过 Consul Client 分别注册 Service A、B、C,这里每个 Service 分别部署在了两个服务器上,这样可以避免 Service 的单点问题。服务注册到 Consul 可以通过 HTTP API(8500 端口)的方式,也可以通过 Consul 配置文件的方式。

4、Consul client 转发注册消息。Consul Client 可以认为是无状态的,它将注册信息通过 RPC 转发到 Consul Server,服务信息保存在 Server 的各个节点中,并且通过 Raft 实现了强一致性。

5、服务发起通信请求。最后在服务器 Server6 中 Program D 需要访问 Service B,这时候 Program D 首先访问本机 Consul Client 提供的 HTTP API,本机 Client 会将请求转发到 Consul Server。

6、Consul Server 查询到 Service B 当前的信息返回,最终 Program D 拿到了 Service B 的所有部署的 IP 和端口,然后就可以选择 Service B 的其中一个部署并向其发起请求了。

//服务注册
NameServer->register(newServer); 


//服务发现
NameServer->getAllServer();

prometheus scrape 组件

scrape 组件只包含三个文件 manager.go、scrape.go、target.go,提供了单实例采集百万指标的能力

创建 Manager

  • main.go 调用 Manager constructor NewManager, 传入的 app storage.Appendable 具体是 fanoutAppender
  • main 调用 Manager 的 Run,传入的 tsets <-chan map[string][]*targetgroup.Group 管道用于与 discovery 组件同步 targets
    • 循环 targetSets,也就是 updateTsets 更新后的 updateTsets。获取每一个 targetSet 的 setName 和 groups,从 manager 的 scrapeConfigs 取出对应 setName 的 scrapeConfig
    • 调用 newScrapePool 为每一个 setName 创建一个 scrapePool,并放到 manager 的 scrapePools 队列中
    • 调用每个 setName 的 scrapePool 的 Sync,Sync 传入了每个 setName 对应的 groups,由于 Sync 执行会有点耗时,这里用了 WaitGroup
    • Run 异步调用 reloader,reloader 周期性(设置 ticket)触发 reload 来执行采集任务的更新。由于要操作 manager 的 scrapeConfigs 和 scrapePools,这里要加 manager 的全局锁 mtxScrape
    • 基于 channel 的阻塞机制,Run 接收管道 tsets 消息,调用 updateTsets 循环更新 manager 的 targetSets

scrapePool sync

  • 循环 targets 取出每一个 tg,一个 tgs 对应一个 scrapeJob
    • 循环传入的 targets 取出每一个 target 来对存量的 target 进行更新
    • 首先判断 activeTargets 有无该 target,如果没有说明是新增的,给该 target 新建一个 scrapeLoop,这里构建了 targetScraper 含有了 htpclient。并将 target 赋值给 activeTargets,scrapeLoop 赋值给 scrapePool 的 loops,和 uniqueLoops。
    • 如果 activeTargets 有说明是已有的,若不在 uniqueLoops 中,将 uniqueLoops 中的该 target 赋值 nil
    • 遍历全局的 activeTargets,判断是否在新增的 uniqueLoops 中,如果没有说明是已废弃的,并从 sp.loops 和 sp.activeTargets 删除
    • 根据新增的 uniqueLoops,启动每一个新增的 target 的 scrapeLoop
    • tg 的 Targets 是 a list of targets identified by a label set,Labels 是 is a set of labels that is common across all targets in the group,循环 tg 的 targets,并结合公共的 tg.Labels 构建每一个 target 的 let
    • 调用 populateLabels 为每一个 target 的 lset 添加端口等信息,并执行 relabel 操作,返回 relabel 前后的 label 来 NewTarget ,labels 的 Target 是 relabel 后的 label,discoveredLabels 是 relabel 前调 label。
    • 最后返回这组 tg 对应的 targets
    • 调用 targetsFromGroup 传入 tg 和该 scrapePool 的 config,build targets
    • 根据每个 target 的 labels 和 discoveredLabels 来确认 target 是否需要加入 droppedTargets 列表中,并得到非 drop 的 target。这里需要对 scrapePool 的 activeTarget 和 droppedTargets 操作,所以需要加 targetMtx 锁
    • 最后调用 sync 来 deduplicates a list of potentially duplicated targets,starts scrape loops for new targets, and stops scrape loops for disappeared targets。

scrapeLoop run

  • 启动定时器,周期性调用 scrapeAndReport 抓取指标,这里传入了采集周期、超时时间、上次&本次采集时间戳
  • sl.scraper.scrape 开始采集数据,具体是 targetScraper 的 scrape
  • 调用 sl.append(app, b, contentType, appendTime) 将采集的数据 append,并返回 total, added, seriesAdded 用于 report target 状态
    • 首先在 scrapeLoop 的 cache 中看该 Series 在不在,如果在直接获得 ref 等数据,直接调用 app.Append(ref, lset, t, v) 来 apppend 数据。如果不在,则 ref 为空,待 app.Append(ref, lset, t, v) 会返回 ref。并将该 ref 信息添加到 cache 中。需要注意 ref 是在 tsdb 中唯一生成的全局 id
  • 调用 sl.report(app, appendTime, time.Since(start), total, added, seriesAdded, scrapeErr) 来 report target 状态,很多信息是由前面返回的。

mysql exporter 组件

官方仓库地址: https://github.com/prometheus/mysqld_exporter

mysqld_exporter 主要由三个部分组成:

  1. 配置处理:读取配置文件并解析参数,如 MySQL 数据库连接信息;
  2. 指标收集:通过 MySQL 数据库查询指标信息;
  3. 指标导出:将收集到的指标以 Prometheus 格式导出。

Prometheus MySQL Exporter源码阅读与分析 - Jiajun的编程随想

收集器并发收集所有指标,每个具体指标都会实现 Scraper 这个接口:

// Scraper is minimal interface that let's you add new prometheus metrics to mysqld_exporter.
type Scraper interface {
	// Name of the Scraper. Should be unique.
	Name() string
	// Help describes the role of the Scraper.
	// Example: "Collect from SHOW ENGINE INNODB STATUS"
	Help() string
	// Version of MySQL from which scraper is available.
	Version() float64
	// Scrape collects data from database connection and sends it over channel as prometheus metric.
	Scrape(ctx context.Context, db *sql.DB, ch chan<- prometheus.Metric) error
}

你可能感兴趣的:(Go,1024程序员节,云原生,服务发现,golang,prometheus)