[转]Uber:大规模系统下如何构建可伸缩的告警生态系统
Uber 的软件架构包含上千个微服务,它们能够让团队快速迭代并支撑公司的全球化增长。这些微服务支撑着各种解决方案,比如移动应用、内部与基础设施服务、产品等,它们有着非常复杂的配置,这些配置会在城市和子城市级别对产品的行为产生影响。
为了维持我们的增长和架构,Uber 的可观察性(Observability)团队构建了一个健壮的、可伸缩的指标和告警管道,当服务出现问题时,它负责探测、缓解和通知相关的工程师。具体来讲,我们构建了两个数据中心内的告警系统,称为 uMonitor 和 Neris,它们会流入相同的通知和告警管道。uMonitor 是我们基于度量指标的告警系统,它会基于度量数据库 M3 运行检查,而 Neris 主要查找主机级别的告警。
Neris 和 uMonitor 利用一个公共管道发送通知和去重。我们将深入研究这些系统,并讨论采取更多的缓解行动、名为 Origami 的新告警去重平台以及在创建高信噪比告警等方面所面临的挑战。
除此之外,我们还开发了一个黑盒告警系统,当我们的内部系统出现故障或数据中心完全不可用时,该系统可以从数据中心外部检测出高级别的中断。以后的博客文章将讨论这种设置。
1. Uber 的告警
图 1:在我们的告警架构中,服务会发送度量指标到 M3。uMonitor 会检查 M3,获取基于度量指标的告警。主机的检查会发送到 Neris 上,用于聚合和告警。在 Uber 外部,会采用黑盒的方式测试 API 基础设施。
在 Uber 这种规模的公司中,监控和告警不能按照传统的现成解决方案进行思考。Uber 的告警系统是从 Nagios 开始的,使用源控制脚本对 carbon 指标进行 Graphite 阈值检查。由于 Carbon metrics 集群的可伸缩性问题,我们决定构建自己的大规模度量平台 M3。为了提高告警系统的可用性,我们开发了 uMonitor,这是我们自行开发的基于时间序列的度量指标告警系统,它所针对的是存储在 M3 中的度量数据。对于没有存储在 M3 中的度量指标,我们构建了 Neris 来执行主机级别的告警检查。
uMonitor 在构建时考虑到了灵活性和使用场景的多样性。有些告警信息是基于标准的度量指标自动生成的,比如端点错误和 CPU/ 内存消耗。其他的告警由度量指标相关的各个团队来创建。我们将 uMonitor 构建成一个平台,满足各种不同的使用场景,具体来说:
- 简单的告警管理:对于每个告警,迭代确定适合的函数和阈值;
- 灵活的操作:各种通知机制,比如网络短信(paging)、Email 和聊天工具。支持自动缓解,比如回滚到上次部署以及配置修改;
- 处理高基数:能够针对最小范围内的严重问题发出告警,但是不会在出现更大范围故障时,对团队成员形成通知风暴。
2. uMonitor 实现基于度量指标的告警
uMonitor 有三个独立的组件:一个存储服务,它具有告警管理的 API,并包装了我们的 Cassandra 告警和状态存储;一个调度器,跟踪所有的告警,针对每个告警要求,每隔一分钟分发告警检查任务给 worker;worker,针对每个告警所定义的底层度量指标,执行告警检查。
worker 会在 Cassandra 存储中维护告警检查的状态,并通过一个主动重试机制确保通知至少能够成功发送一次。worker 会间隔一定的时间(通常是一个小时)对持续出现的告警重新发出警报。目前,uMonitor 拥有 12.5 万个告警配置,每秒检查 7 亿个数据点,超过 140 万个时间序列。
图 2:度量指标由服务发送给 M3,uMonitor 的检查由 worker 基于调度器来执行,如果违反了阈值的话,将会发送通知。
告警的定义包含一个 M3 查询( Graphite 或 M3QL)和阈值,用来判断告警是否违反了阈值。查询将会返回一个或多个时序,阈值将会应用到每个底层的时序上。如果查询违反了阈值的话,就会发送警报。Cassandra 会存储告警的状态,在它的帮助下,worker 会维护一个状态机,确保通知至少能够成功发送一次,如果告警持续触发的话,它会定期重新发送通知,如果问题得到了缓解,告警将会变更为已解决的状态。
阈值有两种类型:静态阈值和异常阈值。对于具有特定稳定状态的指标,或者是可以通过构建查询返回一致值(借助一定的值计算,比如成功 / 失败的百分比)的指标,我们通常会使用静态阈值。对于周期性的指标,如每个城市的出行次数和其他业务指标,uMonitor 会利用我们的异常检测平台 Argos 基于历史数据生成动态阈值,这个动态阈值代表了异常的度量指标值。
2-1. 使用 Neris 进行主机告警
Neris 是我们内部基于主机的告警系统,它针对的是高密度、高基数的主机度量指标,在 M3 中,这是并未实现的。主机度量指标并未存放到 M3 中有两个主要原因。首先,在每个数据中心的 40000 台主机上检查每分钟生成的 150 万个主机度量指标要比查询中心化的度量指标存储更高效。在前者的方案中,摄取和存储度量指标的开销就能避免了。其次,直到最近,M3 的数据保留策略会导致每 10 秒钟的指标要存储 48 个小时,每分钟的指标会存储 30 天,对于高密度的主机指标来说,这样的保留策略是没有必要的。鉴于 Nagios 需要为每项检查编写代码,并且需要单独部署,这样无法随着我们的基础设施增长进行扩展,所以我们决定自行构建一个系统。
Neris 会有一个代理(agent),运行在我们数据中心的每个主机上,它会定期(每分钟)针对主机本身执行告警检查。代理会将检查结果发送到一个聚合层,这个聚合层会将聚合后的结果发送给 Origami。Origami 会基于规则确定要将哪些告警发送出去,这个规则会查看故障告警的数量以及底层告警的紧急程度来进行判断。借助 Origami,Neris 能够在每个数据中心的主机群中每分钟运行大约 150 万次检查。
当代理在主机上启动时,Neris 会从一个中央配置存储中拉取关于该主机的告警定义信息,这个中央配置名为 Object Config,它广泛应用于 Uber 的低层级基础设施服务中。给定主机要运行哪些告警取决于它的角色。例如,运行 Cassandra 的主机应该检查 Cassandra 的状态、磁盘使用以及其他度量指标。大多数这种主机级别的检查是由基础设施平台团队创建和维护的。
图 3:借助 Neris,会对数据中心中的每个主机进行检查,并由 Neris 聚合器进行聚合,然后 Origami 会发送告警通知。
2-2. 处理基数
对于我们的告警系统来说,高基数一直是最大的挑战。在传统做法中,可以运行一个告警查询并返回多个序列,然后可以针对该告警运行简单的规则,如果违反阈值的序列超出了特定的百分比,就会发送该告警信息。uMonitor 还允许用户基于其他告警的结果而发送告警。假设一个告警所跟踪的范围依赖于一个范围更大的告警,如果范围更大的告警已经发出的话,依赖它的告警就会被抑制。
如果查询只返回数量有限的序列,那么上述技术能够运行地非常好,依赖也能很容易地进行定义。但是,Uber 随着增长,需要运维跨数百个城市的众多产品线,基数方面所面临的挑战需要一个更加通用的解决方案。我们使用 Origami 来帮助我们处理高基数方面的问题。Neris 使用 Origami 作为其主要的数据去重和通知引擎,它为 uMonito 告警实现了统一的通知。
对于业务指标,当我们希望为每个城市、每个产品、每个版本的应用提供告警时,Origami 就非常有用了。Origami 允许用户为城市、产品和应用程序版本组合创建底层的告警 / 检查,并根据聚合策略发出告警,以便于接收基于每个城市 / 产品 / 应用程序版本的通知。在出现更大规模的停机情况时 (例如,当许多城市同时出现问题时),Origami 将发送累积的通知,它代表了底层告警的触发列表。
在主机告警的场景中,Origami 能够根据聚合的不同状态发送不同严重程度的通知。以 Cassandra 集群的磁盘使用为例,在这个场景下,Origami 的通知策略可能会像如下所示:
- 如果三个以下的主机磁盘使用率为 70%,使用 Email 进行通知;
- 如果三个以上的机磁盘使用率为 70%,使用短信(Send A Page,通过网络发送文字通知 – 译注)进行通知;
- 如果有一个或更多的主机磁盘使用率达到了 90%,使用短信进行通知。
2-3. 告警通知
在扩展我们的告警系统时,有用的警报信息是最大的挑战。告警操作一般会从通知开始,比如针对高优先级的问题,为值班工程师发送短信,对于信息级别的问题,给他们发送邮件或进行线上聊天工具通知。我们现在的焦点工作已经转移到为这些问题构建缓解操作。大多数故障和中断都是由于配置更改或部署而引发的。在缓解操作方面,uMonitor 为回滚最近的配置和部署环境提供了良好的支持。对于具备更复杂缓解操作的团队来说,我们会支持 webhook 的方式,它会针对某个端点发起一个 POST 调用,在调用中会包含告警的完整上下文信息,在这个请求处理中可以运行缓解问题的操作。除此之外,通过使用 Origami 中的去重管道,我们可以在出现更大范围的停机状况时,抑制粒度更细的通知。
除了以上提到的功能,我们一直在努力使通知更加具有相关性,也就是让它们针对最合适的个人。最近的一项工作涉及到识别配置 / 部署的更改者,并在他们所修改的服务出现告警时,直接联系到这些修改人。通过将 Jaeger 的跟踪信息与告警信息相结合,我们能够在为出现故障的服务发送告警通知时获取更多的上下文信息。
2-4. 告警管理
如前文所述,我们一直致力于将 uMonitor 打造成为一个平台,让其他的团队都能根据特定的使用场景基于它来进行构建。主机告警的设置和管理通常是比较专业化的,由维护专属硬件的团队和为公司构建基础设施平台的团队来进行管理,包括存储、度量指标以及计算解决方案。告警是在团队的 Git 仓库中进行配置的,它们会同步到 Object Config 中。
如果从较高的级别来进行区分,uMonitor 有三种类型的告警:
- 针对所有的服务,根据 CPU、磁盘使用情况和 PRC 统计数据等标准度量指标自动生成的告警;
- 通过 UI 创建的一次性告警,主要用来探查特定的问题;
- 通过脚本或基于 uMonitor 的外部配置系统创建和管理的告警。
我们看到,增长最快的是最后一种告警,因为团队都致力于在最细的粒度探查可告警的问题。对这种粒度的需求来源于 Uber 的全球化增长。对于支撑 Uber 移动应用的服务来说,代码变更通常只涉及特定的一组城市,并且只会在几个小时内有效。所以,非常重要的一点在于,我们需要在城市级别监控平台的健康状况,从而能够在问题大范围扩散之前将其定位出来。除此之外,每个城市的配置参数都是不同的,这些配置是由工程团队和当地的运维团队所控制的。例如,游行或其他的事件会导致交通状况的变化,骑行者在街道上可能被拦阻。
很多团队都基于 uMonitor 构建了告警生成方案以解决此类问题。这些工具所解决的挑战包括:
- 跨不同维度,迭代生成告警;
- 根据特定的业务信息确定告警的时间安排,比如特定国家或城市的节假日,要将这些信息配置在 uMonitor 中,以避免出现虚假告警;
- 在静态或当前异常阈值都无法发挥作用的情况下,根据过去的数据或底层度量数据的复杂查询来确定阈值,这些阈值会用到特定的业务线上(关于告警查询,参见下文的介绍)。
除此之外,很多这样的方案都会生成仪表盘,它们会与生成告警保持同步。
uMonitor 还提供了 UI,用于编辑告警和展现根本原因。UI 化的编辑和实验性功能非常重要,因为指标具有变化性和峰值,所以大多数的指标不能原样作为告警进行发送。可观察团队提供了如何创建查询更适合作为告警的指南。
2-5. 告警查询
Graphite 查询语言和 M3QL 提供了大量的功能,以便于支持更加可定制化的方案。如下是一些样例,列出了如何让查询返回更加一致的值,从而能够让度量指标更加具备告警的能力:
- 告警要反映几分钟的移动平均值(movingAverage),以消除指标峰值的影响;
- 将上述操作与一定的持续时间结合起来,只有当在一定的时间内持续违反阈值的时候,才会发送通知;
- 对于具有上升和下降模式的指标,使用导数函数,确保两个方向的峰值都不会变化太剧烈;
- 针对百分比或比例进行告警,这样度量指标就不会受到量级变化影响。
2-6. 未来计划
在扩展系统时,我们刚刚使其具备检测分钟级问题的能力,并且能够做到只为用户显示恰当的信息,抑制掉不必要的告警。我们正在致力于将该功能应用于管道的各个组成部分中,包括更有效地收集度量指标、扩展性以及流程化告警执行的基础设施,并构建跨各种资源协调信息的 UI 和接口。