# Prometheus基础拆解


我们都知道，Prometheus可用于监控，但这是一个比较模糊的描述，我们需要对Prometheus 进行更具体的拆解，所以本文会介绍 Promethes 是什么以及它的部署架构。话不多说，让我们正式开始吧。

# 什么是Prometheus
Prometheus官网对其描述如下：
> 使用领先的开源监控解决方案监控您的应用程序、系统和服务。检测、收集、存储和查询您的指标，用于告警、仪表板和其他用例。
所以**Prometheus不仅是组件应用，更是一套成熟的开源监控解决方案**。

通常，一个完整的应用监控体系通常包含以下三个层面：
1. ‌指标监控（Metrics）‌：采集数值型数据（如吞吐量、JVM 内存），用于趋势分析和阈值告警。
2. ‌链路追踪（Tracing）‌：记录请求在微服务间的完整调用路径，精准定位分布式架构中的性能瓶颈。
‌3. 日志监控（Logging）‌：收集文本型事件日志，用于异常细节排查和上下文还原

而Prometheus聚焦于监控体系的指标监控层面，将指标作为时间序列数据收集并存储，即指标信息与记录的时间戳一起存储，同时也包含了称为标签的可选键值对，用于形成对指标数据的多维管理。

## 主要特征
Prometheus 主要特征如下：
- 一个多维数据模型，其时间序列数据由指标名称和键/值对标识，多维是通过键值对标签实现；
- PromQL，利用多维特征进行灵活查询的语言；
- 不依赖分布式存储，单个服务器节点是自主的，即每个Prometheus实例不能共享存储；
- 基于HTTP的pull模型收集时间序列数据；
- 支持通过中间网关推送时间序列数据，这通常适用于短job，即还未等到Prometheus拉取，对应目标就可能已经结束运行；
- 可通过服务发现或静态配置发现目标，在k8s中，还可以通过Pod标签匹配等方式自动发现集群中部署的资源；
- 图形和仪表板支持多种模式

Prometheus专为云原生世界而设计，与Kubernetes和其他云和容器管理器集成，以不断发现和监控您的服务。这是继Kubernetes之后从CNCF毕业的第二个项目。

## 组件
Prometheus 生态系统由多个组件组成，其中许多是可选的：
- main prometheus server：负责抓取和存储时间序列数据；
- client libraries：用于检测应用代码的客户端库，这些客户端库实现了Prometheus的指标类型，选择与编写应用程序的语言相匹配的Prometheus客户端库，可以让您通过应用程序实例上的HTTP端点定义和公开内部指标；
- push gateay：支持短期工作的推送网关；
- 专门用于HAProxy、StatsD、Graphite等服务的专用导出器；
- alertmanager：处理告警。

大多数Prometheus组件都是用Go书写的，使它们易于作为静态二进制文件构建和部署。

## 架构
下图来自于官网，说明了普罗米修斯的架构及其一些生态系统组件：
![Alt text](/images/202606/1.png "架构")

整个Prometheus架构可以分为以下四部分：
1. 目标（拉模型）：指标数据的来源，可以是server通过服务发现的k8s服务，也可以是在配置中指定的静态目标；
2. Prometheus Server：拉取指标，并存储在本地的时序数据库中，也可以对这些数据运行规则，以从现有数据中聚合和记录新的时间序列，或生成警报。同时通过HTTP服务对数据可视化组件暴露这些指标数据；
3. alertmanager：用于接收 server推送的告警，并转发至不同邮件等用户接口通道；
4. 数据可视化：通过PromQL从Server暴露的HTTP服务中查询指标数据，进行可视化。

## 概念
Prometheus基本上将所有数据存储为时间序列：属于同一指标和同一组标记维度的带时间戳的值流。除了存储的时间序列外，Prometheus还可以根据查询结果生成临时导出的时间序列。

1. 指标名称和标签
每个时间序列都由其指标名称和称为标签的可选键值对作为唯一标识。

2. job和instance
在Prometheus术语中，可以抓取的端点称为instance，通常对应于单个进程。具有相同目的的实例集合，例如为了可扩展性或可靠性而复制的进程，称为job。

## 配置
Prometheus 的配置文件是 prometheus.yml 文件，文件的主要内容如下：
```yaml
global:
  scrape_interval:     15s
  evaluation_interval: 15s

rule_files:
  # - "first.rules"
  # - "second.rules"

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ['localhost:9090']
```
示例配置文件中有三个配置块：global、rule_files和scrape_configs。

global块控制Prometheus服务器的全局配置。第一个scrape_interval，控制拉取目标的频率。也可以为单个目标覆盖此配置。在这种情况下，全局设置是每15秒拉取一次。evaluation_interval选项控制Prometheus评估规则的频率，规则可用来来创建新的时间序列并生成警报。

也可在 gloabl 中配置label，用于针对所有指标生成全局的标签，这在部署多个Prometheus实例收集相同目标时尤为有用，可以在每个Prometheus实例上配置不同的全局label，以便告警或可视化组件可以区分指标的收集者。

rule_files块指定了我们希望Prometheus服务器加载的任何规则的位置。目前，我们没有规则。

最后一个块scrape_configs控制Prometheus监控的资源。由于Prometheus还将有关自身的数据作为HTTP端点公开，因此它可以抓取和监控自己的运行状况。在默认配置中，有一个名为prometheus的单一作业，它抓取Prometheus服务器公开的时间序列数据。该作业包含单个静态配置的目标，即端口9090上的localhost，且在/metrics路径上的目标上提供指标。因此，此默认作业是通过URL进行抓取的：http://localhost:9090/metrics。

# Prometheus部署
部署一般考虑以下两点：
1. 因单点故障带来的可用性问题，可参考下述高可用章节解决；
2. 因数据增长带来的容量问题，可参考下述扩展章节解决；

## 高可用
可以在两台或多台单独的机器上运行相同的Prometheus服务器，每台服务器都完整的采集数据，也可以将数据写入远程的外部系统，由外部系统对数据进行重复数据消除。

而相同的告警可以由 alertmanager 进行重复数据消除。Alertmanager的高可用需要通过互连多个Alertmanager实例来构建一个Alertmanager集群，alertmanager通过 gossip 协议实现分布式系统的协调控制，所以alertmanager部署实例数量只能是大于或等于3的单数，以便进行投票达成状态一致。

## 扩展
federation（联邦）允许Prometheus服务器从另一个Prometheus服务器抓取选定的时间序列，这是Prometheus本身所支持的。
通常，这是用于实现可扩展的基础监控设置，可将相关指标从一个服务的Prometheus 拉到另一个服务。

假设在多个数据中心的情况下，可能希望有一些时间序列的全局聚合。这可以通过一个“全局Prometheus”服务器完成的，该服务器与数据中心Prometheus服务器联合。配置如下：
```yaml
- scrape_config:
  - job_name: dc_prometheus
    honor_labels: true
    metrics_path: /federate
    params:
      match[]:
        - '{__name__=~"^job:.*"}'   # Request all job-level time series
    static_configs:
      - targets:
        - dc1-prometheus:9090
        - dc2-prometheus:9090
```
dc1、dc2的Prometheus分别负责各自数据中心的指标拉取，而这个全局Prometheus可以抓取两个数据中心Prometheus的时间序列，并进行全局聚合。

### 通过使用切分
随着系统增长，可能通过一个prometheus进行全局指标监控是不够的，所以可以在每个数据中心运行多个Prometheus服务器。

每个Prometheus都将拥有对某个系统的部分应用进行监控，所以一个Prometheus不再监控所有目标。

通常应该尽可能在出现性能问题之前开始按使用进行拆分，这也可以进行更有效的隔离。

### 水平分片
当无法再细分Prometheus服务器时，扩展的最后一步是向外扩展。这通常要求单个job具有数千个实例，这是大多数用户从未达到的规模。

这是一个更复杂的设置，与普通的Prometheus部署相比，管理起来要复杂得多，因此应该尽可能避免。

该架构将有多个分片的Prometheis（Prometheus的复数，也可以称为Prometheus instances），每个Prometheis抓取目标的一个子集，并在分片内聚合它们。leader将分片产生的聚合进行联合，然后将它们聚合到job级别。

在分片上，你可以使用地址的哈希值来只选择一些要抓取的目标：
```yaml
global:
  external_labels:
    shard: 1  # This is the 2nd shard. This prevents clashes between shards.
scrape_configs:
  - job_name: some_job
    # Add usual service discovery here, such as static_configs
    relabel_configs:
    - source_labels: [__address__]
      modulus:       4    # 4 shards
      target_label:  __tmp_hash
      action:        hashmod
    - source_labels: [__tmp_hash]
      regex:         ^1$  # This is the 2nd shard
      action:        keep
```

leader 会从这些分片中联合起来：
```yaml
- scrape_config:
  - job_name: shards
    honor_labels: true
    metrics_path: /federate
    params:
      match[]:
        - '{__name__=~"^shard:.*"}'   # Request all shard-level time series
    static_configs:
      - targets:
        - shard0:9090
        - shard1:9090
        - shard3:9090
        - shard4:9090
```

所以在这种场景下，仪表盘的信息通常来自leader。如果你想深入到一个特定的目标，可以通过它的分片来实现。

# 参考
1. [Prometheus官网](https://prometheus.io)
2. [Scaling and Federating Prometheus](https://www.robustperception.io/scaling-and-federating-prometheus/)
