暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Prometheus系列5 - Alertmanager源码阅读

栋总侃技术 2021-08-29
3086

这一节将给大家带来 Alertmanager 的源码解析,同时借助于阅读 Alertmanager 的源码过程,也给大家讲讲我是怎样看一个开源项目的源码的。

功能概述

首先阅读一个项目的源码之前,需要对该项目大体的功能范围有一定的了解。而在开始阅读 Alertmanager 的源码之前,你至少需要了解以下几点:

  • Alertmanager是Prometheus的一个组件;

  • Alertmanager的输入是来自 Prometheus Server产生的告警;

  • 输入的告警会经过分组、抑制、静默、延时、去重等步骤的处理;

  • Alertmanager的输出是将告警发送出去(邮件、企业微信、WebHook等方式);

框架结构

在阅读源码前,如果有该项目的结构图,也可以先大概了解下整体的数据流走向,方便后续的代码阅读。在Alertmanager的源码 doc目录有一张 Alertmanager的框架图arch.svg,在该框架图我们可以大概了解下数据的走向。

  • 通过API组件接收告警,并存储至ALert Provider;

  • Dispatcher 从Provider 通过Subscribe 获取告警的数据;

  • Dispatcher 对告警的数据进行分组后,将每个告警存储至对应的Group中;

  • Pipeline处理每个分组中的告警,经过集群处理、抑制、静默、路由、等待、去重、发送等步骤。

配置文件

配置文件是能够理解一个项目的功能最直观的体现。有哪些功能,都在配置文件每个配置项中基本上都可以体现出来。但是对于复杂、配置项非常多的配置文件,也是需要学习成本的。在阅读Alertmanager源码之前如果已经对其配置文件比较熟悉了,那也会给阅读源码带来一定的帮助。我们可以看一个简单的Alertmanager配置样例:

    global:
    resolve_timeout: 5m
    smtp_smarthost: 'smtp.163.com:25'
      smtp_from: 'xxx@163.com'
      smtp_auth_username: 'xxx@163.com'
      smtp_auth_password: xxxxx
      smtp_hello: '163.com'
      smtp_require_tls: false
    receivers:
    - name: wechat
    webhook_configs:
    - send_resolved: true
        url: http://qiyeweixin.example.cn/alert
    - name: email
    email_configs::
      - send_resolved: true
    to: xxxx@163.com
    route:
    group_by: ['name']
    group_interval: 2m
    group_wait: 2m
    receiver: wechat
    repeat_interval: 5m
    routes:
    - match:
    severity: critical
    receiver: email
    inhibit_rules:
    - source_match:
    severity: Critical
    target_match:
    severity: Warning
    equal: ['class']

    以上配置中可以读取到以下信息:

    • 配置了两个告警的接收者,wechat、email;

    • 告警级别为严重级别(包含一组label serverity=critical)的告警将通过email发送,其他的告警通过wechat发送;

    • 所有的告警以labe的key为name分组,即name的值相等的告警会在一组告警中一起发送;

    • 相同类别(class属性相等)的严重级别的告警(serverity=critical)抑制一般级别的告警(serverity=warning)

    代码框架

    到这里,借助结构图,配置文件就对Alertmanager的功能有着大体的一个印象了,但是细节可能还不太清楚。接下来我们可以通过阅读代码来理解每个功能的实现,从而了解每个细节。

    在阅读代码前,不妨先看看整个代码的目录有哪些。而且一眼看去对一些目录(包)中是做什么逻辑处理的也有个大概的认识。

    代码脉络

    接下来第一遍阅读代码,可以对整体的代码脉络,也就是数据流向进行一个认识,在把每个脉络串起来后再去读每个功能源码的细节。接下来我们将从配置、API、provider、dispatch、分组、pipeline、抑制、延时、静默这些功能串起来。

    配置

    首先我会关注配置文件的解析,直接进入config目录找到配置文件的结构对应的struct:

    可以看到与配置文件中的yaml结构是完全对应的。

    API接口

    在examples\ha\send_alerts.sh 文件中可以看到是通过/api/v1/alerts接口接收告警的,同时可以知道告警的json结构是怎样的:

    我们去代码中找到这个接口的处理方法(api\v1\api.go)

    最终告警put至api.alerts。我们可以凭感觉知道api.alerts是个列表。我们根据定义可以知道api.alerts其实就是结构图中的provider。

    初识Provider

    provider是一个提供了以下方法的接口。

    在main.go中可以看到Alertmanager使用的是mem.Provider实现的这个接口。

    到这里我们不用去细看mem.Provider的实现细节,我们继续去跟踪数据流向。

    Dispatch

    在生成Dispatch对象的时候将上一步的mem.Provider作为参数。

    再回到结构图,可以猜测Run函数内肯定会调用Subscribe方法获取从API接口接收的告警来进行处理。在Dispatcher的 Run函数中确实也是这么做的。

    在run方法中不断地获取告警列表(provider)中的告警,进行匹配路由然后进行process。

    route

    在配置文件中,我们定义了两个路由,分别是严重告警和一般告警的发送对象不一样,我们看下route是如何match每个告警的。

    首先,route对象也是根据配置文件生成的,同时根据NewRoute逻辑和配置文件的结构可以了解到整个route是一个树结构,从根节点到子节点

    而Match操作则是从最里层,也就是叶子节点逐级匹配到根节点这样一个递归的过程。而且一条告警可能会有多个路由,返回的是route数组。

    分组

    匹配到告警的路由后会将该告警和对应的路由绑定在一起进行处理。

    而处理的第一步就是分组:

    首先会根据分组条件(配置文件中的name)计算不同name的value的指纹,也就是这个告警的指纹,如果存在该指纹的告警组,则直接将该告警放到该组中,若组不存在则先创建组。

    最终成了如下的数据结构,一个route上有很多的分组,一个分组中维护着该组的告警列表。

    分组会一定时间处理一次这个组内的所有告警:

    这个时间也就是配置文件中的group_interval配置,这表示具有相同分组条件的且在这个周期内的告警会在一个分组中处理。

    最终一个组内的告警在flush函数中会一起notify:

    而这个notify是在dispatch中传入的函数方法:

    而该函数方法中,调用的是stage的Exec。

    Pipeline

    通过pipeline的New方法可以跟踪到这个stage就是pipeline:

    而通过Pipeline的New方法可以知道其由静默、抑制、延时等步骤构成。

    我们直接去看pipeline的Exec方法:

    Exec中遍历所有的stage,进行每一步的Exec,且每一次执行后的alert列表是下一步的入参。最终得到的是经过每一步过滤后的告警列表。

    接下来我们来看每个stage是如何处理告警的,在pipeline的New方法中可以看到stage数组的顺序是:集群处理,抑制,去重,静默。

    在接收stage顺序是:等待集群、去重、发送重试、日志记录:

    GossipSettle

    等待集群的其他伙伴:

    抑制

    匹配到被抑制则会被过滤:

    那么抑制条件是如何匹配到的呢? 根据配置文件的 inhibit_rules 配置可以生成对应的规则,如果某条告警的label满足source条件则会被设置到cache里。

    而如果某条告警正好满足rule的target条件,那么该告警将会被抑制:

    被抑制的告警也会打上相应的属性标签:

    Silence

    通过界面可以设置静默条件,跟踪接口可以找到对应的handler函数:

    当插入静默规则后会生成对应的sid:

    而对于列表的告警根据指纹查找是否命中某个sid,如果命中,判断时间是否满足条件,如果满足则这条告警会被静默掉(不会被发出去)。

    被静默的告警也会被打上标签:

    延迟

    获取配置的延迟条件,且判断该报警路由是否匹配到了延迟条件,如果匹配到,则在延时时间内一直会跳转到loop,从而继续循环判断下去:

    这个功能的实现,大家考虑下是否可以进行优化,进行sleep或者其他等待,而不是不停的循环一直占用者cpu时间片。

    wait

    可以看到wait是在集群模式才会使用到,这段时间内等待其他的集群相应:

    Dedup

    可以看到在dedup阶段的Exec中对告警取hash,如果存在该hash值的告警则会被过滤掉,起到去重的作用。

    Retry

    在retry阶段会调用不同的receiver去真正的发送告警,而如果失败则会重试:

    重试策略如下:

    Receiver

    根据不同的配置会生成不同的receiver对象,这些对象都继承了Notify接口,我们看看其webhook方式的receiver是如何实现的:

    在webhook的Notify方法中发起http请求发送到配置的url地址。

    以上是Prometheus产生的告警从进入Alertmanager到流出Alertmanage的数据流向以及对应的处理逻辑。在后续的文章中将会带来每一步处理的详细分析,以及如何结合实际的使用进行二次开发。

    本系列回顾:

    Prometheus系列4 - 高可用集群thanos

    Prometheus系列3 - AlertManager专场

    prometheus系列2 - Exporter专场

    prometheus(一)快速入门

    文章转载自栋总侃技术,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

    评论