“我们该如何为关键的 Tier 1 API 端点配置 10,000 条自定义服务级目标(SLO)告警?我又该如何为排名前 100 的应用创建仪表板?”这是我们经常从工程师和应用负责人那里听到的问题。这些问题之所以会出现,是因为他们仍在沿用 10 到 20 年前的那种监控思维方式:针对少量关键系统,手工制作仪表板并配置自定义告警。但世界已经变了。在当今持续扩张、动态变化的混合云基础设施中,新的应用、服务和功能每天都在被开发、部署和发布,这种做法已经无法扩展。
在本章中,你将了解到,为什么面对这些问题时,我们——本书作者——会这样回应:“我理解这个需求从何而来!你负责确保数字服务的健康性、可用性和韧性,因为它们对业务至关重要。随着你的架构随着时间不断演进,你的监控方式也必须同步演进。让我们向你展示一种更简单、可扩展的方法,通过利用云原生可观测性与 AI,来满足你的需求!”
本章建立在前两章的基础之上:我们已经讨论了可观测性如何从监控演进而来,以及 AI 的最新进展如何帮助我们更好地管理和理解爆炸式增长的可观测性数据。我们将进一步探讨,这两项技术结合起来,如何改善 IT 运维、SRE、应用负责人以及业务负责人在确保其所负责的数字服务按预期运行时所面临的日常挑战。
本章将涵盖以下内容:
- 当“玻璃上的数据”和静态告警失效时
- 可观测性驱动开发
- 上下文为王:可观测性数据的质量
- AIOps:通过异常检测与根因检测降低噪声
- 从运维走向业务:基于 SLO 的影响分析
当“玻璃上的数据”和静态告警失效时
“我们以前有 1,000 个仪表板。现在已经减少到 50 个了!”这往往是一场成功的可观测性转型故事的结尾。此前,大量数据被堆放在许多仪表板上,但却无法转化为可执行的行动。如果通过更聚焦的数据可视化与分析方法,也能达成同样的目标,那么我们就在从“只是把数据展示在屏幕上”,走向“让数据可视化真正可执行”。但这一切的开端,通常并不是这样。
“我们的监控工具能不能配置 10,000 个自定义阈值?”这是我们不止一次听到的问题。我们的回答是:“可以!但你为什么会有这个需求?它从哪里来?”
静态阈值在静态基础设施时代非常有效。当时如果只有三台数据库生产服务器、五个应用以及两台 Web 服务器,那么为 CPU、内存、垃圾回收(GC)、工作线程和连接池利用率设置静态阈值是合理的。IT 运维人员非常了解这些机器。他们可能还会给这些机器起一些容易记忆的名字,例如 sql-prod-1、app-frontend-4 或 ws-lb-2。部署在这些服务器上的应用,其流量是可预测且稳定的。因此,设置静态阈值是一种很好的方式,用来在某台服务器没有达到其应有健康状态时发出告警。
下面这个仪表板很好地体现了传统基础设施可观测性的形态:通过静态阈值突出显示各个独立组件是健康(绿色)、不健康(红色),还是正处于走向不健康的路径上(橙色)。
图 3.1 —— 在我们云端弹性化的世界里,健康指标的静态可视化方法无法扩展!(来源:community.grafana.com/t/status-pa…
快进到 2026 年:我们不再只有三台数据库生产服务器,而是拥有多种不同的数据库云服务——有些用于存储记录,有些充当缓存,还有些只是数据湖。过去运行单体 Java 应用的应用服务器,也变成了分布在多个 Kubernetes 集群中的 100 到 500 个 Pod。运行中的 Pod 数量会随着事件驱动扩缩容和负载驱动扩缩容而变化,因此在任何一个时刻都很难准确说出具体数量。至于 Web 服务器?它们中的大多数已经被负载均衡服务、Ingress 控制器或 API 网关所替代。架构已经发生了巨大的变化!但是在监控方面,我们却常常看到,人们只是把旧时代的思维模式和方法原封不动地套用到现代架构之上。这正是“我们的监控工具能不能配置 10,000 个自定义阈值?”这一需求的来源。过去是少量命名整齐的服务器,现在却是成千上万个组件。如果你仍然想对每一次 CPU 尖峰或 GC 活动都收到告警,那么你就真的需要设置 10,000 个自定义阈值。但这既不现实,也没有必要。
基础设施指标上的静态告警替代方案
好奇的人可能会问:“为什么你说我们不再需要这些告警了?难道 CPU、内存或 GC 不需要保持健康吗?”
答案是:“需要,我们仍然依赖 CPU、内存、磁盘、网络等等!但是 Kubernetes 或云服务提供商这样的编排层,会根据当前所需负载,负责提供合适数量的 CPU、内存和磁盘。通过利用弹性基础设施和自动化编排来提供恰到好处的计算、内存和存储资源,以确保我们能够实现自己的 SLO,这样就大幅降低了对静态阈值的需求。”
你现在也许会想:“等等,这听起来像是非常糟糕的建议!我们很快就可能陷入一种局面:系统不断扩容,甚至无限扩容,却没有任何治理和成本控制!”你说得完全正确!因此,我们需要澄清:在哪些地方我们仍然需要静态阈值,哪些地方不需要,以及哪些地方我们有了新的告警或扩缩容方式。
静态阈值:适用场景
静态阈值在以下场景中仍然非常相关且高效:
- 边界清晰的关键系统约束,例如 API 速率限制
- 运行时或设计中的已知上限,例如工作线程数或队列长度
- 合同性约束,例如服务级协议(SLA)或服务级目标(SLO)
- 容易预测的指标,例如延迟、重试次数或等待时间
下图展示了工具通常如何定义和可视化静态阈值:
图 3.2 —— API 速率限制是静态阈值的一个很好的例子,因为它们是明确已知的系统边界
基线建模:静态阈值不切实际的地方
在高度动态、同时采用动态扩缩容技术(例如 Kubernetes Event Driven Autoscaling(KEDA)和 Karpenter 开源集群自动扩缩容器)的系统中,我们需要思考静态阈值的替代方案。存在多种基线方法,可帮助我们适应工作负载和环境的动态特性,例如:
- 自动自适应基线(Auto-adaptive baselining) :根据滚动历史窗口自动调整阈值,例如过去七天的数据
- 多维基线(Multi-dimensional baselining) :在多种数据维度上计算基线,例如服务端点或地理区域
- 季节性基线(Seasonal baselining) :能够理解明显季节性行为的基线,例如工作时间或节假日
下图表明,动态基线通常通过置信带来可视化,而且多数工具还允许根据具体用例调整基线建模方法:
图 3.3 —— 对某些数据进行基线建模后,就无需再大规模定义和维护静态阈值(来源:grafana.com/docs/grafan…
虽然静态阈值在可观测性中始终会占有一席之地,但在我们现代的高动态环境中,我们必须要有替代方案。不仅设置阈值的方式需要改变,就连我们设置阈值所依据的数据也需要改变。
超越 CPU 和内存:云原生黄金信号
CPU、内存、磁盘和网络一直都是非常适合告警、或作为基础设施扩缩容触发条件的指标。在此基础上,许多站点可靠性团队都遵循 Google SRE 手册中定义的“四大黄金信号”(延迟、流量、错误和饱和度)。因此,SRE 和 ITOps 团队通常非常清楚其系统的边界,也能够采取纠正措施来修复问题,例如:调整虚拟机规格以满足当前内存和 CPU 需求、重启停止处理请求的异常或挂起进程,或者将传入负载分摊到负载均衡器后的多台虚拟机上,以确保更快的单次响应时间。
现代云原生架构,尤其是像 Kubernetes 这样的容器编排器,引入了新的组件、层次以及内建自动化,从而自动化或简化了这些任务。比如 Kubernetes 的 Operator 模式及其内建控制循环,就将部分原本需要 ITOps 团队自己实现的任务自动化了,这也改变了 ITOps 团队的关注重点:
- 自动重启挂起进程:Kubernetes 会为我们自动重启不健康的 Pod。因此,告警不一定要针对无响应 Pod 本身,而应更多关注某些 Pod 是否频繁重启、重启是否开始出现问题,或者是否影响到了其他组件。
- Pod 的自动调大/扩容:Kubernetes 可以根据资源需求变化或工作负载变化,对 Pod 进行纵向扩容(vertical scaling)或横向扩容(horizontal scaling)。因此,告警必须聚焦于这些调大/扩容任务何时出现问题,或何时引发服务中断。
- 集群自动扩容:和任何系统一样,Kubernetes 也受到其所有节点 CPU、内存和磁盘总量的限制。然而,集群自动扩容可以从底层基础设施虚拟化层新建节点。因此,告警必须聚焦于集群扩容本身是否按预期工作,以及是否能及时完成,以避免影响已部署的 Pod。
随着部分自动化被下沉到云原生平台的不同层(Kubernetes、OpenShift 或托管云服务)中,我们需要关注新的健康指标,然后决定应如何对其进行最佳告警,以及哪些指标应作为扩缩容决策依据。
说到云原生,我们——本书作者——认为,是时候重新定义那些帮助我们监控健康状态、触发告警并驱动整个动态技术栈扩缩容的黄金信号了。下图展示了一个现代平台,以及各层对应的指标:
图 3.4 —— 为云原生平台各层重新定义黄金信号
下面我们来看看云原生技术栈中部分指标,从基础设施层(底部)一直到应用层(顶部),以及横跨全局的可观测性层。
资源层(Resource layer)
毫无疑问,我们需要足够的资源来处理所有工作负载请求。做好充分的测试和容量规划,然后基于计算、存储和内存设置告警与扩缩容,这一点并没有太大变化。
在云原生架构中,我们看到更多组件通过多种通道进行通信。比以往任何时候都更重要的是,网络延迟、吞吐量和服务质量对整体稳定性和性能起着关键作用。
基础设施层云原生可观测性建议
我们建议对 Ingress 和 Egress 流量进行基线建模。显著的激增或下降都可能表明存在配置错误,例如开发人员不小心在生产环境打开了调试日志,或者 SRE 团队把分布式追踪采样率调到了 100%。由于所有这些数据都需要通过网络发送,任何偏离预期行为的变化都可能影响业务关键服务的网络健康。同样的基线还能够检测 DNS 或防火墙配置错误,导致流量无法到达目标地址的情况。
编排层(Orchestration layer)
Kubernetes 是领先的容器编排层,并提供了大量重要的健康指标。如果你没有使用 Kubernetes,那么你选择的编排层(自研或供应商方案)也会有类似指标来衡量健康状况。关键指标主要围绕编排层 API 的可用性,以及它调度和部署新工作负载请求的速度。
Kubernetes 云原生可观测性建议
我们建议对新工作负载的调度延迟(scheduling latency) 和部署延迟(deployment latency) 进行基线建模。也就是从执行 kubectl apply 开始,到 Kubernetes 找到合适节点进行部署所花的时间,以及 Pod 真正被部署完成还需要多久。待调度 Pod 数量的增加最终会导致部署变慢,这说明集群状态不健康,或没有足够资源来满足所有这些进入系统的调度与部署请求。
工作负载层(Workload layer)
工作负载是指运行在 Kubernetes 集群上的一组 Pod 和容器,用来承载一个应用或服务。它定义了 Pod 和容器、它们应如何部署、如何分配资源以及如何通信。
最重要的是,工作负载能够被及时启动、持续运行,并在需要时成功重启。持续关注 Pod 失败率、失败原因(例如触碰资源限制)以及重启失败次数至关重要。
容器工作负载云原生可观测性建议
除此之外,我们还建议对工作负载启动所需时间以及工作负载运行时长进行基线建模。启动时间在很大程度上取决于工作负载类型。类似数据库的工作负载通常启动更慢、运行时间也更长;相比之下,一些仅用于特定场景、其他时候会缩容至零的小型微服务则不同。另一方面,Init 容器或依赖项也可能影响小型工作负载。因此,我们建议按工作负载类型,对启动时间和生命周期时长进行基线建模。任何时序行为的变化都值得进一步深入分析!
平台服务层(Platform service layer)
平台服务通常是业务服务和应用所依赖的共享服务。例如消息队列、服务网格、数据库及其他存储服务,以及像身份认证服务或单点登录(SSO)这类关键共享业务服务。所有这些服务都会向其消费者提供某种 API,因此我们会看到,四大黄金信号(延迟、吞吐、错误和饱和度)被广泛用于监控和告警其健康状态。
图 3.5 —— 确保 Kafka 健康的关键技术指标
队列与关键服务的云原生可观测性建议
我们建议对队列的关键健康指标进行基线建模,例如等待时间、队列长度、消息大小、压缩率以及处理时长。一个持续增长的队列,或等待时间不断上升的队列,都是必须处理的警告信号,否则它将影响依赖它的下游服务。由于大多数关键服务也都有 API 速率限制,因此监控并对速率限制使用率或突发使用率进行告警,是很好的实践。它们可能表明调用方存在配置错误,或者在整体交易量上升时,需要为某些调用方调整限流值。
服务层(Service layer)
这里的服务,指的是支撑平台上应用运行的所有业务服务。对于一家金融服务机构来说,这些服务可能包括 account-service、trading-service、transfer-service 或 currency-service。
围绕这些服务的可用性、性能和失败率定义 SLO 是一个很好的做法,因为它们能很好地反映服务是否兑现了自己的承诺。虽然 SLO 以及错误预算、预算消耗速率(burndown rate)是极好的工具,但如果使用不当,也会带来管理和运维成本,因此这里给出如下建议。
关于 SLO 的云原生可观测性建议
我们太常见到一些组织为每一个内部和外部 API 端点都定义 SLO。这会导致成千上万个 SLO,而且组织内部没有统一的阈值标准。更好的做法是:只为业务交易关键路径上的终端消费者 API 定义 SLO。即便如此,最佳实践仍然是为 gold、silver、bronze,或者 tier 1、tier 2、tier 3 API 提供模板,预先定义公司范围内统一的阈值和告警标准。至于其他 API,我们建议采用异常检测,再去看某个内部 API 的异常是否真的影响到了上游的 SLO。关于这一点,我们会在本章稍后的“从运维走向业务:基于 SLO 的影响分析”一节中进一步讨论。
应用层(Application layer)
大多数服务都会为业务应用及其关键业务交易提供关键 API。例如,登录网上银行系统的用户数量、创建新的银行账户,或用户之间转账。
对于任何组织来说,这是最关键的一层,因为这正是业务产生收入、或者员工得以完成工作的地方!
从高层来看,你需要确保这些系统和关键交易可用。最低限度,我们建议使用合成测试(synthetic tests) ,来验证这些关键交易是否能够端到端正常工作,并对任何可用性或性能退化进行告警。
关于业务层的云原生可观测性建议
我们还建议对能够反映业务是否健康的关键指标进行基线建模。这包括系统中的并发活跃用户数、业务流程和交易吞吐量、关键交易耗时,以及每个用户或每个会话的交易数量。如果这些数字在正常工作日或工作周期间开始偏离常态,那么你就应该采取行动。聚焦业务影响已经成为现代可观测性的关键,这也是为什么我们会在本章稍后的“从运维走向业务:基于 SLO 的影响分析”中更详细地讨论这一点。
可观测性层(Observability layer)
有句话说:最好的总在最后。没有可观测性数据,任何告警或扩缩容都无法实现。因此,我们同样需要把健康原则应用到可观测性技术栈本身。这包括埋点、采集、存储和分析的健康。当使用 OpenTelemetry 时,例如,我们需要确保所有 Collector 的容量合适、所有 receiver 都健康、没有数据丢失,并且所有可观测性信号都被正确转发到后端分析平台。
关于可观测性技术栈的云原生可观测性建议
除了遵循可观测性工具自监控的最佳实践之外,我们还建议对所有可观测性信号的接收量、处理量和入库量进行基线建模。这个基线能帮助你识别可观测性数据是否突然激增或骤降。根因可能是配置错误,或者是手工或自动埋点发生了意外变化。我们见过因为手工埋点代码失误,导致 span 数量暴增 100 倍的例子。
一个非常适合使用静态阈值的关键指标是信号丢弃比例,因为理想情况下,除非是有意为之,否则你不应有任何信号被丢弃。
“分析可用时间”这个指标定义了从数据采集开始,到该数据点能够在仪表板中展示或可供告警所花的时间。这个时间越长,你对问题的响应就越偏向“事后反应”,而不是“事前预警”。
前面讨论的这些指标和度量,可以为你理解云原生平台健康状态提供一个很好的起点。并不是每个人都运行在 Kubernetes、OpenShift 或其他容器编排平台之上,因此理解你自身环境的各个层次非常重要。请务必查阅你所使用供应商的最佳实践文档。另一个非常有效且强烈推荐的方式,是通过负载测试和性能测试来选择正确的指标。
通过规范的负载测试选择正确的指标
每个应用和分布式系统都是独特的。因此,不存在一个放之四海而皆准的“黄金模板”,可以罗列出所有必须关注的指标。尽管我们前面讨论过的指标对大多数系统都很重要,但理解你的系统在不同条件下的特性和动态行为仍然十分关键。
负载测试和性能测试是一项关键实践,它能帮助你理解应用的动态行为。它可以帮助你识别架构中的关键组件,例如:当某些队列或共享服务开始变慢或失败时,哪些组件会引发连锁反应;或者某个关键组件在开始崩溃之前,理论上能承受多大的最大吞吐量。
全面展开如何建立一套完善的负载测试体系,超出了本书的讨论范围。不过,我们仍然想给你一些指导,说明关键步骤是什么,并告诉你去哪里获取更多信息。
步骤 1:搭建测试环境
如果你可以直接在生产环境测试,那么我们甚至可以在这里就停下来了。对于非常大胆的人来说,这意味着完全不做前置测试,直接上线,然后对任何发生的问题进行响应。对于没那么冒险的人来说,这也可能意味着使用生产故障切换环境来做负载测试。有些组织也会在生产系统低流量时段直接做测试。这两种情形都高度依赖于你的生产环境是如何搭建的,以及这些环境中是否能够部署不同版本的软件。如果你做不到这些,请继续往下看。
理想情况下,测试环境应尽可能贴近生产环境。但这并不总是可行的,因为运行这些大型系统的成本,以及搭建和运维这类系统所需要的时间,都非常高。虽然得益于自动化和按需提供的云资源,短时间拉起所需基础设施已经变得更容易,但最常见的挑战仍然是:很难复制一套与生产系统记录数据规模和特征相近的数据集。
网上有大量资料介绍如何克服这些挑战,例如在不触及合规问题的前提下生成具有代表性的生产数据,甚至考虑对这些数据层进行 mock 或模拟。
如果无法实现一个类生产系统,我们建议搭建一个缩小版环境,保证它能在可接受的成本内搭建并运行,因为这能帮助你理解:当系统扩展到大规模时,跨各层的哪些指标会成为问题的领先指标。记住,这才是整个练习真正的核心所在。
步骤 2:定义真实场景
测试一辆跑车时,你不会只让它挂一档、以每小时 20 英里的速度行驶。那并不能反映真实驾驶者会如何使用它。软件测试也是同样的道理。在创建任何测试场景之前,你需要先理解最主要的真实终端用户场景是什么。理想情况下,你可以从被测试应用和服务的负责人那里获得这些信息。更理想的是,你可以直接查看自己的可观测性系统,从中提取这些信息;例如,哪些 API 命中次数最多,或者在 Web UI 或 API 调用链中,最常见的点击/调用序列是什么?你还可以了解这些负载在这些场景之间的分布情况,以及在一个正常的 24 小时时段里它们是如何变化的。这些洞察都有助于你把负载测试尽可能建模得接近生产。
一旦掌握了这些信息,你就可以选择自己偏好的负载测试工具并创建测试场景。
可选工具并不稀缺,包括大量开源方案,例如 JMeter、Gatling、k6 和 Locust。选择最适合你、且便于你轻松创建和维护测试场景的工具即可。
步骤 3:向左扩展可观测性(expand-left observability)
“Shift left(左移)”这个术语在过去几年里被频繁使用。通常,它指的是把任务和职责前移到软件开发生命周期(SDLC)的更早阶段。对我们的场景而言,一个更好的说法是 expand left(向左扩展) ,因为我们真正想做的是:把生产环境中使用的工具和实践,扩展到生命周期更早的阶段。对于测试来说,这意味着我们只需在负载测试环境中使用与生产环境相同的可观测性工具和实践。这样不仅能帮助我们捕获相关数据,用于识别生产监控中的关键指标,也能验证我们的可观测性体系本身是否按预期工作。例如:你是否采集到了所有日志、指标和追踪?你是否能像在生产中那样访问这些数据?你的可观测性体系是否因为你已经设置了动态基线或使用机器学习识别异常,而能在负载测试期间自动对瓶颈发出告警?
我们希望你已经能看到“向左扩展”这个概念有多么强大。
步骤 4:运行测试
理想情况下,你应当持续运行测试,有点像始终在模拟生产负载。这让你可以持续验证测试系统是否可运行,也能持续观察系统在持续负载下的行为。例如,这种方式特别适合发现那些只有在一段时间之后才会显现的内存泄漏或连接泄漏。如果你有这样一套系统,那么你还可以验证:当系统持续承压时,部署新版本组件会发生什么。这是一个极好的测试,因为这正是生产环境中发布变更时会真实发生的事情。
当然,如果无法做到持续测试,我们建议以固定节奏运行较短时间的测试,例如每天一次,或每周几次。这能持续向工程师反馈系统变化对内部行为的影响。
还存在不同类型的测试,它们具有不同的负载行为,例如逐步加压测试、稳定负载测试、突刺测试和压力测试。关于不同模拟行为及其目标,你可以从许多在线资源中获取更多信息。你也可以应用混沌测试(chaos testing) :在负载测试过程中模拟各种故障,把系统推入临界状态,例如人为放慢网络连接、杀掉 Pod,或关闭数据库这类关键服务。
所有这些测试以及模拟混沌,都将为你提供大量可观测性数据,用于分析并识别新的技术指标,而你可以把这些指标应用到生产监控与告警中。这也引出了下一步。
步骤 5:识别关键指标
负载测试具有多个目的。一方面,它告诉我们系统在模拟负载下是否按预期工作;另一方面,它让我们能够优化系统参数和配置,以便在负载下获得更优的系统输出,例如调优连接池大小,或调整容器资源与请求限制以实现最佳资源利用率。它还帮助我们验证:我们的可观测性体系是否已经捕获了发现问题、性能瓶颈及其根因所需的全部数据。这样一来,一旦部署到生产,我们就更有信心在生产出现问题时也能找到根因。如果在本应发现问题的时候我们却没有任何迹象,那就意味着我们需要重新思考:开发人员还需要在系统中补充哪些额外的指标、日志或追踪,以获得更好的洞察!
另一个收益是:我们可以在测试结束后查看全部采集到的数据,观察是否存在某些指标、日志或追踪,其中包含的信息可以作为性能或可用性问题的早期预警信号。
一旦你识别出这些指标,务必确保在生产环境中也对其进行监控。正如本章前面讨论的那样,把它们作为早期健康预警指标来使用。还要记住,系统会随着每次发布持续变化,因此你需要不断验证并更新这些关键指标,因为它们本身也可能随时间变化。
现在你应该理解,现代可观测性的重点已经不再局限于 CPU、内存和磁盘。尽管这些指标仍然很重要,但我们已经拥有了新的系统健康指标,例如 Kubernetes 集群部署 Pod 的速度。我们也讨论过:在需要面对成千上万个云原生组件,而非传统架构中的少量组件时,设置和维护静态告警阈值已经无法扩展。现代可观测性平台提供了从简单到复杂的多种基线算法,作为替代方案,它们会基于历史行为自动调整告警。
为了结束这一节,让我们来看一个我们合作过的组织中的具体实施场景。
用例:基于 Kubernetes 健康状态的基线告警
定义系统健康的方法有很多种,Kubernetes 也是如此。观察健康状态的一种方式,是查看 Kubernetes 能否顺利且成功地配置所请求的资源。Kubernetes 的特性在于:由于其控制器和 Operator 模式的实现,它会自动重试失败的操作。因此,仅仅对一次失败进行告警并不好,因为系统会自行重试。更好的做法是分析趋势,并对异常行为进行告警。这正是基线特别适合的地方。下图展示了如何对 Kubernetes Provisioning Failed 事件数量建立基线。当我们摄入这些事件后,就可以对“正常”进行建模,然后对“异常”或“偏离基线”的情况进行告警,如图所示:
图 3.6 —— 针对 Kubernetes Provisioning Failed 事件数量,使用基线告警优于使用静态阈值
上图显示,在早上 7:00,Provisioning Failed 事件数量异常偏高。使用静态阈值很可能会产生过多误报,因为我们可以看到,失败事件本身是持续发生的。在这里使用基线还意味着:如果“基础错误事件量”随着时间增加或减少,我们的告警会自动调整。我们真正关注的,是那些异常的、超出基线的行为。
现在,我们已经看到了如何把监控从“对已知基础设施指标采用静态阈值告警”,提升到“在现代平台不同层面上利用基线建模”。接下来,是时候思考:我们如何从根本上改变可观测性的方法了。
我们需要从“在系统上线到生产之后再试图理解它”,转向“让软件工程团队在写下第一行代码之前,就把可观测性作为关键软件需求”。
可观测性驱动开发
“这是开发交付的新软件包。现在交给你们运维,你们自己想办法让它稳定运行。”这就是过去很多组织采用的那种经典的“把东西扔过 DevOps 墙”的思维模式。
关于“shift left”和“expand left”哪个更适合描述将可观测性更早嵌入软件开发过程,我们可以继续讨论。但我们——本书作者——更喜欢 observability-driven development(可观测性驱动开发) 这个术语,因为它准确描述了组织内部必须发生的那种思维转变:把可观测性视为软件需求。就像测试驱动开发强调“在写业务代码之前先写测试”一样,可观测性驱动开发强调的是:在软件第一次部署到测试环境或生产环境之前,就先定义观察什么以及如何观察。
像这样的转型不会一夜之间发生,也不会因为某个人一声令下就能实现。和很多其他转型一样,实践表明,从一个“灯塔项目(lighthouse project)”开始是成功率很高的做法:在这个项目中贯彻新的开发流程,把可观测性嵌入其中。一旦这个项目成功,其他团队就会跟进。下面我们就带你走一遍这样一个灯塔项目实施过程中的关键思想与步骤。
步骤 1:定义系统内部和外部健康指标
当你看手机时,如果看到电池电量从绿色变成黄色,最终变成红色,你就知道必须尽快采取行动,否则手机就会没电。开车时也是一样,如果仪表盘显示发动机温度逐渐逼近红区,你就知道发动机正走向不健康状态,可能导致无法修复的损伤。根因可能是驾驶方式不当,比如长时间高转速,也可能是冷却液泄漏。
就像这些简单的健康指标一样,我们也需要定义如何衡量软件系统和组件的健康状态。通常,我们首先从调用者或消费者感知到的系统健康开始度量。当系统是通过 API 被调用时,我们可以遵循“四大黄金信号”的最佳实践:
- 吞吐量(Throughput) :系统是否能够在单位时间内处理我们为其设计的请求数量?
- 延迟(Latency) :系统是否能在终端用户可接受的时间内完成请求处理?
- 错误(Errors) :系统在大多数情况下,是否能够对合法输入返回有效响应?
- 饱和度(Saturation) :系统是否按照容量规划,使用了预期范围内的资源?
但是,正如前面发动机过热的例子所说明的那样,我们还必须定义额外的技术性领先指标,以便在系统对用户变得不健康之前就收到告警。我们需要定义那些能发出早期预警、同时也可用于根因分析的指标。下面是一些例子:
- 缓存命中率(Cache hit ratio) :对于内部带缓存的系统来说,这个指标是判断缓存是否按预期工作、是否需要更新的极佳信号,它可以在整体性能和稳定性受到影响之前就暴露问题。
- GC:对于内存抖动密集型系统,GC 次数和 GC 耗时是很好的内部健康指标。行为变化表明可能存在内存泄漏,或者内存行为模式发生了变化,这可能需要调整堆大小或 GC 策略。
- 错误请求格式(Incorrect request format) :对于任何请求/响应系统来说,尽量不要在格式错误的输入消息上浪费太多时间,这一点非常重要。它通常意味着调用方使用方式错误,也可能表明某种拒绝服务攻击。了解本质上无效的请求消息占比,是一个很好的健康指标,因为它让我们能够在问题影响正确请求处理之前就做出反应。
一旦我们知道什么是良好的外部和内部健康指标,就必须确保这些指标能够被测量。
步骤 2:定义如何测量健康指标
对于大多数健康指标,往往已经存在现成的方法,可以从日志、指标或追踪中提取。例如 API 响应时间,我们可以通过在 Ingress 控制器上启用 Prometheus exporter 来获取,或者从 HTTP 请求日志中抽取。如果没有现成方式,开发人员可以通过 Prometheus 或 OpenTelemetry 导出额外数据,或者把它写入日志,再在数据管道中将其转换为指标,例如通过 OpenTelemetry Collector。如果采用基于 Agent 的可观测性方式,那么很大概率这些指标开箱即用,无需额外开发介入。
对于系统特定的内部指标,例如错误请求格式、数据库往返次数,或任何业务指标,例如新开银行账户数量,就有必要定义如何测量它们,以及如何将这些信息暴露给可观测性工具(Prometheus、OpenTelemetry、日志等)。你还需要明确:这些指标的哪些取值范围表示健康,哪些表示不健康;否则,即使指标超出了预期范围,也不会有人采取行动。
步骤 3:让工程团队能够轻松获取这些数据
“全量采集、无处不在,并且要让它易于访问!”只有这样,可观测性驱动开发才有可能被真正采用,因为它避免了工程师为了找到数据而多走一步。
为了做到这一点,我们必须回答两个关键问题。
数据是在何处被采集的?谁需要它?
稍后在本章“上下文为王:可观测性数据的质量”一节中,我们将更深入地讨论如何在数据采集时为其补充这些额外元数据。而这些元数据,正是回答下一个问题所必需的。
这些数据应在何时、何地、以什么形式提供给谁?
首先,我们要了解工程师日常使用哪些工具,以及他们在完成不同任务时主要花时间在哪些地方:
- 用于自助服务的 IDP 是什么?例如 Backstage、Kratix 或 Confluence?
- 用于编码的 IDE 是什么?例如 Visual Studio Code 或 IntelliJ?
- 用于提交代码的 Git 平台是什么?例如 GitHub、GitLab 或 Bitbucket?
- 用于沟通的聊天平台是什么?例如 Slack、MSTeams 或 Discord?
- 用于任务管理的工单系统是什么?例如 Jira 或 ServiceNow?
接下来,我们需要分析:工程师在这些工具中为了完成某些任务,需要做出哪些决策,而可观测性数据又能如何帮助他们。例如,在排查问题时,工程师是更适合在服务目录中看到自己组件最近的关键日志,还是更适合把这些信息直接呈现在新创建的 Jira 缺陷工单中?
下面这个由某航空公司实施的自助式用例,就是一个很好的例子:它利用可观测性数据帮助提升软件质量和性能,而无需工程师自己去手动识别热点。
自助式用例 1:为团队晨会提供前三大数据库查询
大多数开发团队每天都会开站会,讨论当天任务,并向同事寻求解决当前阻塞问题的建议。这正是可观测性作为自助服务发挥价值的绝佳时机。
通过利用包含数据库查询调用信息的分布式追踪,团队可以构建自动化流程,识别过去 24 小时中生产环境里各服务最频繁出现的前三大数据库查询,这与我们在第 1 章“观察数据库”一节中讨论的内容类似。借助那些被额外补充过上下文的信息,例如哪个团队拥有该服务、该服务当前部署的是哪个版本,他们还可以在团队早上 8 点开站会之前,刚好把这份信息直接发送到团队内部聊天频道。
这对工程师来说是一个即时价值时刻,因为他们能够在自己日常讨论问题所使用的工具中(例如 Slack 或 MSTeams),直接获得可执行的洞察。
步骤 4:不断打磨、补充上下文,并自动化走向生产
“这些数据真的有用吗?你能据此采取行动吗?当生产出问题时,它真的能帮助你排障吗?”
如果这些问题的答案是否定的,那么就该重新打磨健康指标定义,并更新你正在采集的数据,以及你如何利用这些数据。
随着软件逐步逼近生产环境,你还需要提出这些问题:“有没有哪个健康指标你希望对其触发告警?有没有哪个指标应该触发扩缩容?或者在排障时必须能访问到?”
这些问题的答案,将帮助你建立告警——无论是像本章前面讨论的那样使用自定义阈值,还是使用基线。接着,这些告警既可以创建一个问题工单用于排查,也可以通过打开一个 Pull Request 的方式触发自助式 GitOps 流程,正如下一个自助式用例所展示的。
自助式用例 2:以 Git Pull Request 的形式提供容器资源规格优化建议
为容器定义合理的 requests 和 resource limits 是一件非常有挑战的事。也正因此,大多数 Kubernetes 工作负载定义要么根本不设置 limit,要么只依赖默认值。
如果我们拿到容器实际 CPU 和内存消耗的指标,并将其与当前设置的 limit 进行对比,就能构建自动化流程,自动发起一个 Pull Request,建议采用更贴合生产系统真实资源需求的 limits。
这对工程师来说也是一个即时价值时刻,因为他们在自己所遵循的开发流程中,直接得到了可执行的洞察;例如,一个带有如何修改工作负载建议的 Pull Request。
我们会在“AIOps:通过异常检测与根因检测降低噪声”一节中更详细地介绍这个用例,因为它既需要自动化,也需要一些 GenAI,才能以正确格式生成对这些 Kubernetes 工作负载定义的 Pull Request。
图 3.7 —— 基于可观测性数据自动生成建议 request limit 的 Pull Request
这两个简单却强大的自助式用例,突出了贯穿全栈摄入带上下文增强的可观测性数据有多么重要。我们会在本书后续内容中继续介绍更多用例,尤其是在讨论这些用例如何与自助式平台对齐时。
遵循这些步骤后,运维和开发双方都很可能会看到这种方法的价值:运维能够提前了解一个新软件组件需要哪些可观测性数据,从而无需再自己研究,或在故障发生时通过现场排查才识别出来;而开发则明确了哪些信息对自己重要,并能在需要时随时访问这些数据,从而让排障和修复问题的过程更快、更轻松。
随着越来越多的软件团队采用这种新方法,被采集到的可观测性数据也会越来越多。但“更多”并不总是“更好”,尤其是当数据在缺乏正确上下文——例如软件组件、版本和团队信息——的情况下被采集时。为了让从开发开始的现代可观测性能够扩展,我们必须制定一套良好的策略,用更多上下文去增强数据。这使得许多新的用例成为可能,例如前面提到的“把最热数据库查询发送给负责团队”;同时也能确保 IT 运维团队不会被海量数据和潜在告警淹没,却根本不知道该由谁负责、该把数据发给谁。
接下来,让我们深入看看其中的细节!
上下文为王:可观测性数据的质量
我们已经说过很多次,而且还要再说一次:可观测性数据正在增长,而且还会继续增长!
挑战在于:如何应对这种增长,并确保这些数据能产生真正有价值的洞察,而不是只是躺在一个不断堆积、持续增加存储成本的“大数据湖”里。
我们知道,必须借助 AI 来管理这些数据;但只有当这些数据具备足够质量,并且被补充了足够的上下文——包括它来自哪里、它与其他数据如何关联、以及哪些数据对决策有用——AI 才能真正帮到我们。
接下来,我们将逐步介绍有关何时、何地进行数据增强的最佳实践,以及这些增强后的数据如何支持我们从可观测性实施中挖掘更多价值的用例。
让我们先从“规模扩展”所带来的挑战,以及“语义约定”的必要性开始说起。
从宠物到牲畜:面向可观测性的语义
如果你曾经管理过基础设施,那么你很可能听说过“宠物与牲畜(pets versus cattle)”这个类比。与其把每台服务器都当成一只独一无二、精心照料的宠物——有可爱的名字,甚至记得它的“生日”——我们更需要把基础设施看成牲畜。所谓“牲畜式思维”,是指我们要接受这样一种现实:组件的数量可能接近无限,它们应该被视为一次性的、可替换的,一旦出问题就直接替换掉。为了让这种模式能够扩展,我们也必须把思维方式从“手工照顾每一个组件”,转向“自动化完成任务”。
在云原生架构中,默认不会存在“宠物名字”。但在实体命名上,存在某些语义约定。以 Kubernetes 为例,对象在创建时会获得一个新的唯一标识符(UID),并生成一个名字;即使对象以前曾被创建过,每一个新实例的名字也会不同。拿 Pod 举例:如果你创建一个名为 my-critical-svc 的工作负载,那么这个工作负载的每一个 Pod(无论是一副本还是多副本),在 Pod 重启或扩容时,都会得到类似 my-critical-svc-1694、my-critical-svc-4352 这样的唯一名称。
这也意味着,在可观测性领域,我们需要调整对“被监控实体”的理解:主机、集群、节点、工作负载、Pod、服务、无服务器函数、数据库——这些实体在其整个生命周期中,可能都不再拥有一个永久唯一的名字,尤其是在它们被重启、扩容或缩容时。为名叫 Joe、Sally 和 Hugo 的服务器创建仪表板的时代已经过去了。取而代之的是:我们现在创建的是面向特定用例和技术栈的仪表板,然后基于经过标准化语义约定的元数据来进行过滤。这些元数据可以来自我们正在观测的“牲畜式”基础设施上的标签(labels)、注解(annotations)或标签/标签集(tags)。
图 3.8 —— 可基于元数据(例如服务名、服务器、集群或云区域)进行过滤的可观测性仪表板(来源:grafana.com/docs/grafan…
接下来,让我们深入看看在现代实践中,如何以能够随组织和运维需求扩展的方式来组织可观测性数据。
跨全栈增强可观测性数据
当谈到如何组织可观测性数据、并为其增加上下文时,我们必须从全栈角度审视所有元数据(从主机,到命名空间,到容器,到服务,再到端点)。例如,一条日志,不仅要包含它是由哪个 API 调用生成的,还应包含关于 Pod、工作负载、命名空间、Kubernetes 节点以及云区域的信息。这对于排障和根因分析至关重要,因为它让我们能够回答关键问题:
- 这个错误是否发生在所有云区域中?
- 它是否发生在所有不同版本的 Kubernetes 集群中?
- 它是否仅限于某一次具有特定配置的部署?
下面我们将从技术栈的上层一路讲到下层,讨论上下文增强的最佳实践。
使用基础设施标签进行增强
大多数基础设施团队都已经摸索出一套良好的基础设施打标签方法。通过 Terraform、Ansible、Chef 或 AWS CloudFormation 等 IaC 工具创建新的虚拟机时,单个服务器实例通常会遵循统一的命名规则,同时还会被附加一些标签,用于表明该基础设施为什么存在、其用途是什么、以及由谁负责。下面是一个例子:
- Name:
foneacme_p_w_csp_web3 - Environment: Production、Dev、Staging
- Group: Web、DevTools、BusinessApps、Backoffice
- Application: CustomerServicePortal、OnlineBanking、TimeManagement、Salary、SalesPortal
- Project: Project-A、Project-B、Project-C
- Owner: CustomerService、Engineering-TeamA、LOB-OnlineBanking、SalesUSEast、CFO
- CostCenter: Dev、Sales、Finance、LOB
关于良好的标签策略,请查阅你的云服务提供商所提供的白皮书与最佳实践文档,例如 AWS 的《Best Practice for Tagging AWS Resources》[1]。
在摄入日志、指标、追踪,或任何类型的事件(部署、配置、安全等)时,最佳实践是:用同样的标签去增强这些信号。这样可以确保过滤、告警和访问控制都建立在这些已有元数据之上,而无需在原有标签之外再额外配置。沿用前面的例子,我们来看两个场景。
用例:仅销售团队可访问的日志
如果销售门户后端的日志在摄入时附带了 Owner:SalesUSEast 标签,那么这些日志很可能只应对该区域销售组织的成员,以及全球销售团队可见。可观测性后端存储可以通过配置某些厂商所称的 security context(安全上下文) ,来确保带有该标签的日志只对这些团队成员可见。
用例:开发环境的追踪仅保留两个 Sprint
如果在低级别开发环境中采集的追踪被增强为带有 Environment:Dev 标签,那么它们可能只需保留两个 Sprint。为什么?因为开发周期中采集到的追踪,通常只在活跃开发期间才有意义。大多数开发者并不需要在更晚的时候还查看开发环境中的追踪。因此,可以为这些追踪设置较短的保留周期,由此组织能够节省可观测性存储和保留成本。
请回头审视你当前的基础设施标签策略,并确保这些标签能够用于增强任何摄入的可观测性信号,包括日志、指标、追踪、事件、真实用户数据以及 profiling 数据。
看完这些用例后,让我们沿着技术栈向上走一层:从基础设施进入应用部署层。
用部署中的标签、label 和 annotation 进行增强
基础设施是应用和服务部署的承载平台。当我们在 Kubernetes、无服务器函数平台,或负载均衡器、数据库服务等云 SaaS 服务上部署工作负载时,我们可以通过额外的标签、label 或 annotation,为这些部署补充更多元数据。
下面是一段 Kubernetes Deployment 定义模板的快照,其中包含了关于版本、环境、服务依赖和环境的一些额外元数据:
template:
metadata:
labels:
dt.owner: "engineering-team1"
app.kubernetes.io/name: authentication-svc
app.kubernetes.io/part-of: customer-service-portal
app.kubernetes.io/version: 4.0.2
dynatrace-release-stage: "preprod"
backstage.io/component: "default.authentication-svc"
与基础设施标签类似,这些 label 也可以用来增强从该 Deployment 所运行的 Kubernetes Pod 中摄入的任何日志、指标、追踪或事件。这样就能支持如下用例。
用例:版本 4.0.3 足够好吗?还是我们需要回滚?
在分析指标、日志或追踪时,我们现在可以比较某个服务不同版本在响应时间、执行行为、内存消耗或错误日志方面的差异。如果团队决定将新版本推送给一部分客户,他们就能够有选择地分析和比较这些信号。此外,我们还可以利用 AI 以及基于元数据的自动异常检测。当新发布版本的失败率高于前一版本时,这能使开发团队收到自动告警,从而快速回滚到更稳定的版本。
用例:customer-service-portal 在预生产环境中的质量是否足以进入生产?
将所有环境(开发、质量保障、预生产和生产)中的部署信号及其元数据摄入进来后,也能很方便地进行版本对比。太多时候,人们会提出这样的问题:“我们预生产中的新版本,真的会比生产中的当前版本更好吗?”一旦日志、指标和追踪里都带有元数据,你就可以主动比较性能、错误和运行行为,从而更有依据地决定是否应将新版本从预生产提升到生产。
跨 SDLC 增强可观测性数据
前面我们讨论了如何通过全栈信息增强可观测性数据,从而知道一条日志、一个指标或一段追踪究竟来自哪个数据中心区域、集群、命名空间、工作负载、Pod 或服务。我们甚至还可以进一步补充版本号或发布阶段信息。
这些增强过的可观测性数据,已经是我们所谓 SDLC observability(软件开发生命周期可观测性) 的基础。下面我们更深入地解释它解决了哪些问题。
跟踪 SDLC
“我的构建现在到哪一步了?一个代码修复从提交到上线生产要多久?到底是哪个 Git commit 把生产搞坏了?”这些问题,我们在第 1 章“三大支柱及更多——日志、指标与追踪的用例”中已经简要讨论过。那部分内容讲的是:如何摄入在软件制品创建、构建、测试或部署过程中,所有参与 SDLC 的工具发出的事件。这些工具可能包括 Backstage、Jira、GitHub、SonarQube、Jenkins、Harbor、Argo,或你 DevOps 工具链中的任何其他工具。
下图概括了这类具有代表性的 SDLC 事件,以及它们可以从哪些工具中被摄入(通常通过 webhook、通知、事件日志或追踪):
图 3.9 —— SDLC 作为跟踪制品生命周期的标准
将这些事件摄入可观测性后端,并把它们与这些制品在运行过程中产生的日志、指标和追踪关联起来,就能支持以下用例。
用例:自动化实时制品清单与软件目录
通过从每一次交付流水线运行(GitHub Actions、GitLab pipeline、Azure DevOps、Jenkins、Argo、Flux 等)中摄入事件,并包含哪个制品被部署到了哪个环境的信息,我们就能自动生成一个实时总览,用来回答这样的问题:哪个服务、哪个版本,被部署到了哪里,以及是谁部署的?
在大多数组织里,这个问题非常难回答。而跨生命周期的可观测性可以开箱即用地提供这些洞察。
用例:跟踪 DORA 和其他开发者体验效率指标
通过摄入 Git commit、Pull Request、流水线运行以及部署事件,并确保这些事件都包含关于制品的一致元数据(例如构建出的镜像版本),我们就能自动、实时地提供诸如 变更前置时间(lead time for change,即从 commit 到 deploy 的时间) ,以及 部署频率(deployment frequency,即某个组件被部署的频率) 等信息。
大多数工程负责人都试图在组织范围内统一采集这些指标,以形成工程效率洞察。借助 SDLC 事件,你就能为工程负责人提供这些 KPI,以及更多相关指标。
用例:自动关联部署变更与故障
当我们把部署或配置变更事件摄入到与监控已部署组件健康状态相同的可观测性后端时,就可以自动把这些变更与检测到的问题建立关联。这样我们就能自动回答这样一个问题:“是不是我的 Git commit 把生产搞坏了?”
除了能自动将该事件与问题关联,并将其识别为潜在根因外,我们还可以建立自动化机制:立即打开一个新的 Pull Request,建议回滚当前正在引发问题的那次变更。成熟的组织往往会把这类自动化修复作为标准做法。
把我们的 DevOps 工具整合进可观测性体系、让它们在生命周期关键阶段发送事件,还有一个额外好处:我们也能进一步观测这些工具本身的健康状态和活跃情况。毕竟,对开发团队来说,没有什么比“最需要这些支持工具时,它们却出故障了”更糟糕的了。
观测你的 DevOps 工具
“为什么今天 Jira 这么慢?为什么我最近一次代码变更还没通过构建流水线?为什么我在使用 Visual Studio Code 的 AI 代码审查代理时,总是报错?”
有时候,这些问题的答案其实很直接。可能是 Jenkins 流水线执行变慢,因为共享基础设施出了问题;Jira 不可用,可能是网络路由配置错误;又或者某个配置错误的 MCP 服务器执行了过多 GitHub API 调用,导致其他用户遭遇限流。
虽然这些答案听起来很简单,但通常它们并不会被自动识别出来,而是只有当人们已经受到影响并开始抱怨之后,才会被发现。
得益于 OpenTelemetry 这类开放标准的广泛采用,我们看到越来越多此类工具内建了更好的可观测性支持。这使我们能够像对待业务关键应用一样,对这些工具本身也应用同样的可观测性实践。
理想情况下,你也应该为 Jira、GitHub、GitLab、Argo、Harbor 等类似工具建立完善的可观测性和告警机制。这样你就有机会在问题影响到大多数工程师之前把它修复掉。
下面这个仪表板基于 GitHub 和 GitHub Actions 的 OpenTelemetry 追踪,展示了一个 GitHub 组织下所有仓库的活动概览,并指出存在问题的仓库或工作流。具备这样的洞察,有助于你让这些工具保持健康运行。
图 3.10 —— 借助 OpenTelemetry 等开放标准,可观测性可以覆盖新的用例,例如对 DevOps 工具进行观测
不妨做一个思维实验:想想你们工程组织中所有的工具。有多少已经建立了完善的可观测性和告警?哪些还没有?如果提前具备合适的告警体系、在问题影响到大多数工程团队之前就把它修掉,你本可以节省多少工程工时?
希望到现在为止,你已经对现代可观测性有了扎实的基础理解,并明白了:为什么高质量、带上下文增强的可观测性数据,能够让自动化和 AI 在你开发、部署和运行 IT 软件系统时,产出更好的、可执行的洞察。在“AIOps:通过异常检测与根因检测降低噪声”一节中,我们将看到,这一切如何组合在一起,让那些必须确保系统持续运行的工程师的日常工作变得更轻松。
在结束这一节之前,我们还必须回应一个你大概已经想到的问题:“这看起来像是海量数据,而且还要再为它增加更多数据点。我们怎么确保它的质量,并管理它的数量?”
数据质量:在哪里增强,采样什么
我们已经大量讨论过:我们需要通过补充全栈和 SDLC 上下文,来增强可观测性数据。剩下的问题是:我们的日志、指标、追踪和事件,应该在哪里进行增强?以及,我们应该如何在“全部采集”和“只采集我们真正需要的内容”之间取得平衡,以控制成本并提升数据分析效率?
如何以及在何处用上下文增强可观测性数据
最好的、也是最有效的方式,是在可观测性数据生成源头附近、也就是它被产生的地方对其进行增强。因为一旦开始讨论如何用机器学习或 AI 来分析可观测性数据,真正重要的就是质量。俗话说:垃圾进,垃圾出。
可观测性数据也是一样。只有当可观测性数据具备良好质量,并且被补充了足够的上下文,我们才能真正发挥 AI 的全部潜力。为什么?因为和人类一样,AI 在面对更高质量的可观测性数据时,也会产生更高质量的结果。
换句话说:高质量的可观测性输入,才能得到高质量的洞察输出。
根据用于采集可观测性数据的工具不同,在何时、何地进行增强会有不同选项。厂商提供的可观测性 Agent 通常会在 Agent 获取这些信号的地方,直接增强日志、指标、追踪和事件。例如,Dynatrace 的 OneAgent 就支持在集群、主机或进程级别增强数据。Dynatrace 还提供了一个公开的语义字典,定义它会查找哪些元数据,以及分析这些可观测性数据后会做什么[2]。另一种常见替代方案是使用像 OpenTelemetry Collector 这样的数据管道。Collector 提供所谓的 processors,可用于定义规则:在日志、指标或追踪被发送到可观测性后端之前,使用哪些附加元数据来增强它们。关于 OpenTelemetry 增强的最佳实践,可参考许多现有教程和最佳实践资料,例如 isItObservable 的 GitHub 教程[3]。
我们真的需要全部数据吗?采样策略
虽然带上下文增强的数据质量更高,也更易于分析,但我们还必须提出一个重要问题:“我们真的始终都需要所有可观测性数据吗?”
想象一家银行希望对每一笔银行交易都进行端到端可观测,包括日志、指标和追踪。若每秒可能有成千上万笔交易,这意味着巨量数据:这些数据都要被采集、附加上下文、发送到后端、存储下来,并在交易失败时用于后续分析。但如果事实上只有 0.1% 的交易会失败,我们是否仍需要把成功的 99.9% 交易也以如此细粒度的方式全部保留下来?还是说,只保留其中具有代表性的一部分详细样本,再为交易总量和响应时间生成指标,用于报表和告警,就已经足够?
这些都是我们必须回答的关键问题,它们将对可观测性后端的规模和成本产生重大影响。
如果我们认定不需要始终保留所有数据的全部细节,那么就可以采用采样(sampling) 策略。顾名思义,采样就是决定哪些可观测性数据应被纳入我们要查看和存储的样本中,哪些则因为无法带来额外价值而被忽略。
关于可观测性数据采样,这个主题太大,不适合在本书中完整展开。OpenTelemetry 社区已经提供了大量文献和最佳实践。各家可观测性厂商也提供了非常好的文档,介绍其 Agent 或数据管道支持哪些采样策略。有些只提供简单的按百分比采样,而有些则基于单个服务端点、工作负载或基础设施组件在日志和追踪中观察到的行为,提供高度复杂的动态采样。
如果你决定自己实现数据管道,而不使用带有内建自动采样策略的 Agent,那么请注意:采样工作就必须由你自己负责! 为此,你需要熟悉你所使用工具所支持的各种采样策略。
作为参考,请重点了解以下几种主要采样策略及其思路:
- 概率采样(Probabilistic sampling) :你定义一个采样比例(例如 5%)来保留追踪。它效率高,而且在大规模数据下,这 5% 很有可能包含常见失败或耗时较长的重要追踪。但如果失败或慢请求数量本来就很少,那么你很可能采不到这些离群点的详细数据!
- 自适应采样(Adaptive sampling) :采样率会根据进入系统的流量自动调整。这样可以避免在高峰负载期间采集过多附加信息,从而给采集端、传输网络,或者存储与分析这些数据的可观测性后端带来压力。
- 规则采样(Rule-based sampling) :你可以根据属性来配置什么重要、什么不重要,例如特定 URL、耗时超过某个阈值或失败的请求,或来自特定 IP 范围的请求。这里的挑战在于,这些规则本身也要配置和维护,并且需要在每一条追踪上执行判断。
下一个问题是:由哪个组件来决定某个数据点是否应该被采样?头部采样(head-based sampling) 在根 span(开始处)做决定,而 尾部采样(tail-based sampling) 则是在一条追踪的所有 span 都接收完成后再做决定。尾部采样之所以能做出更好的判断,是因为它能看到完整追踪,并据此分析这条追踪是否值得保留,例如它是否包含问题、错误,或者耗时特别长。但其缺点是:尾部采样需要大量内存,因为它必须先把所有 span 全部接收并分析完,才能决定是否采样。在大规模、高度分布式且高吞吐环境中,这会变得非常耗资源。相比之下,头部采样在追踪开始时就做决定,开销较低,但它只能基于追踪起始阶段已知的信息来判断。
一个很好的经验法则是:先从头部采样开始,然后对于关键交易,再结合尾部采样,以确保对失败交易能保留完整细节,或者满足某些监管与合规要求下的特定数据留存需求。
如需进一步学习,我们强烈推荐 isItObservable 的另一篇教程:《Sampling Best Practices in OpenTelemetry》[4]。
我们要管理的系统只会越来越大。正如在传统基础设施上那样,我们必须把被监控实体视为“牲畜”,而不再是“宠物”。我们需要复用现有元数据,并增强我们的可观测性信号,从而基于一套被充分理解的统一语义字典,提供一致的使用体验。我们还必须超越“只观察应用本身”的范围,也要去观察那些推动软件发生变化的工具——理想情况下,从需求定义、第一行代码写下、测试、部署,一直到软件退役,都应覆盖在内。这种高度增强的数据,将使我们能更好地把可观测性数据用于 AI 和机器学习,在日益复杂的分布式系统中,更快、更准确地获得洞察,尤其是在系统未按预期运行时。
那么,接下来让我们看看,这一切在实践中会呈现出什么样子。
AIOps:通过异常检测与根因检测降低噪声
设想一下,如果你生活在这样一个世界里:无需任何人工介入,事故分析和修复建议都可以被完全自动化,那会是什么样子?当你还在忙别的事情时,你的 AI 代理在 Slack 中对你说:
“最近一次由 Pull Request PR-342 触发的 Argo CD 同步,导致我们的 payment service 出现了高于平时的失败率。同时,我们还看到 frontend service 中出现了新的 java.lang.abcdException 日志,从而导致面向终端用户请求的 HTTP 500 错误。建议采取的行动是:回滚这个 Pull Request,以缓解问题。”
听起来像科幻小说?其实今天就可以做到。怎么实现?答案是:建立在带上下文增强的可观测性数据基础之上,结合自动基线与机器学习来检测异常,并使用能够穿越关联可观测性数据的训练模型来发现根因。下面这张截图展示了这样一种实现。
图 3.11 —— 一个问题卡片,展示了影响(1,070 名用户)和根因(高失败率 + Argo CD 同步)
最后一步,是借助 LLM 生成人类可读的说明,包含以下内容:
- 发现的问题(The finding) :影响是什么?
- 根因(The root cause) :造成这一影响的原因是什么?
- 建议(The recommendation) :我们可以采取什么措施?
图 3.12 —— LLM 对检测到的问题与根因进行解释,并给出修复建议
下面我们把其中的细节逐一拆开,让你能够判断:如果想基于你自己的信号与工具体系实现这种 AIOps 方法,需要具备哪些条件。
步骤 1:检测异常事件
我们的旅程始于跨所有相关指标检测异常行为。在“超越 CPU 和内存:云原生黄金信号”一节中,我们已经讨论过:必须从上到下审视各层指标:
- 应用层:外部 API 的四大黄金信号、并发用户数、用户行为
- 业务服务层:内部 API 的四大黄金信号、资源消耗和成本
- 平台服务层:等待时间、队列长度、吞吐量和速率限制
- 工作负载层:request limit 使用率、Pod 失败百分比、连接超时
- 编排层:Kubernetes API 健康、Pending Pod 数量和调度延迟
- 资源层:网络 Ingress/Egress,以及计算、存储和内存利用率
无论这些指标中的任何一个,是因为静态阈值被突破,还是动态基线被突破,只要我们检测到异常,就可以把它视为我们技术栈中某个特定层、某个特定组件上的异常事件。
我们用一个简单示例把它可视化。假设我们有一个面向客户的 Web 应用,它依赖一个面向外部的业务 API 来实现用户登录。这个业务 API 比平时更慢,导致终端用户在应用中的性能体验变差。这个 API 由一个运行在特定 Kubernetes 集群上的业务服务实现。该 API 依赖一个队列型平台服务,并且还会与数据库通信。所有这些服务都共享网络、内存和 CPU 等资源,因为它们都部署在同一组共享硬件之上。下图展示了我们如何在一个热力图仪表板中可视化这种情况。我们把整个技术栈上的关键指标放在 y 轴上,把每一分钟的指标状态放在 x 轴上。状态用绿色=良好、黄色=降级、红色=失败来表示。这样的可视化能让人眼很容易地看出若干模式:
图 3.13 —— 针对特定应用和 API 端点、跨全栈各层的简化版可观测性热力图
观察这张热力图,可以得到如下结论:
- 在时间片 4,Pending Pod 出现了问题,但它并没有影响业务层或应用层。
- 数据库查询时间一旦退化,业务 API 性能总会在一分钟之后跟着退化。
- 当数据库影响持续时间较短时,业务 API 性能会恢复;但在时间片 13,我们看到数据库查询时间较长时间处于退化状态后,业务 API 开始失败。
- 在时间片 14,当业务 API 开始失败后,应用层也开始跟着失败。
当我们按时间维度可视化关键指标上的异常行为时,效果是最好的。把这些信息按照架构组织进仪表板后,人眼就能相当轻松地识别模式。在上面的例子里,看起来问题似乎与数据库查询时间有关,它影响了业务 API,进而影响了最终用户在应用上的性能体验。但这种方式有几个局限,我们接下来会讨论。
范围(Scope)
这是一个极度简化的例子,因为我们只看了一小部分指标,并且范围只限定在一个非常具体的 API 端点上。在真实世界中,我们可能面对数百个应用、成千上万个 API 端点,而这些端点又共同依赖大量共享平台服务。因此,现代可观测性需要有能力分析数百万个不同数据点。真正的挑战在于:不要因为误报、错误假设,或那些并不会造成业务影响的异常而制造大量噪声。
依赖关系(Dependencies)
说到错误假设,再看看上面的例子:我们假设业务 API 是受数据库查询时间影响的。但在分布式系统中,实际上还存在更多依赖关系需要分析。数据库查询时间变慢,和业务 API 同时变慢,也可能只是巧合。我们并没有检查其他因素。也许是业务 API 查询了更多数据库数据,导致数据库服务响应变慢;而返回更多数据又意味着需要占用更多内存,从而增加 GC 压力,最终造成性能下降。正如你所看到的,需要调查的数据点太多了,根本无法轻松塞进像图 3.13 这样的表格中。
共享资源(Shared resources)
业务 API 与同一集群上的其他服务共享资源。我们还没有检查那些可能影响业务 API 性能的“噪声邻居(noisy neighbors)”。网络流量也由运行在共享基础设施上的所有组件共同承担。我们还没有调查网络延迟是否影响了我们的服务、数据库服务,或其他依赖组件。依赖关系和共享服务这两类信息,都可以从前面讨论过的带上下文增强的元数据中提取出来。此外,我们还可以利用分布式追踪来查看从一个服务到其他服务的事务依赖关系。所有这些数据都必须被纳入考虑,以提高告警准确性。这也有助于降低噪声,因为我们可以把那些已知源于依赖系统的问题异常聚合在一起。
外部事件(External events)
到目前为止,我们只看了技术栈内部各层的指标。而在“跨 SDLC 增强可观测性数据”一节中,我们已经讨论过:把交付或配置变更工具中的事件摄入进来也非常重要。我们原本可能忽略了一次请求与资源限制的配置变更:例如,为了给另一个 API 更多 CPU 和内存,调整了业务 API 的资源限制。这种改动可能导致业务 API 出现 CPU throttling,从而性能下降。
所有这些的重点在于:我们必须在全栈范围内检测异常。然而,仅有异常本身是不够的;如果这些异常没有被增强依赖关系、共享资源和外部事件信息,那么我们所能做的依旧只是猜测。正如上一节所讲,我们需要所有这些带上下文增强的数据,以便拥有完整证据。只有这样,我们才能真正扩展可观测性,并将其输入到机器学习或各种 AI 系统中,去连接所有这些点,找出异常行为背后的真实根因。
步骤 2:连接所有线索,找出根因
在这一步,我们解释如何沿着可观测性依赖模型进行分析。
在第 1 章“可观测性与分布式系统”一节中,我们已经讨论过:理解垂直栈和水平栈上的依赖关系,能够帮助我们快速定位那些影响终端用户、API 消费者或 SLA 的问题的技术根因。
沿用第 1 章中的同一个可视化思路,并把它应用到上一节的用例中,就更容易看清依赖关系、共享资源带来的交叉影响,以及外部事件。
图 3.14 —— 找到根因,需要沿全栈把所有异常事件连接起来,并同时考虑依赖关系和共享服务
上图突出了一个事实:现代可观测性之所以更有效,正是因为有了带上下文增强的可观测性数据。由于日志、指标、追踪和事件都带有元数据,我们就能更好地分析垂直栈和水平栈中的因果关系。由于我们能够大规模摄入和分析海量数据,我们也不再局限于只看一个应用。相反,我们可以,也必须跨越所有应用和服务,去检测各种交叉依赖。下面我们把它拆开来看。
水平栈,或调用链(Horizontal stack, or call chain)
哪个服务调用了哪些其他服务?被调用方的响应(延迟、失败率和吞吐量)又是如何影响调用方的?例如,我们可以学习数据库查询时间或返回的数据行数,是如何影响业务 API 调用性能的。
垂直栈,或“运行在……之上”的关系(Vertical stack, or runs on another component)
哪个组件运行在另一个组件之上或内部?这种关系又如何反过来影响该组件本身?例如,我们可以学习 Java 运行时的 GC 如何影响业务 API 的性能行为,而 GC 本身又是如何受到编排层施加的资源与请求限制的影响。
网络连通性(Network connectivity)
各组件分布在本地网络、远程网络和云网络中的方式,会受到什么影响?我们可以学习网络质量如何影响业务服务与平台服务之间建立的点对点连接。
跨应用或跨技术栈(Cross-application or cross-stack)
图 3.14 还引入了一个新角色:另一个与该业务线应用相关的应用,或者说另一套技术栈。如果我们只聚焦于我们已知的那部分信息,而不去观察其周边系统,我们可能永远找不到真正的根因。我们也许会把问题归咎于数据库,但数据库本身可能正是被其他服务施加了过多压力才出问题的。
借助这些新的上下文信息——包括垂直依赖、水平依赖、网络关系以及跨栈依赖——我们大概率会先把问题定位到数据库,但我们还能高置信度地指出:真正的根因其实是一个定时触发的、由内部批处理任务驱动的 reporting service。这个任务同样在给数据库施压,从而使数据库对业务 API 的响应变慢,最终把问题传导给终端用户。
最后一点:虽然我们可以通过可观测性把所有这些信息采集到手,但怎样才能让它变得更容易理解呢?
步骤 3:解释所有证据
并不是每个人都能持续不断地画出组件之间如何关联,以及异常事件的证据链如何一步步导向已知问题的根因。
这正是自动化可以帮忙的地方。步骤 2“连接所有线索,找出根因”中所解释的一切,其实都可以自动化。许多可观测性平台今天已经可以做到这一点:借助元数据,它们把所有可观测性数据连接起来,然后像我们刚才那样沿着依赖树进行分析。有的平台做得比其他平台更高效,因为这在很大程度上取决于数据质量,以及可观测性数据被存储、访问、连接和分析的效率。
无论你决定用何种方式实现异常检测与根因检测,有一个关键步骤会决定这些数据是否真正可执行,并确保用户能看到价值:那就是,能够把这些数据转化为一种可解释的形式,让人类无需成为专家也能轻松理解。这会是什么样子呢?
请看图 3.15。它展示了一个 JavaScript 错误率升高问题的证据链,其中包含依赖信息、检测到的异常列表,以及被判断为很可能与根因分析相关的外部事件。当我们把 LLM 放在这些数据之上时,就能得到对问题、其根因以及建议措施的一段良好解释:
图 3.15 —— 使用 LLM 来解释检测到的问题细节,甚至还能收到建议措施
现在我们已经看到:现代可观测性如何通过基线建模、机器学习与 AI 的结合真正发挥作用。接下来,是时候讨论一个 AI 如果没有我们的帮助就无法解决的挑战了:如何提供业务上下文,以便更好地判断哪些异常比其他异常更重要,因为如果不够快地做出反应,它们将会影响你的 SLO。
从运维走向业务:基于 SLO 的影响分析
想象一下,我们是某个组织里的可靠性工程师,而我们团队的运维现实是这样的:
“自从我们在所有应用和技术栈中上线新的可观测性平台后,每天都会收到数百个事故。团队已经不堪重负,并开始质疑这个新平台的价值。看起来我们对所有事情都在收到通知,但却不知道哪些才是真正与业务相关的。请解决这个问题。”
正如上一节所解释的,异常检测做的事,正如它名字所表达的那样:检测异常行为。这可能是 Apache Kafka 中队列等待时间增加,也可能是某个 API 端点 HTTP 500 响应码增加,或者来自某个 IP 地址段的请求数下降。在拥有数百甚至数千个应用的大型企业中,即使已经采用了本书到目前为止介绍的所有最佳实践,每周仍然很常见会检测到数百乃至数千个异常。
问题是:我们到底应该关注哪些问题?
该问的正确问题!
客户是否受到了影响?我们是不是在亏钱?我们是否违反了 SLA,并可能因此被罚款?这些才是 IT 运维和 SRE 团队需要问的问题,借此判断哪些问题必须立刻处理,哪些问题则可以暂时放一放。
与其在每次检测到问题之后再去回答这些影响分析问题,不如借助 SLO 的概念,事先定义我们自己——或者终端用户——对系统的期望。一旦我们定义了这些 SLO,例如核心业务 API 端点可用性达到 99.999%,那么我们就可以根据某个异常是否影响 SLO 来给它排序优先级。
从技术目标走向业务目标
正如我们已经讨论过的,现代云原生可观测性必须超越只看 CPU 和内存这类基础设施告警;同样,我们对 SLO 的理解也必须从原始定义扩展出去,不再只看基础技术指标,而是扩展到四大黄金信号:可用性、吞吐量、延迟和资源消耗。现代可观测性还必须观察业务信号,例如活跃用户数、下单数、理赔处理耗时,或面临风险的收入。
在这一节最后的部分,我们会更深入解释:为什么在一开始,你很可能会淹没在事故之中。然后你将学习 SLO 的基本概念,了解如何从技术目标扩展到业务目标,以及如何在事故响应过程中利用这些目标,把关注点放在那些真正有潜在业务影响的异常上。
为什么我们仍会淹没在事故中
如果你需要为“为什么你的可观测性实施产生了比预期更多的事故”做辩护,那么下面这些内容足以解释原因,因为这是我们在很多项目中反复见过的现象。
观察范围扩大(Expanded scope)
很多组织在迁移到新的可观测性平台时,往往会把它当作一个机会,顺势把可观测性覆盖到更多技术栈层级和更多应用中。因此,突然之间就会有更多数据可用。而更多数据也意味着检测到更多异常,于是就会在初期带来更多事故噪声。
工具整合(Tool consolidation)
迁移到新的可观测性平台,通常意味着替换掉一个或多个原先用于特定应用或特定技术层的点状工具。这些工具很多时候原本并不会自动发告警,只是在发生较大问题时用于排障。如今,这些额外数据也被纳入异常检测中,因此会增加初期事故噪声。
端到端可观测性中的缺口(Gaps in end-to-end observability)
在上一节中,我们已经展示了端到端可观测性的重要性。还记得那个被后台批处理任务拖垮、进而影响业务应用的数据库吗?如果我们不知道数据库同时被这两个应用使用,那么我们的异常检测可能就会产生三个事故,而不是一个:业务应用一个、批处理任务一个、数据库自身一个。因为系统无法把它们全部串联起来并视为同一个异常。
同样的问题,往往也会发生在组织刚开始推广现代可观测性的时候。由于系统尚未完全落地,他们会存在一些盲区或可观测性缺口。而这些缺口,就会带来初期事故噪声。
缺乏归属关系(Missing ownership)
理想状态下,事故应当被自动分配给真正需要采取行动进行修复的团队。但一个组件到底由谁负责?在“上下文为王:可观测性数据的质量”一节中,我们讨论过:要为数据补充“归属关系”信息,以便知道哪个团队拥有哪个应用、服务或集群。在许多组织里,这些信息一开始并不存在,或者至少对可观测性平台来说并不容易获取。在这种情况下,所有新检测到的事故都会被分配给一个兜底团队,而这个团队通常就是 IT 运维或 SRE 团队。你可以想象,当这些团队突然收到远多于以往的事故时,会有多么不堪重负。因此,明确归属关系,是降低事故噪声的关键。
缺乏关键性与业务影响信息(Lack of criticality and business impact)
并不是每个异常都同等重要。但很多时候,如果我们没有关于受影响系统高低关键性的判断,也没有关于潜在业务影响的信息,那么所有检测到的异常都会被一视同仁地处理。而一旦拥有这些信息,就可以对检测到的问题进行自动过滤和路由。例如,低关键性的异常——比如某个内部报表服务变慢——可以进入一个较低优先级的事故响应流程;而那些影响业务的问题——比如用户无法登录——则应被升级处理,因为这可能会对声誉和收入造成极大负面影响。
上下文再次证明了它的核心地位。一个最佳实践是:为每个组件打上关键性标签,例如 high、medium 或 low。这样,一旦检测到新的异常,我们就可以分析受影响组件的关键性,并据此驱动事故响应流程;例如,把受影响组件中最高的关键级别作为自动创建事故工单时的默认优先级。
但业务影响呢?我们能不能比简单的“低、中、高”更精细地量化它?有没有办法把系统每多受影响一分钟所带来的风险或潜在收入损失也纳入考量?
答案是:可以! 这正是 SLO 和 SLA 登场的地方。SLO 允许我们定义系统在一段时间内应达到的预期行为,并据此衡量我们偏离预期的程度,例如 30 天内 99% 的可用性,或者 95% 的关键 API 响应时间必须快于 500 毫秒。那么 SLA 是什么?它和 SLO 很像,但通常被写进合同里,因为它定义了如果我们未达到目标会发生什么。一个典型例子就是:若未能兑现承诺的可用性,就必须支付罚金。比如银行,它们必须保证某种等级的可用性;如果无法达到,不仅可能要支付罚款,甚至还可能失去银行牌照。
关于 SLA 和 SLO,有大量文献可供参考。我们强烈推荐阅读 Google 的 SRE 手册,它对 SLI、SLO、SLA、错误预算和预算消耗速率等核心原则做了极好的解释[5]。
在接下来的内容中,我们只会覆盖 SLO 的基础知识,但我们会解释:SLO 如何帮助我们把影响分析从纯技术视角(可用吗?够快吗?)提升到可以回答这样的问题:它是否足够关键,以至于正在对我们的业务目标造成负面影响?
SLO 入门:来自 Google SRE 手册的启发
关于 SLO,我们不需要再写一本新书,因为现有材料已经非常丰富。围绕如何定义和计算 SLO,也已经有很多不同的工具厂商方案,甚至还有开放标准倡议。因此,我们建议你自行深入阅读相关资料。
这里我们最希望你带走的一条最重要原则是:你不需要为所有东西都定义 SLO! 这话听起来也许有点刺耳,但我们确实见过一些组织尝试在所有应用中定义 10,000+ 个不同的 SLO。他们对读到的一些内容理解得过于字面化,于是为组织中每个服务端点上的四大黄金信号都配置了自定义阈值和告警。
下面我们快速回顾一下 SLO 的基础概念,确保我们在谈论 SLI、SLO、SLA、错误预算和预算消耗速率时,彼此理解一致。
我们用“某个 API 端点请求成功率的 SLO 定义”为例。
服务级指标(Service-level indicator)
SLI 是一个用来理解目标健康状况的指标。SLI 通常是一个比率型指标,取值范围在 0(失败)到 100(通过)之间。在大多数场景中,我们会通过“好事件”(例如成功的 API 调用)除以“所有有效请求”(例如所有被测量的 API 调用),来计算这个比例。下面是一个完整示例:定义请求成功率的 SLI,用返回状态码为 200 的 HTTP 请求总数,除以请求总数:
图 3.16 —— SLI 是一个用来理解组件健康状况的指标
理想情况下,我们会把 SLI 归一化到 0 到 100 的范围内,这会让后面定义 SLO 时更容易。
服务级目标(Service-level objective)
在 SLI 的基础上,我们就可以定义某个指标在特定时间窗口内应达到的目标值。比如,在我们的示例中,我们可以规定:某段观察周期内,请求成功率必须达到 99.98%。也就是说,我们希望确保 99.98% 的请求都返回 HTTP 200 状态码,而只允许 0.02% 的请求返回非 200 值。
图 3.17 —— SLO 定义了某个指标在一段时间内应达到的目标
SLO 本质上定义的就是 SLI 的阈值——或者说,定义了它的期望值。除了单一阈值外,我们也经常看到同时设置 warning 阈值和 error 阈值,用来更细粒度地区分,不只是简单的成功或失败。
错误预算与预算消耗速率(Error budget and burndown rate)
一旦有了 SLI 和 SLO 的阈值,我们就能计算在不违反既定目标的前提下,系统还能容忍多少错误。这就是所谓的错误预算(error budget) 。它通常也被简单表示为 100 – SLO。换句话说,它表示在多大比例的时间里,系统允许“不工作”而仍不算违反 SLO。下图展示了一个简单计算示例:如果 SLO 阈值是 99.9%,那就意味着在一年的时间里,我们允许有 0.1% 的时间系统不可用,或者没有达到应有速度(具体取决于 SLI 衡量的是什么)。
图 3.18 —— 错误预算表示系统允许在多长时间内未达到目标
当你真正看这些数字时,往往会意识到 99.9% 可用性目标有多严格。它实际上意味着,一年时间里,你只有 43.2 分钟可以不满足 SLO。再往上多加几个“9”,例如 99.999%,这个容忍时间就会迅速缩短。
那错误预算有什么用?它能帮助我们在做决策时判断自己还能承担多少风险,例如是否还能部署某个可能短期影响 SLO 的变更。通过查看剩余错误预算,我们就可以权衡:一方面是推动变更的愿望,另一方面是违反 SLO 的潜在风险。
虽然错误预算本身是很好的指标和报表工具,但真正更实用的是错误预算消耗速率(burndown rate / burn rate) 。当我们消耗剩余错误预算过快时,它是一个非常好的告警工具。
图 3.19 —— 错误预算消耗速率告诉我们,按当前趋势,是否会在统计周期结束前违反 SLO
预算消耗速率的核心思想是:它让你不仅能在周期结束时发现“未达到目标”,还能在你消耗错误预算过快时提前收到告警。例如,如果 burn rate 太陡峭,你就能迅速对一个正在吞噬错误预算的关键问题采取行动。
图 3.20 —— 预算消耗总量和 burn rate 都是判断何时应对问题做出反应的良好指标
如果你过去研究过这个领域,可能会想:“这听起来和 SLA 很像啊。”没错。那么,两者到底有什么区别?
服务级协议(Service-level agreement)
从技术上说,SLA 和 SLO 很相似,因为它们都用来衡量系统在某个时间段内的健康状况。一个经典例子就是:向终端消费者承诺系统 99.999% 的可用性。我们完全可以像计算 SLI 那样,通过分析系统可用和不可用的比例来衡量它。
那么区别是什么?SLA 通常写进合同里。 例如,当你订阅任何 SaaS 服务时,通常都会在合同中看到一个关于可用性的 SLA,甚至可能还会对性能作出约定。我们也常见到与问题或事故响应速度相关的 SLA;例如,作为我们 SaaS 服务的高级客户,我们保证在 95% 的关键问题场景下,会在一小时内做出响应。
相比之下,SLO 通常用于组织内部,作为更细粒度的前置信号。 它们往往是一些领先指标:一旦被违反,如果我们不及时采取行动,就极有可能进一步演化成 SLA 违约。还是以可用性 SLA 为例。我们可以在那些用户访问服务时所依赖的关键 API 上定义多个 SLO。这些 API 必须可用,系统才会被用户感知为“可用”。因此,我们通常会定义一些更技术化、也更严格的 SLO,把它们作为早期预警信号。
从 SLO 走向业务目标
可用性、响应时间和错误率,是登录服务、购买服务、推荐服务这类技术服务中非常常见的 SLO。等待时间、消息处理时长和消息投递成功率,则是我们在分布式架构中常见的队列系统的典型 SLO。虽然这些 SLO 对技术服务负责人来说非常有价值,但它们并不能回答一个关键问题:我们的服务变慢,或者队列积压,是否正在影响终端用户,从而影响业务?
提出关于业务影响的问题
要把讨论真正提升到“哪些指标对业务重要”的层面,我们在定义 SLO 时就需要采取一种**自上而下(top-down)而非自下而上(bottom-up)**的方法。以一家金融服务公司推出新移动 App 为例。从业务角度看,最重要的指标并不是后台队列每秒能处理多少消息,而是下面这些问题:
- 有多少用户正在使用这款新 App?
- 有多少用户能够成功登录?
- 有多少用户能够通过 App 完成转账或购买其他金融产品?
当我们逐步走向技术侧时,问题可能会变成:
- 这个移动 App 在应用商店里的评分是多少?
- 这个 App 的崩溃率有多高?它是否阻碍了用户与公司完成业务交互?
这些才是应当被定义的问题。利用可观测性去采集这些指标,再应用 SLO 的概念去报告和告警:当我们无法达到业务目标时,问题就会被显性化。
把业务目标与技术目标连接起来
下图出自 SLOconf 2022 上的一场演讲《Tips for running successful SLO workshops in under one hour》[6]。在这场演讲中,讲者解释了一种自顶向下定义 SLO 的方法:从最顶层的业务目标开始,再逐层拆解到技术实现中的前置指标目标。
图 3.21 —— 好的 SLO 应从业务目标出发,并向下映射到支撑实现的领先技术指标
从这场 SLOconf 演讲中,可以提炼出若干关于 SLO 设置的明确结论与最佳实践:
- 定义清晰(Clearly defined) :对目标以及它为何重要,要有明确说明。
- 可测量(Measurable) :你如何测量这个目标——通过指标、日志,还是其他方式?
- 目标现实(Realistic objective) :大家都认可、并且确实能够实现的阈值是什么?
- 明确归属(Ownership) :谁拥有这个目标?当它被违反时,谁来负责处理?
- 与业务对齐(Aligned with business) :底层技术 SLO 如何映射到顶层业务目标?
关于这一主题的更多内容,也建议阅读博客文章《How to start with SLOs to align Business, DevOps, and SREs》[7]。
将业务影响分析纳入事故响应
一旦我们拥有了 SLO,就可以把业务影响分析纳入 AIOps 实现。在每一个检测到的问题上,我们都可以进一步判断:受影响的组件里,哪些被分配了 SLO,哪些没有。假设带有 SLO 的组件更关键,那么我们就可以依据它来决定哪些异常应在事故响应中获得更高优先级。
没有 SLO 上下文时的事故分析
在观察真实系统时,我们往往会面对大量不同的问题。下图展示了如何按问题领域对所有问题进行汇总概览。
图 3.22 —— 对问题领域进行概览,可以识别热点,并为基于 SLO 优化问题优先级提供基线
这看起来告警噪声非常大,而且大部分问题似乎都与资源争用有关,而真正与可用性、性能或错误相关的问题只占极少数。这已经是一个很强的信号,说明大量检测到的异常其实未必会影响业务层面的目标。
将 SLO 与事故连接起来
现在,如果我们能够把这些检测到的异常和 SLO 关联起来,就可以非常容易地按“影响 SLO”和“不影响 SLO”来筛选问题。下图展示了两个检测到的问题,并清楚地说明了哪一个应该被赋予更高优先级,哪一个则应被降级处理。
图 3.23 —— 对 SLO 的影响和受影响用户数,有助于设置事故响应优先级
我们见过一些组织,仅仅通过聚焦那些会影响其目标的问题,就把事故噪声降低了高达 90%。
如何开启这段旅程
大多数组织还没有形成良好的实践,去同时定义技术目标和业务目标。你应该开始追踪这样一个指标:事故中有多少经过了基于 SLO 的优先级评估。这会为组织提供清晰指引,推动 SLO 的定义,因为它能直接改善事故响应流程。如果你持续追踪:检测到了多少问题,其中有多少之所以不具备业务相关性,是因为它们没有定义 SLO,那么这就会清楚地暴露出你应重点补齐哪些高质量 SLO。
这会是一段漫长的旅程,但非常值得开始。因为它最终会把团队的注意力拉回到那些真正影响业务的问题上,而不是被那些无关紧要的告警淹没,从而蚕食本应用于创新的时间。
Financial One ACME 将如何完成这场转型?
和前几章一样,我们会通过一个虚构公司 Financial One ACME 的转型旅程,把这些内容具体化。他们是如何从“到处是数据和大量静态告警”的状态,走向“按层分级、基于健康检查并使用基线建模”的方法?他们如何向左扩展,并建立起可观测性驱动开发文化?他们决定向可观测性数据中加入哪些上下文,以支持更多自助式用例?他们又是如何定义业务目标与技术目标,从而使 AIOps 更高效?
答案将在接下来的章节中揭晓!
小结
本章中,我们解释了要把可观测性提升到新的层级,需要经历哪些转型步骤,才能让本章开头那句话也成为你的现实:
“我们以前有 1,000 个仪表板。现在已经减少到 50 个了!”
我们讨论了为什么必须为可观测性数据补充更多上下文,为什么需要理解云原生架构每一层的健康指标,以及如何从静态阈值过渡到动态基线建模。我们也学习到:一旦拥有了这些数据,就可以利用简单或高级算法,通过把关联到同一问题的异常行为聚合起来,降低告警噪声。
最后,我们讨论了为什么必须采用一种自顶向下(从业务出发) ,而不是**自底向上(从技术出发)**的方法,来根据问题对业务的真实影响,对检测到的问题进行优先级排序。
在第 4 章中,我们将正式引入 ACME Financial Services,了解他们是谁、他们从哪里来、他们面临哪些挑战、他们如何推动 IT 版图现代化,以及这对其可观测性转型意味着什么。
延伸阅读
[1]:AWS 标签最佳实践
docs.aws.amazon.com/whitepapers…
[2]:Semantic Dictionary
docs.dynatrace.com/docs/discov…
[3]:isItObservable GitHub 教程
github.com/isItObserva…
[4]:追踪采样最佳实践
isitobservable.io/open-teleme…
[5]:Google SRE 手册中的 SLO 入门
sre.google/sre-book/in…
[6]:关于如何开展 SLO workshop 的 SLOConf 演讲:YouTube
www.youtube.com/watch?v=XwC…
[7]:如何通过 SLO 让 Business、DevOps 和 SRE 对齐
www.dynatrace.com/news/blog/h…