作者: Steven Thurgood、David Fergusonwith
编辑: Alex Hidalgo、Betsy Beyer
校验:张胜楠,臧万顺,樊帅宇,刁冰雪
监控涉及到多种类型的数据,包括监控指标,纯文本日志,结构化日志,分布式跟踪日志, event introspection。 以上各种数据都有它们各自的用处,但是本章主要讨论监控指标和结构化日志。根据我们的经验,这两种数据最适合SRE的基础监控需求。
从根本上讲,监控系统应当能够透视系统的内部,当需要判断服务的健康状态和诊断服务问题时,这是最关键的需求。在第一版SRE的第6章中给出了一些基本的监控方法,并且提到SRE监控他们系统的主要目的有:
当达到阈值时触发报警
诊断和分析服务问题
展示系统的可视化信息
获取系统资源使用情况或服务健康状况的变化趋势,以便做长期计划
比较变更前后的系统变化或一个实验的两组样本的不同
这些用例的不同重要程度能指导你在选择或构建一个监控系统时做出权衡。
本章讨论Google如何管理监控系统,并提供一些如何选择和运行监控系统的指导意见。
监控策略的特征
在选择监控系统时,理解那些你关心的功能并对它们进行一个优先级的排序很重要。如果你正在评估一个监控系统,本节提到的这些特性可以帮助你思考哪种方案最适合你。如果你已经有一个在运行的监控系统了,你可以考虑使用现有解决方案的一些别的特性。根据你的需求,一个监控系统可能就能解决你所有的问题,也可能你需要组合好几个监控系统。
速度
不同的组织对于数据的时效性和获取数据的速度有不同的要求。
你需要用到数据的时候,数据就应该立马可用。数据的时效性影响的是当系统出错后多长时间监控系统才会通知你。另外,不及时的数据可能会导致因为错误的数据而采取不合适的行动。举个例子,关于事故响应,事故的发生时间和监控系统能够反映出结果的时间间隔如果太长,你可能会认为某一个改动没有产生什么不好的影响,或者你认为这个修改和某一个结果之间没有联系。超过4到5分钟才能获取的数据将会显著地影响你快速响应。
如果你是基于速度在选择监控系统,那你需要首先确定对速度的需求。当你查询大量数据时,数据的获取速度通常都是个问题。如果需要汇总多个监控系统的数据,图形的加载需要费一些时间。监控系统如果从输入数据生成新的时间序列,则可以对一些通用查询进行预先计算,从而加速图形的加载。
计算
很多应用场景都要求支持各种复杂计算。至少,你会希望能从监控系统获取几个月时间跨度的数据。没有长期数据的视图,你很难判断像系统增长性这样的长期趋势。关于粒度,汇总数据对于制作增长计划很有效。详细地存储每一个指标会有助于回答这样的问题:这样的异常行为以前发生过吗?但是,数据可能会耗费昂贵的存储空间,或者难以检索。
理想的事件或资源消耗的指标是递增的计数器。利用计数器,监控系统可以基于时间做计算——比如,报告每秒的访问量。如果在更长的时间范围做计算,你就可以实现implement the building blocks for SLO burn-based alerting (see Chapter 5).
最后,支持种类更齐全的统计函数很有用,因为细微的操作可能会掩盖不好的行为。当记录访问延迟时,算数平均数只能告诉你访问比较慢,而支持百分比计算的监控系统能让你一眼就看出是50%,5%还是1%的访问太慢。如果你目前的监控系统不支持直接的百分比计算,你可以采取下面的替代方案:
通过把每一个访问的时间相加然后除以访问数量,从而计算出平均访问时间
每一个请求写入日志,然后对日志记录进行扫描或取样,从而计算出百分比
你也可以把原始的监控指标写到另一个离线分析系统中,用于生成周报或月报,或者执行一些难以在监控系统中直接进行的复杂的计算。
交互界面
一个健壮的监控系统应该允许你简洁地在图形(graph)中显示时间序列数据,同时也能将数据结构化到表或各种类型的图表(chart)中。Dashborad是显示监控信息的主要的界面,所以选择显示格式很重要,要能最清晰地显示你所关心的数据。可以选择热力图、直方图和对数刻度图。
你可能需要为同一数据提供不同的视图给不同的用户。高级管理人员希望看到的信息跟SRE的大不相同。针对使用者专门创建对他们来说有意义的dashborad。每一组dashborad,对同一类型的数据显示要保持一致,这样方便沟通。
你可能需要实时图形化显示某一指标的不同聚合结果,比如分别根据机器类型、服务器版本或请求类型进行聚合。It’s a good idea for your team to be comfortable with performing ad hoc drill-downs on your data。根据不同的指标对你的数据进行切片,在需要的时候你可以查找数据之间的联系和模式。
警报
报警分级很有必要,不同的级别采取不同的响应机制。 给报警设置不同的级别很有用,你可能会开一个工单用于跟踪一个低级别故障,调查可能会持续了一个多小时;100%的错误则属于紧急情况,需要立即响应。
报警屏蔽功能能避免不必要的分散值班工程师精力的干扰。比如:
当所有的服务器都出现同样的高故障率时,可以只针对全局的高故障率报警一次,不需要为每个服务器单独发送报警。
当你的服务所依赖的服务报警时,不需要为你的服务发出故障报警。不要忘记,一旦故障事件处理完了,记得恢复被屏蔽的故障报警。
你还需要确保在事件结束后不再屏蔽警报。
监控系统的可控控制程度决定了你应该使用第三方的监控服务,还是部署和运行自己的监控系统。Google内部开发了自己的监控系统,但是外面有很多开源或商业的监控系统可供选择。
监控数据的来源
选择何种监控系统也受将要使用的监控数据源的影响。这节讨论两种常用的数据源:日志和指标。还有些其它的有用的监控数据源这里没有讨论的,比如分布式跟踪和运行时introspection.
指标是属性和事件的数字度量,通常间隔一段固定的时间产生多个数据点。日志是只能追加的事件记录。本章的讨论主要集中在结构化日志,相比纯文本日志结构化日志更易于复杂查询和聚合工具。
Google基于日志的监控系统处理大量细粒度(highly granular)数据。事件发生到log可见之间不可避免的有些延迟。日志分析并不要求即时性,可以先经过一个批处理系统的处理,然后运行一些特定的查询,并在控制面板展示。比如,可以先使用Cloud Dataflow处理日志,BigQuery做查询,Data Studio做控制面板。
而我们的基于指标的监控系统,从Google的各种服务处收集了大量的指标,能几乎实时地提供粗粒度的信息。基本上其它基于日志或指标的监控系统有着和Google的系统类似的特性,当然也有例外,比如也有实时日志处理系统,或者细粒度的指标等。
我们的报警和控制面板通常使用指标数据。指标监控系统的实时性能让工程师快速地发现问题。我们倾向于使用日志监控系统查找问题发生的根本原因,指标常常无法提供这方面的信息。
报表不要求实时性,我们经常用日志处理系统来生成详细的报表,因为日志几乎总是比指标产生更准确的数据。
如果你的报警是基于指标的,可能会临时增加一些基于日志的报警,比如你需要在某个异常事件发生时收到报警。就算你有这样的需求我们仍然推荐基于指标的报警系统,其实你可以在某个特殊事件发生时将计数器指标加一,然后创建一个基于这个指标的报警。这样做的好处是把所有报警配置都放在一个地方,便于管理(详见“管理你的监控系统”)。
案例
接下来这些真实的例子解释了如何选择不同的监控系统。
将信息从日志移到指标
问题。对于App Engine的用户来说,HTTP状态码对于错误诊断来说非常重要,它存在于日志而不是指标中。指标控制面板只能显示一个总的错误率,无法包含错误相关的其它信息,如错误码或者错误发生的原因。因此,诊断一个问题需要这样做:
检查总体错误率的图表,找到错误发生的时间
读取日志文件,查找包含错误信息的日志记录
尝试在错误日志和错误图表之间建立联系
日志记录无法体现出“数量”,因此很难从日志判断某一个错误是否频繁发生。日志还包含很多其它不相关的信息,让查找错误的根源变得很难。
建议的解决方案。 App Engine开发组决定将HTTP状态码输出成一个指标(比如,requests_total{status=404} 或者 requests_total{status=500})。由于不同HTTP状态码的数量是有限的,这样做不会导致指标数据的数据量大小增长到一个不可接受的水平,但是这些数据可以用于图表和报警。
结果。新的指标允许App Engine的开发组升级监控图表,分别显示不同的错误类型。用户则可以基于错误码快速判断可能发生的问题。同时,我们还能为客户端和服务端错误分别设置不同的报警阈值,让报警更加准确。
优化日志和指标
问题。Ads SRE组维护这近50个服务,由不同的语言和框架开发。SRE组将日志作为检验SLO(SLO compiance)的权威数据源。为了统计错误情况,为每个服务编写了专门的日志处理脚本。这里有一个日志处理脚本的例子:
If the HTTP status code was in the range (500, 599)
AND the 'SERVER ERROR' field of the log is populated
AND DEBUG cookie was not set as part of the request
AND the url did not contain '/reports'
AND the 'exception' field did not contain 'com.google.ads.PasswordException'
THEN increment the error counter by 1
脚本很难维护,并且用了一些指标监控系统没有的数据。因为报警由指标驱动,有时候报警可能跟用户无关的错误引发的。每一个报警都需要一个显式分类的步骤来判断它是否跟用户相关,这拖慢了响应速度。
建议的解决方案。 SRE组开发了一个库,植入到业务处理系统中,如果判断出某一个错误会影响用户的请求,将判断的结果写入日志并且导出一个指标数据,提高日志和指标的一致性。如果指标显示某个服务返回了错误,日志包含那个错误以及一些请求相关的数据,帮助重现和诊断错误。日志中任何SLO相关的错误同时也会改变SLI指标,SRE组可以据此创建报警。
结果。通过创建一个跨服务的统一控制接口,运维组重用了工具和报警逻辑,避免为不同的服务重复开发运维系统。去除了复杂的、服务特定的日志处理逻辑,所有的服务都从中受益,获得了扩展性的提升。一旦报警跟SLO直接绑定就变得清晰可执行了,因此错误报警显著减少
为数据源保留日志
问题。 在诊断生产环境中的问题时,运维组经常会查看受影响的实体ID,判断对用户的影响和问题的根源。在App Engine早期的时候,这样的调查工作需要用到只有日志中才有的数据。处理每个事故,运维组不得不执行一些一次性的查询,这就增加了事故恢复的时间——需要几分钟来编写正确的查询,然后花些时间查询日志。
建议的解决方案。 起初,运维组讨论了是否应该用指标来代替日志。然而,实体ID可能有几百万种不同的值,要把它们做成指标不太可行。最终,运维组决定编写一个脚本来执行那些一次性的查询,并在报警邮件中说明需要运行哪个脚本。在需要的时候,运维人员可以直接把脚本拷贝到命令行执行。
结果。运维组不再需要花太多精力管理一次性查询,获取结果的速度也更快了(尽管还没到指标查询那么快)。他们还有一个备用方案:报警发生时自动运行脚本,用一个小服务器定时查询日志,能获得半实时(semi-fresh)的数据。
管理你的监控系统
你的监控系统与你运行的任何其他服务一样重要。 因此,应该给予适当的关注。
将配置视为代码
将系统配置作为代码处理并存储在版本控制系统中是常见的做法,好处也很明显:更改历史记录,从特定更改到任务跟踪系统的链接,更简单的回滚和格式检查,以及强制执行的代码审查过程。
我们强烈建议你将监控配置视为代码(有关配置的更多信息,请参阅第14章)。 支持文本缩进配置的监控系统比仅提供Web UI或CRUD样式API的系统更好。对于很多开源程序,这是标准的配置方法,它们只从配置文件读取信息。 一些第三方配置解决方案(如grafanalib)为传统UI配置增加了文本配置的方式。
鼓励一致性
大型公司会有多个工程团队使用监控系统,他们需要寻求一种良好的平衡:集中式的监控系统保证了一致性,但另一方面,各个团队可能希望自己能完全决定配置的设计。
正确的解决方案取决于你的组织。 谷歌的方法随着时间的推移逐渐发展为基于单一框架的集中式监控服务。 说这套解决方案适用于我们有几个原因。 单一的框架使工程师在换团队时能够更容易上手,并使调试过程中的协作变得更加容易。 我们还提供集中式仪表盘服务,每个团队的仪表盘对于其它团队都是可发现和可访问的。 如果你很容易了解其他团队的仪表盘,则可以更快地调试你们的问题。
如果可能,尽量让基本监控覆盖很容易做。 如果你的所有服务都导出一组一致的基本指标,则可以在整个组织中自动收集这些指标,并提供一组一致的仪表盘。 这也意味着任何新组件都自动具有基本监视功能。公司的许多团队 - 甚至是非工程团队 - 都可以使用这些监控数据。
倾向于松耦合
随着业务需求的变化,一年后您的生产系统看起来会有所不同。 同样,你的监控系统需要随着时间的推移而发展,因为它监控的服务会出现不同的故障模式。
我们建议保持监控系统的组件松耦合。 你应该有稳定的接口来配置每个组件和传递监控数据。 但负责收集、存储、警告和可视化的组件应该相互独立。 稳定的接口使得更换任何给定组件更容易,如果你有更好的替代组件的话。
将功能拆分为单个组件在开源世界中变得越来越流行。 十年前,像Zabbix这样的监控系统将所有功能集中到一个组件中。现在的设计通常将收集和规则评估(使用Prometheus服务器之类的解决方案)、长期时间序列存储(InfluxDB)、警报聚合(Alertmanager)和仪表盘(Grafana)等拆分开来。
在本书撰写时,至少有两种流行用于检测软件和输出指标的开放标准:
statsd
度量聚合守护进程最初由Etsy编写,现在移植到大多数编程语言中。
Prometheus
一种开源监控解决方案,具有灵活的数据模型,支持指标标签和强大的直方图功能。 Prometheus正在被标准化为OpenMetrics,其他系统现在也采用Prometheus格式。
独立的仪表盘系统可以使用多个数据源的数据,对服务状态进行集中的统一的展示。 谷歌最近在实践中看到了这一好处:我们的旧监控系统(Borgmon3)将仪表盘与报警规则放在同一配置中。在迁移到新系统(Monarch)时,我们决定将仪表盘移动到单独的服务(Viceroy)中。 由于Viceroy不是Borgmon或Monarch的组成部分,因此Monarch的功能要求较少。 由于用户可以使用Viceroy显示来自两个监控系统的数据,因此他们可以逐渐从Borgmon迁移到Monarch。
有目的的指标
第5章介绍了在系统的错误数阈值快接近时如何使用SLI指标进行监控和报警。 SLI指标是您在基于SLO的警报触发时要检查的第一个指标。 SLI指标应显示在服务仪表盘的显眼位置,最好位于其首页上。
在调查SLO报警触发的原因时,你很可能无法从SLO仪表盘获得足够的信息。 这些仪表盘告诉你SLO被触发了,但不会告诉你为什么。 监控仪表盘还应显示其它哪些数据?
我们发现了一些指定指标的指导意见。 指标应提供合理的监控,便于调查生产环境的问题,同时提供有服务相关的很多信息。
预期的变化
在诊断SLO报警时,你需要能够从告知你有影响用户的问题发生的报警指标切换到能让你知道问题根源的指标。 最近对服务进行的预期更改可能是错误的。添加一些能监视生产环境中的任何改动的指标。对于监控触发条件,我们有如下的建议:
监控二进制文件的版本。
监控命令行参数,尤其是在使用这些参数开启和禁用某些服务功能时。
如果配置数据动态推送到你的服务,请监视此动态配置的版本。
如果系统中的任何部分未进行版本控制,你可以监控上次编译或打包的时间戳。
当你尝试将服务中断与上线关联起来时,查看与报警链接的图表/仪表盘要比查看CI / CD(持续集成/持续交付)系统日志更容易。
服务依赖
即使你的服务没有更改,其任何依赖项都可能会更改或出现问题,因此你还应该监视来自直接依赖项的响应。
将每一个服务依赖项返回的字节数、延迟和响应代码都输出。 在选择图标展示指标时,请牢记四个黄金信号。你可以给指标额外定义标签,通过响应代码,RPC(远程过程调用)方法名称和服务依赖项的名称对指标进行进一步细分。
理想情况下,你可以检测较低级别的RPC客户端库以导出这些度量标准,而不是要求每个RPC客户端库导出它们.检测客户端库提供更高的一致性,并允许你免费监控新的依赖关系。
你有时会遇到提供非常狭窄的API的依赖项,其中所有功能都可通过名为Get,Query或同样无用的单个RPC获得,并且实际命令被指定为此RPC的参数。 客户端库中的单个检测点与此类依赖关系不符:你将观察到延迟的高度变化和一些百分比的错误,这些错误可能会或可能不会表明此不透明API的某些部分完全失败。 如果这种依赖关系很重要,那么你有几个选项可以很好地监控它:
导出单独的度量标准以定制依赖关系,以便度量标准可以解压缩它们收到的请求以获取实际信号。
要求依赖项所有者执行重写以导出更广泛的API,该API支持跨单独的RPC服务和方法拆分的单独功能。
资源容量
目的是监视和跟踪服务所依赖的每种资源的使用情况。 某些资源具有你不能超过的硬限制,例如分配给你的应用程序的RAM,磁盘或CPU配额。 其他资源(如打开文件描述符,任何线程池中的活动线程,队列中的等待时间或写入的日志量)可能没有明确的硬限制,但仍需要管理。
根据使用的编程语言不同,你还应该监视些其他资源:
对于Java:堆和元空间大小,以及根据垃圾收集类型不同而使用一些特殊指标
对于Go:协程的数量
语言本身为跟踪这些资源提供了不同的支持。 除了如第5章所述警告重大事件之外,你可能还需要设置当某些特定资源接近耗尽时触发的报警,例如:
当资源有硬性的上限时
当资源使用超过阈值会导致性能下降时
你应该对所有的资源都设置监控 - 即使是服务本身能很好地管理的资源。 这些指标对于容量和资源规划至关重要。
服务流量状况
最好能增加跟流量相关的指标或指标标签,以便仪表盘按状态代码显示详细的流量情况(除非你的服务用于SLI目的的指标已包含此信息)。以下是一些建议:
对于HTTP流量,监视所有响应代码,即使它们没有达到报警的级别,因为某些响应代码可能由不正确的客户端行为触发。
如果你对用户设定了频率速率限制或流量限制,监控系统应当统计有多少用户的请求因为流量限制被拒绝了。
流量监控图表可以帮助你确定一个线上的改动在何时导致了显著的错误数量的变化。
指定有目的指标
每个输出的指标都应该有用它的目的性。 不要仅仅因为某些指标很容易产生就轻易地将它们输出。 相反,应该想想输出的这些指标会被如何使用。Metric design, or lack thereof, has implications。
理想情况下,用于报警的指标只在系统出问题时发生显著的变化,而在系统正常运行时不会变化。 但是,调试用的指标没有这些要求 - 它们旨在告诉我们当报警触发时系统内部发生了什么。 好的调试指标能指示系统中可能导致问题的地方。 编写事后调查时,想一下还有其它哪些指标能帮助你更快地诊断问题。
测试报警逻辑
在理想情况下,监控和报警的代码应遵循与系统开发代码相同的测试标准。虽然Prometheus的开发人员正在讨论开发用于监控的单元测试,但目前广泛采用的监控系统还没有单元测试的支持。
在Google,我们使用领域特定的语言测试我们的监控和警报,该语言允许我们创建合成时间序列(synthetic time series)。 然后,我们根据派生时间序列中的值,或特定报警的触发状态以及标签是否存在来编写测试断言。
监控和报警通常是一个多阶段过程,因此需要多个单元测试集。 这个领域仍然有待发展,但如果你想在某个时候实施监控测试,我们建议采用三层方法,如图4-1所示。
二元报告:检查输出的标准的值是否在预期的某些条件下发生变化。
监控配置: 确保规则评估产生了预期的结果,并且特定条件会产生预期报警。
警报配置: 测试生成的报警是否按照报警的标签值路由到了预定目的地。
如果你无法综合测试监控系统,或者你的监控的某一个阶段无法进行测试,可以考虑创建一个运行系统来输出一些公认的指标,例如请求数和错误数。 你可以据此来验证时间序列和报警。 你的报警规则很可能在设置之后的几个月或几年内都不会触发,你需要确信当指标超过某个阈值时,有意义的报警通知会发给正确的工程师。
结论
由于SRE角色负责生产系统的可靠性,SRE通常需要非常熟悉服务的监控系统及其功能。如果没有这方面的知识,SRE可能不知道在哪里查看监控,如何识别异常行为,或者如何在紧急情况下找到所需的信息。
我们希望通过指出我们认为有用的监控系统功能及其为什么我们认为它们有用,可以帮助你评估监控策略和需求有多匹配,探索你可能能够利用的一些其他功能,并考虑你可能想要做出的改变。也许你会发现将一些指标和日志监控结合起来的策略很有用;具体如何结合取决于应用场景。注意收集的指标要有其目的性,可能是为了更好地进行容量规划,辅助调试或直接通知你发生了什么问题。
一旦你有了监控系统,需要确保它可见且可用。 为此,我们还建议你测试你的监控设置。良好的监控系统能带来好的回报。好好想想什么样的监控系统最适合你的需求,不断地摸索直到找到最好的那个,这是一笔很值得的投资。