惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
O
OpenAI News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 三生石上(FineUI控件)
Webroot Blog
Webroot Blog
GbyAI
GbyAI
S
SegmentFault 最新的问题
Cyberwarzone
Cyberwarzone
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
J
Java Code Geeks
Google DeepMind News
Google DeepMind News
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
博客园 - 【当耐特】
S
Secure Thoughts
酷 壳 – CoolShell
酷 壳 – CoolShell
AWS News Blog
AWS News Blog
Engineering at Meta
Engineering at Meta
S
Security Affairs
H
Help Net Security
Microsoft Security Blog
Microsoft Security Blog
D
DataBreaches.Net
云风的 BLOG
云风的 BLOG
Hugging Face - Blog
Hugging Face - Blog
Google DeepMind News
Google DeepMind News
Spread Privacy
Spread Privacy
T
Threatpost
Forbes - Security
Forbes - Security
C
Cisco Blogs
Scott Helme
Scott Helme
Attack and Defense Labs
Attack and Defense Labs
Simon Willison's Weblog
Simon Willison's Weblog
腾讯CDC
The Last Watchdog
The Last Watchdog
Cloudbric
Cloudbric
Last Week in AI
Last Week in AI
Recorded Future
Recorded Future
小众软件
小众软件
V
Vulnerabilities – Threatpost
美团技术团队
人人都是产品经理
人人都是产品经理
有赞技术团队
有赞技术团队
Apple Machine Learning Research
Apple Machine Learning Research
Hacker News - Newest:
Hacker News - Newest: "LLM"
I
Intezer
月光博客
月光博客
C
Cyber Attacks, Cyber Crime and Cyber Security
博客园 - 司徒正美
C
Cybersecurity and Infrastructure Security Agency CISA
Martin Fowler
Martin Fowler
博客园 - 聂微东

卡瓦邦噶!

服务器高性能网络调优 | 卡瓦邦噶! 为何写作 | 卡瓦邦噶! 读《金阁寺》 | 卡瓦邦噶! 雨季又来 | 卡瓦邦噶! MTU Probe 引起的初始延迟 | 卡瓦邦噶! 3.5 秒的固定延迟问题 | 卡瓦邦噶! 学习网络的一点经验 | 卡瓦邦噶! ARP 问题诊断 | 卡瓦邦噶! 网络断断续续…… | 卡瓦邦噶! Piccolo P2P 镜像分发 | 卡瓦邦噶! 一起看电影 | 卡瓦邦噶! 《征服C指针》 | 卡瓦邦噶! 我的姥姥 | 卡瓦邦噶! Python的哲学 Python 3.5的新特性 学校不教的计算机课 垃圾回收(GC)的三种基本方式 在编程中体验纯粹的快乐 从《美丽新世界》谈自由 在快钱实习 迷人的嗓音和迷人的故事——《Sleepyhead》 Python 的十个自然语言处理工具 记一个愚蠢的bug 一年炉石传说的游戏体验 《以撒的结合:重生》网页版图鉴 分清 C++的指针、引用和数组 笑话三则 自由比皇帝更伟大——《悲惨世界》笔记 Git 10 周年访谈:Linus 讲述背后故事 用 0x3f3f3f3f 设定最大int值的优点 Joel给计算机系学生的建议 一个词法分析器的简单实现 怎样才算健康的生活方式 MacVim 配置攻略 学习培训课程的视频效果好吗? 语言的控制 可悲的大多数 CSS样式思维导图 2014年终总结 奥巴马成为首位写程序的美国总统 青岛老城区的下水道好在哪里? 做优秀 UI 的七个建议(第二部分) 选择爱情的骑士 Git简明教程 Java集合总览 读 《1984》 java问答:终极父类(六)——等待/唤醒和接口 Java 问答:终极父类(五)——toString() Hyperlapse快速视频背后的技术细节 平庸之恶 Java程序员须知的七个日志管理工具 苏州 逛书摊随想 关于考试作弊 你的工作不仅仅是编程 推荐在线学习Java的英文资源 关注女性命运——《千禧年三部曲》 用好你的幻灯片——《演说之禅》 使用ReentrantLock和Lambda表达式让同步更纯净 新手学编程,从哪里开始? 程序员都是工程师吗? 不要学习代码,要学会思考 Java 问答:终极父类(四)——hashCode() Java 问答:终极父类(三)——finalize()和 getClass() 程序员职业之路的选择 我是一名摄影家 写给何小树的城市指南 为什么一些语言会比别的快? Java的常见误区与细节 跟朋友在一起玩游戏 死神永生——读《三体》 五种类型的程序员 创业圣经——读《黑客与画家》 纪念加西亚·马尔克斯 Junit中处理异常的另一种方式:catch-exception Java8采用Martin Fowler的方法创建内部DSL Linux HotSopt虚拟机GC线程的CPU占用率 J2EE概念介绍 如何成为一名黑客 Java 问答:终极父类(二)——equals()方法 为什么我喜欢Java Java 问答:终极父类(一)——clone()方法 七个改变世界的Java项目 java中默认类型转换的小问题 传统与创新 欢迎来到互联网 莫言和马尔克斯——读《生死疲劳》 位运算的妙用 读《人为什么活着》 写博客教会我的事情 中国特色操作系统 2013年总结 《永不妥协》影评 恨不相逢未嫁时——《廊桥遗梦》影评 如何优雅地使用PPT 给明年依然年轻的我们 天才与柱子 黑客守则和黑客精神 Looking for Freedom——《被解救的姜戈》 简洁之道
Alert 自动诊断系统的设计
laixintao · 2023-07-27 · via 卡瓦邦噶!

在运维系统中,我认为 Alert 光发出来是不够的。每一个 Alert 发出来都需要处理,都要被解决。

我们现在设计的监控系统是这样的:

  1. Vmalert 是 alert rule evaluation 的组件,简单来说,它就是不断查询 TSDB 数据库中的数据,如果发现满足了 alert rule 的条件,就发送请求给 alertmanager;
  2. Alertmanager 会把 Alert 路由到正确的接受者,如果同一个 alert 被多个实例触发,它还会将他们聚合在一个 alert 中,只发送一条。此外,还有 Mute,高级 alert 抑制低级 alert,判断 alert 是否恢复的条件,等等功能。
  3. Alert reaction 是我写的一个系统,补充了开源监控系统中缺失的一部分:alert 的处理。这个系统在一次 PromCon 上分享过。Alertmanager 所有的 alert 会同时发送给 Alert reaction 一份,这个系统对于 Alertmanager 来说,就是一个普通的接受者。收到 alert 之后它会在自己的数据库中记录下来。

Alert reaction 系统能够提供的功能有:

  1. 对于所有触发过的 alert 进行统计,触发频率,应急事件,解决事件,等等,这些统计信息可以帮助我们优化 alert 触发的条件,让 alerting 效率更高;
  2. 保证所有的 alert 都有人在处理,设计了一个签到机制,alert 必须有人 ACK,如果没有 ACK 的话,过一段时间会 page 其他人;
  3. 有协同处理的功能,记录处理流程。比如可以在 Alert reaction 上面标记 alert,一键创建工单,评论等,所有时间会根据原先 alert 的路由策略发送更新消息。比如,Alertmanager 将 alert 发送到了 3 个群组,那么后续对于这个 alert 做出的更新会继续通知到这三个群组;
其中某一个 alert 的详情页面

最近发现这样还是不够的。很多 alert 需要人工处理,或者需要更加复杂的判断条件,仅仅用 metrics 无法表达出这样的 alert rule。比如,当发生 A 的时候,需要去检查 B,C,如果 B 和 C 都没有问题,则认为 A 有问题,如果其中一者有问题,我们认为不是 A 的问题。

即,我们需要用代码去定制 Alert rule,或者说需要用代码去处理发生的 alert。最近就写了这么一个框架。

框架的原理很简单:当 Alert Reaction 系统从 Alertmanager 收到 alert 的时候,将 alert 放到一个任务队列中,队列我选择的是用基于 Redis 做 broker 的 rq

任务队列本质上有两个:

  1. 第一个叫做 polyclinic (这在新加坡叫做综合医院,或者家庭门诊,人们需要看病的话首先要去这里咨询全科医生),polyclinic 会收到所有的 alert,然后匹配已注册的诊断程序(医生,doctor),如果匹配的话,将产生一个新的任务队列,放入 doctor queue
  2. Doctor queue 是真正执行诊断程序的 queue。没有什么逻辑,只是运行用户定义的函数
alert 的处理流程

对于用户来说,如果想要使用脚本来处理 alert 的话,只需要定义两个事情:

  1. 我的脚本能够处理哪一些 alert
  2. 如何处理(即代码逻辑)

我设计的目标是让用户需要写的代码足够少,上线速度足够快。因为这个编码的场景是需要不断调试,优化,来处理各种各样 alert 的场景。

我设计了一个装饰器,用户使用一个装饰器,只需要写一个函数,就可以了:

Python

1

2

3

4

@diagnose({"alertname": "disk_full", job="nginx"}, timeout="30s")

def disk_clean(alert, alert_id):

    # do something with other systems

    post_comment(alert_id, "I am groot!")

其中 diagnose 装饰器第一个参数就是如何 match alert,是和 PromQL 一样的 match 逻辑,不过只支持 equals match,不支持正则,not 等,因为用户函数的入参是携带了 alert 的原文的,所以如果要做更加复杂的 match 的话,可以在自己的代码中实现,如果不 match 在代码返回即可。post_comment 是框架所带的 SDK 的功能,可以回复评论到 Alert Reaction 系统上。

这个简单的实现带来了很多问题。

第一个问题是如何加载用户的代码。按照 Python 的 import 机制,如果这脚本文件从来没有被 import 过,那么不会被加载回 Python 的解释器的。一种方法是在 __init__ 里全 import 一遍,但是这样,每次添加新的诊断脚本,都需要记住要修改 import,否则,如果忘了的话,就比较难 debug 为什么我的脚本没有运行;另一种方法,就是我现在用的,worker 在启动的时候将用户脚本的目录全部扫描 import 一遍。感觉也不太优雅,也没有找到其他好的方法。

第二个问题是如何区分不同的诊断程序。比如各种 bot 都去给 alert 评论,怎么知道哪一条评论是谁发的。为了减少用户设置一个字段,这里直接用 function name 了,这个 decorator 的实现如下:

Python

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

def diagnose(match_alert_labels=None, enable=True, timeout="60s"):

    """

    Args:

        match_alert_labels: dict, {"label": "value"}, currnetly only support

          equals match, `{}` will match all alerts.

        enable: default to True. this field is used to keep the code but

          disable the function.

        timeout: int or string, timeout specifies the maximum runtime of the

          job before it’s interrupted and marked as failed. Its default unit

          is seconds and it can be an integer or a string representing an

          integer(e.g. 2, '2'). Furthermore, it can be a string with specify

          unit including hour, minute, second (e.g. '1h', '3m', '5s').

          see: https://python-rq.org/docs/jobs/

    """

    matchers = match_alert_labels

    def real_decorator(func):

        def real_func(*args, **kwargs):

            threadlocal.current_doctor_name = func.__name__

            logger.info("Now doctor %s take a look...", threadlocal.current_doctor_name)

            return func(*args, **kwargs)

        is_debug = os.getenv("DIAGNOSE_DEBUG") == "1"

        # for debugging, there is no job queue, so we need to run the `real_func`

        # but for debugging, there is job queue, and worker's function is actually replaced with `real_func`

        if is_debug:

            doctor = Doctor(matchers, real_func, timeout)

        else:

            doctor = Doctor(matchers, func, timeout)

        if enable:

            polyclinic.take_office(doctor)

        return real_func

    return real_decorator

我是选择存储在 threadlocal 里面,然后用到的时候,比如在 post_comment 里,从 threadlocal 拿出来就可以了。有几个好处:

  1. 用户在调用 post_comment 的时候,不需要用户传递任何参数,在 post_comment 内部拿出来 threadlocal 就可以知道是谁想要发出评论;
  2. 在多线程环境下,比如多个 worker 同时执行,每一个都可以拿到正确的名字;

从上面的代码中可以看到还有一段 debug 相关的逻辑,这个很有意思,不是一个很容易理解的问题。一开始,我要让用户使用这个框架很方便地 debug,提供了几个功能:

  1. 可以针对构造的 alert 进行诊断测试,自己生成 alert 存在本地,然后一条命令就可以测试自己的程序;
  2. 可以直接针对线上的 alert 进行测试,直接一条命令就可以了

但是有一个问题,通过上面的架构描述,可以看到这个 alert 要被诊断,经过了很多系统,还有 job queue,用户本地测试可以跳过这些步骤,直接运行诊断程序就行。

那么为什么需要这段代码呢?

Python

1

2

3

4

if is_debug:

    doctor = Doctor(matchers, real_func, timeout)

else:

    doctor = Doctor(matchers, func, timeout)

这里的区别就是我们放入 queue 的函数是被装饰的函数 real_func 还是原始函数 func. debug 环境没有经过 job queue,生产环境经过了 job queue。

如果有 job queue 在的话,中间存在一个序列化的过程。我们将任务放入 queue,实际上是在 Redis 里面存储了这个字符串。如果是被装饰的函数,rq 会将函数序列话成如下:

1

decorator.diagnose.<locals>.real_decorator.<locals>.real_func

当 worker 拿到的话,反序列化出来是出错的。因为 worker 跑的时候装饰器已经运行完了,已经没有装饰器的上下文了。

如果 enqueue 的是原始函数,那么 worker 拿出来,其实运行的还是被装饰之后的函数,因为 worker 运行的时候,装饰器已经跑完了,所以原始函数实际上是运行完装饰器之后的函数了。(有点绕,注意这段代码在跑的时候,是装饰器的初始化阶段)

Debug 的时候没有 job queue,所以也就不存在序列化和反序列化,我们如果 enqueue 的是 func,那么实际跑的就是 func,因为这个场景没有 worker,没有人把装饰器都 evaluate 一遍再去跑实际的代码。如果是 func 的话,没有注入 threadlocal 的代码,后面获取 threadlocal 的时候就会报错。

除了这一个地方比较绕,其他的都比较直观。其他人写起来也比较好上手,关键是本地测试非常方便。

后记,我之前在蚂蚁金服的时候,也参与过类似的一个项目。回忆起来让人感慨万分。

这个项目有很多团队参与:老板设想的是所有 alert 1 分钟内发现,自动定位,自动恢复。分了三个组去完成这个目标。我们要对接监控系统发出来的消息,对接业务部门(我们的用户),对接上的成本难以想象的大,API 全没文档,数据格式乱七八糟,有一个叫 sunfire 的系统返回的监控数据是一个 json,其中有一个字段的 value 是 string,但是是一个 json 形式的 string,decode 之后里面还有一个字段是 json 形式的 string,我称之为 json in json in json,如果不反序列化,看到的都是 \\\\\\"\\\\"\\" 之类嵌套 N 层的转译符号,含义全靠猜。我们项目要想做出成果,一个关键就是搞大,所以就拉了很多人参与我们的项目,强制用户来这个诊断平台(那时候还叫中台)写规则。但是平台设计的糟糕,用户不会用,触发链条很长,测试成本很高,测试的方法就是在丁丁上喊其他组的人触发一下,效率很低。

为此搞了个闭关项目,还有誓师大会,做成了运动式的项目。实际上,第一个组胡乱配告警,配了上千条,平时没事也在一直发,每次故障总是能找出来几条 alert,可以说我们的发现率是 100% 了。做定位的,因为告警配置的垃圾所以我的工作相对也好做,绩效也不错。就是自愈比较难做。

前几天前同事戏称蚂蚁的项目是“年包”项目,指项目基本上只做一年。我那个时候做的项目一年之后也确实没了。