深入浅出边缘云 | 6. 监控与遥测
随着技术的发展以及应用对时延、带宽、安全的追求,一个明显的技术趋势是越来越多的应用组件将会被部署到企业所管理的网络边缘。本系列是开源电子书Edge Cloud Operations: A Systems Approach的中文版,详细介绍了基于开源组件构建的边缘云的架构、功能及具体实现。
第 6 章 监控与遥测
运行系统的遥测数据采集是管理平台的重要功能,从而使运维人员能够监控系统行为、评估性能、做出明智的配置决策、响应故障、识别攻击和诊断问题。本章主要讨论三种类型的遥测数据: 指标(metrics) 、日志(logs) 和跟踪(traces) ,以及用于帮助收集、存储并对每种数据采取行动的开源软件栈。
指标(Metrics)是关于系统的定量数据,包括常见的性能指标,如链路带宽、CPU 利用率和内存使用情况,还包括与上下层对应的二进制结果,以及其他可以用数字编码的状态变量。系统周期性的通过读取计数器或者返回值产生和收集(例如,每隔几秒)这些指标。这些指标可以与物理资源(如服务器和交换机)、虚拟资源(如虚拟机和容器)或高级抽象(如 5.3 节中介绍的 Connectivity Service)相关联。对于可能的数据源,指标监视堆栈的工作是收集、归档、可视化和分析这些数据。
日志(Logs)是定性数据,当发生值得注意的事件时生成,可用于确定有问题的操作条件(例如可能会触发告警的操作),但更常见的是用于在检测到问题后进行故障排除。各种系统组件(从底层操作系统内核到高级云服务)向日志系统写入遵循良好定义格式的消息。这些消息包含时间戳,使得日志记录堆栈能够解析和关联来自不同组件的消息。
跟踪(Traces)是由用户发起的事务或作业产生的因果关系记录(例如,服务 A 调用服务 B)。它们与日志相关,但提供了关于不同事件发生的上下文的更专门的信息。跟踪在单个程序中很容易理解,其中运行跟踪通常被记录为内存中的调用栈,但跟踪本质上体现的是分布在云上的微服务网络连接图。这使得问题具有挑战性,但也非常重要,因为通常情况下,理解时间依赖现象(例如为什么某个特定资源超载)的唯一方法是理解多个独立工作流彼此如何交互。
退一步看这三种遥测数据,有助于进一步理解设计空间,为此,我们做了四个观察。
首先,遥测数据通常有两个使用场景,大体上可以分为"监控"和"故障排除"。我们以最一般的术语来表示:(a)在一个状态稳定的系统中主动观察问题的告警信号(攻击、错误、故障、过载情况); 与(b)一旦出现潜在问题的告警,仔细观察以确定根本原因并解决问题(修复错误、优化性能、提供更多资源、抵御攻击)。这种区分很重要,因为前者(监控)需要保证最小的开销,最少的人力参与,而后者(故障排除)可能更具侵入性/更昂贵,通常涉及某种程度的人类专业知识。这不是一个完美的区分,大量运维活动发生在中间灰色区域,但了解可用工具的成本/效益权衡是一个重要的起点。
其次,监控和故障排除约自动化越好。首先是自动检测潜在问题的告警,通常包含仪表板,使人们很容易看到模式,并在所有三种数据类型中钻取相关细节。现在越来越多的利用机器学习和统计分析来识别对人类操作者不明显的更深层次的连接,并最终支持闭环控制,其中自动化工具不仅能够检测问题,而且能够发出纠正错误的控制指令。基于本章目的,我们给出了前两个(告警和仪表板)的例子,并声明后两个(分析和闭环控制)超出了本书范围(但可能作为后续章节概述的遥测数据的应用程序运行)。
第三,从生命周期管理的角度来看,监控和故障排除只是测试的延续,区别只在于是工作在生产负载而不是测试负载下。事实上,同一套工具可以用在开发与生产边界的任何一边。例如,任何一个对程序进行过剖析的人都会认识到,在开发过程中,跟踪是一个非常有价值的工具,既可以追踪错误,又可以调优性能。同样,人工端到端测试可以通过触发早期预警警报为生产系统提供价值,在处理有问题的故障模式时,这可能特别有帮助。
最后,由于各个子系统收集的指标、日志和跟踪都有时间戳,因此可以在它们之间建立相关性,这在调试问题或决定是否需要发出告警时很有帮助。在本章的最后两节中,我们将举例说明这种遥测范围的功能在今天的实践中是如何实现的,并讨论产生和使用遥测数据的未来。
6.1 指标和告警
我们从指标开始,流行的开源监控堆栈使用 Prometheus 来收集和存储平台和服务指标,Grafana 用来可视化指标,Alertmanager 用来通知运维团队需要注意的事件。在 Aether 中,Prometheus 和 Alertmanager 被实例化在每个边缘集群上,Grafana 的单一实例在云中集中运行。网上可以找到每个工具的更多信息,所以我们只关注(1)单个 Aether 组件如何适配这一堆栈,以及(2)这个堆栈如何以特定方式对服务进行定制。
延伸阅读:
6.1.1 导出指标
各个组件通过实现 Prometheus 导出器(Prometheus Exporter) 来提供组件指标的当前值。通过 HTTP 查询组件的 Exporter,使用简单的文本格式返回相应的指标。Prometheus 定期抓取 Exporter 的 HTTP 端点,并将指标存储在其时间序列数据库(TSDB, Time Series Database)中,以便进行查询和分析。许多客户端库都可以用于调用代码,以生成 Prometheus 格式的指标。如果组件指标以其他格式输出,那么通常可以使用工具将其转换为 Prometheus 格式并导出。
YAML 配置文件指定了 Prometheus 要从中提取指标的一组 Exporter 端点,以及每个端点的轮询频率。另外,基于 Kubernetes 的微服务可以通过服务监视器(Service Monitor) 自定义资源描述符(Custom Resource Descriptor, CRD)进行扩展,然后 Prometheus 查询该描述符以了解微服务提供的任何 Exporter 端点。
除了基于组件的 Exporter 外,每个边缘集群还定期测试端到端连接(针对各种端到端定义)。一个测试是判断 5G 控制平面是否工作(即边缘站点是否可以连接运行在中心云中的 SD-Core),另一个测试是判断 5G 用户平面是否工作(即终端是否可以连接互联网)。这是一种常见模式,即单个组件可以导出累加器和其他本地变量,但只有"第三方观察者"才能主动测试外部行为,并报告结果。这些例子对应于第 4 章图 19 中最右边的"端到端测试"。
最后,当一个系统在多个边缘站点上运行时,就像 Aether 的情况一样,有一个设计问题,即监测数据是存储在边缘站点上,只在需要时才慢慢拉到中央位置,还是在产生后立即主动推送到中央位置。Aether 同时采用了这两种方法,取决于所收集数据的数量和紧迫性。默认情况下,由 Prometheus 本地实例收集的指标留在边缘站点上,只有查询结果被返回到中央位置(例如,由 Grafana 显示,如下一小节所述),适用于高流量和很少查看的指标。一个例外是上面提到的端到端测试,结果被立即推送到中央站点(绕过本地 Prometheus),因为数量不多,而且可能需要立即关注。
6.1.2 创建仪表板
Prometheus 收集的指标可以用来可视化 Grafana 仪表板。在 Aether 中,意味着在中心云中作为 AMP 的一部分运行 Grafana 实例将查询发送到中心 Prometheus 实例的某个组合以及运行在边缘集群上的 Prometheus 实例的一个子集上。例如,图 25 显示了 Aether 边缘站点集合的摘要仪表板。
图 25. 显示 Aether 边缘部署状态的中央仪表板。
Grafana 为最常见的指标集提供了一套预定义的仪表板,特别是与物理服务器和虚拟资源(如容器)相关的仪表板,但也可以进行定制包含服务级指标和其他特定部署信息的仪表板(例如,Aether 中的每个企业)。例如,图 26 显示了 UPF(用户平面功能)的自定义仪表盘,这是 SD-Core 的数据平面包转发器。该例子显示了站点过去一小时的延迟和抖动指标,底部还有三个折叠的面板(PFCP 会话和消息)。
图 26. 自定义仪表板,显示 SD-Core 组件转发数据平面 UPF 的时延和抖动指标。
简单来说,仪表板由一组面板(panels) 构建而成,其中每个面板都有定义良好的类型(例如,图 graph、表 table、测量 gauge、热图 heatmap),绑定到某个特定的 Prometheus 查询。使用 Grafana GUI 创建新的仪表板,然后将生成的配置保存在 JSON 文件中。然后,这个配置文件被提交到配置存储库,当它作为生命周期管理的一部分重新启动时,加载到 Grafana 中。例如,下面的代码片段显示了与图 25 中的Uptime
面板相对应的 Prometheus 查询。
注意,这个表达式包括站点($edge
)和计算 uptime 的时间间隔($__interval
)的变量。
6.1.3 定义告警
当组件指标超过某个阈值时,在 Prometheus 中可以触发告警,通过 Alertmanager 这一工具,可以将告警发送到一个或多个收件人的电子邮件地址或 Slack 频道。
通过定义告警规则(alerting rule) 可以针对特定组件定义告警,该规则调用 Prometheus 查询表达式,每当指定时间段内计算为 true 时,就会触发一条相应的消息,并将其路由到一组接收者。这些规则记录在 YAML 文件中,该文件签入配置存储库中并被加载到 Prometheus(或者独立组件的 Helm Charts 可以通过 Prometheus Rule 自定义资源定义规则)。例如,下面的代码片段显示了两个告警的 Prometheus 规则,其中expr
行对应于提交给 Prometheus 的相应查询。
在 Aether 中,Alertmanager 被配置为向一组公共接收者发送具有关键(critical) 或警告(warning) 级别的告警。如果需要将特定告警路由到不同的接收端(例如,开发人员为特定组件使用的 Slack 频道),需要相应改变 Alertmanager 的配置。
6.2 日志
从 Unix 早期开始,操作系统程序员就一直在向 syslog 写入诊断信息。最初收集在本地文件中,如今 syslog 抽象已经通过添加可扩展的服务适应了云环境。今天,典型的开源日志堆栈使用 Fluentd 来收集(聚合、缓冲和路由)由一组组件编写的日志消息,Fluentbit 作为客户端代理运行在每个组件中,帮助开发人员规范日志消息。然后,ElasticSearch 被用来存储、搜索和分析这些消息,Kibana 被用来显示和可视化结果。图 27 显示了一般的数据流,使用 Aether 的主要子系统作为日志消息的说明性来源。
图 27. 日志子系统的日志消息流。
延伸阅读:
6.2.1 通用模式
日志的关键挑战是在所有组件中采用统一的消息格式,由于集成在一个复杂系统中的各种组件往往是独立开发的,因此这一要求变得复杂起来。Fluentbit 通过支持一组过滤器在规范这些消息方面发挥了作用。这些过滤器解析由组件编写的"原始"日志信息(ASCII 字符串),并将"规范的"日志信息输出为结构化的 JSON。虽然还有其他选择,但 JSON 作为文本是合理可读的,这对于人类调试仍然很重要,同时也得到了工具的良好支持。
例如,SD-Fabric 组件的开发人员可能会写这样的日志消息:
Fluentbit 过滤器将其转换成如下结构:
这是一个简化的例子,但确实有助于说明基本的想法,同时还强调了 DevOps 团队在建立管理平台时面临的挑战,即为整个系统决定一套有意义的名/值对。换句话说,必须为这些结构化的日志信息定义通用模式。Elastic 通用模式(Elastic Common Schema) 是一个很好的开始,除此之外,还有必要建立一套公认的日志级别,以及使用每个级别的惯例。例如,在 Aether 中,日志级别是: FATAL, ERROR, WARNING, INFO, 以及 DEBUG。
延伸阅读:
6.2.2 最佳实践
当然,建立一个共享日志平台没有什么价值,除非所有单独的组件都被正确的检测以写入日志消息。编程语言通常带有编写日志消息的库支持(例如 Java 的 log4j),但这只是一个开始。只有当组件遵循以下最佳实践时,日志才是最有效的。
平台处理日志传送。组件应该假设 stdout/stderr 被 Fluentbit(或类似工具)吸收到日志记录系统中,并避免通过尝试路由自己的日志使工作变得更复杂。除了管理平台无法控制的外部服务和硬件设备,在部署过程中,必须确定这些系统如何将日志发送到日志聚合器。
应该禁用文件日志。将日志文件直接写入容器的分层文件系统被证明是 I/O 效率低下的,并可能成为性能瓶颈。如果日志也被发送到 stdout/stderr,通常也没有必要这样做。通常,当组件在容器环境中运行时,不鼓励将日志记录到文件中。相反,组件应该将所有日志传输到收集系统中。
鼓励异步日志。在可伸缩环境中,同步日志可能成为性能瓶颈,组件应该异步写入日志。
时间戳应该由程序的日志记录器创建。组件应该使用所选的日志库来创建时间戳,并且使用日志框架所允许的尽可能精确的时间戳。由应用程序或日志处理程序记录时戳可能会比较慢,在接收时创建时间戳也可能会产生延迟,从而使得在日志聚合之后在多个服务之间对齐事件时产生问题。
必须能够在不中断服务的情况下更改日志级别。组件应该提供在启动时设置日志级别的机制,以及允许在运行时更改日志级别的 API。基于特定子系统确定日志级别的范围是一个有用的特性,但不是必需的。当一个组件由一组微服务实现时,日志配置只需应用于一个实例,就可以应用于所有实例。
6.3 分布式跟踪
运行时跟踪是遥测数据的第三个来源。在云环境中,由于需要跟踪跨多个微服务的每个用户发起的请求的控制流,因此是一个挑战。好消息是,可以通过微服务的底层语言运行时系统(通常在 RPC stub 中)中激活跟踪支持,而不需要应用程序开发人员在程序中支持。
一般模式类似于我们已经看到的指标和日志: 代码执行过程中生成数据,然后收集、聚合、存储这些数据,并用于显示和分析。主要区别在于我们想要收集的数据类型,对于跟踪来说,通常是 API 从一个模块到另一个模块的调用序列,这些数据为我们提供了重建调用链所需的信息。原则上,我们可以利用日志系统跟踪,只需要勤奋的记录必要的接口调用信息,但这是一个通用用例,足以保证拥有自己的词汇表、抽象和机制。
在较高的级别上,跟踪(trace) 描述了事务在系统中的移动,由一系列 span(每个 span 表示在服务中完成的工作)和一组 span 上下文(span context)(每个 span 上下文表示通过网络从一个服务到另一个服务的状态)交织组成。图 28 中显示了一个跟踪示例,但从抽象意义上讲,跟踪是一个有向图,其中节点对应于 span,而边对应于 span 上下文。然后,节点和边被打上时间戳,并用端到端执行路径的相关事实(键/值标签)进行注释,包括何时运行以及运行了多久。每个 span 还包括在执行过程中生成的带时间戳的日志消息,从而简化了将日志消息与跟踪关联的过程。
图 28. 横跨两个网络服务的跟踪示例。
同样,就像指标和日志信息一样,细节很重要,而这些细节是由一个商定的数据模型来指定的。OpenTelemetry 项目现在正在定义这样的模型,它建立在早期 OpenTracing 项目之上(该项目又受到 Google 开发的 Dapper 分布式追踪机制的影响)。除了定义一个能够捕获最相关的语义信息的模型这一挑战之外,还有一个务实的问题: (1)尽量减少跟踪的开销,以免对应用程序性能产生负面影响,但(2)从跟踪中提取足够的信息,以便使收集这些信息变得有价值。采样是一种被广泛采用的技术,因此被引入到数据收集流水线中来管理取舍。这些挑战的一个结果是,分布式跟踪是正在进行的研究主题,我们可以期待模型定义和采样技术在可预见的未来不断发展和成熟。
延伸阅读:
B. Sigelman, et al. Dapper, a Large-Scale Distributed Systems Tracing Infrastructure. Google Technical Report. April 2010.
OpenTelemetry: High-quality, ubiquitous, and portable telemetry to enable effective observability.
在机制方面,Jaeger 是一个被广泛使用的开源跟踪工具,最初由 Uber 开发(Jaeger 目前不包含在 Aether 中,但在 ONF 的前身边缘云中使用)。Jaeger 包含了用于实现应用程序的语言的指令、收集器、存储,以及用于诊断性能问题并进行根因分析的查询语言。
6.4 集成仪表板
仪表化应用软件生成的指标、日志和跟踪使得收集有关系统健康状况的大量数据成为可能。但是,只有在正确的时间(需要采取行动的时候)将正确的数据显示给正确的人(那些有能力采取行动的人)时,这种工具才有用。创建有用的面板并将其组织成直观的仪表板是解决方案的一部分,但跨管理平台的子系统集成信息也是必要的。
统一所有这些数据是正在进行的工作的最终目标,比如上一节提到的 OpenTelemetry 项目,但也有机会使用本章介绍的工具来更好的集成数据。本节重点介绍两种一般策略。
首先,Kibana 和 Grafana 都可以配置为显示来自多个来源的遥测数据。例如,可以直接在 Kibana 中集成日志和跟踪。通常首先将跟踪数据输入到 ElasticSearch,然后由 Kibana 进行查询。类似的,如果有一种方便的方式来查看已收集的指标上下文中与特定组件关联的日志消息,就会很有用。而这很容易完成,因为可以配置 Grafana 显示来自 ElasticSearch 的数据,就像显示来自 Prometheus 的数据一样简单,两者都是可以查询的数据源。这使得创建包含一组选定的日志消息的 Grafana 仪表板成为可能,类似于图 29 中所示的来自 Aether 的消息。在这个例子中,我们看到与 SD-Core 的 UPF 子组件相关联的 INFO 级消息,扩充了图 26 所示的 UPF 性能数据。
图 29. 日志消息与 SD-Core 的 UPF 组件显示在 Grafana 仪表板上。
其次,第 5 章中介绍的运行时控制界面提供了改变运行中系统的各种参数的方法,但要想做出明智的决定,获得所需数据以知道需要做哪些改变(如果有的话)是其前提条件。为此,理想做法是在一个综合仪表板上既能访问"按钮"又能访问"刻度盘"。可以通过在运行时控制 GUI 中加入 Grafana 框架来实现,在其最简单的形式下,可以显示一组与底层数据模型字段相对应的 Web 表单(更复杂的控制面板当然也是可能的)。
图 30.示例控制仪表板显示了为虚构的 Aether 站点集合定义的设备组集合。
例如,图 30 显示了一组虚构的 Aether 站点的当前设备组,单击"Edit"按钮将弹出一个 web 表单,该表单允许企业 IT 管理员修改 Device-Group 模型的相应字段(未显示),单击"Monitor"按钮将弹出类似于图 31 所示的 Grafana 生成的框架。原则上,这个框架是经过裁剪的,只显示与所选对象最相关的信息。
图 31. 选择 Device Group 关联的监控帧示例。
6.5 可观测性
知道收集什么样的遥测数据,以便在需要的时候得到准确的信息,但这样做又不会对系统性能产生负面影响,这是一个困难的问题。可观测性(Observability) 是一个相对较新的术语,被用来描述这个一般的问题空间,虽然这个术语被认为是最新的市场流行语(确实如此),但也可以被解释为所有好的系统都渴望的另一个"特性",与可扩展性(scalability)、可靠性(reliability)、可用性(availability)、安全性(security)、易用性(usability)等等并列。可观测性是一个系统的质量,可以让人们看到其内部运行的事实,从而做出明智的管理和控制决策。这已经成为肥沃的创新空间,因此我们以两个可能在不久的将来成为普遍现象的例子来结束本章。
首先是带内网络遥测(INT, Inband Network Telemetry) ,它利用可编程交换硬件的优势,使运营商能够在数据包流经网络时,提出关于数据包如何被"带内"处理的新问题。这与依赖硬接入固定功能网络设备的预定义计数器集,或仅能检查数据包的抽样子集形成对比。由于 Aether 使用可编程交换机作为其基于 SDN 的交换结构的基础,能够使用 INT 作为第四种遥测数据,并以此对流量模式和网络故障的根本原因提供定性的更深入的见解。
例如,INT 已经被用来测量和记录单个数据包在沿着端到端路径穿越一连串交换机时所经历的排队延迟,从而有可能检测到微爆(microbursts, 以毫秒甚至亚毫秒的时间尺度测量的排队延迟)。甚至有可能将这一信息与遵循不同路径的数据包流联系起来,以确定哪些流在每个交换机上共享缓冲容量。作为另一个例子,INT 已经被用来记录指导数据包如何交付的决策过程,也就是说,在端到端路径上的每个交换机都应用了哪些转发规则。这为使用 INT 来验证数据平面是否忠实执行网络运营商所期望的转发行为打开了大门。关于 INT 的更多信息,请参考我们的 SDN 配套书籍。
延伸阅读:
L. Peterson, et al. Software-Defined Networking: A Systems Approach. November 2021.
其次是第一章中提到的服务网格(Service Mesh) 的出现。像 Istio 这样的服务网格框架提供了一种通过在微服务之间注入"观察/执行点"来执行细粒度安全策略并收集云原生应用遥测数据的手段。这些注入点被称为边车(sidecar) ,通常由一个容器来实现,该容器在实现每个微服务的容器旁边运行,从服务 A 到服务 B 的所有 RPC 调用都要通过相关的 sidecar。如图 32 所示,这些 sidecar 实现运营商想要施加在应用程序上的任何策略,将遥测数据发送到全局收集器并从全局策略引擎接收安全指令。
图 32. 服务网格框架概述,通过 sidecar 拦截在服务 A 和服务 B 之间流动的消息。每个 sidecar 执行从中央控制器接收到的安全策略,并向中央控制器发送遥测数据。
从可观测性的角度来看,sidecar 可以被编程以记录运营商可能想要收集的任何信息,原则上甚至可以根据条件的需要动态更新。这为运维人员提供了一种定义系统观测方式的通用方法,而不需要依赖开发者在服务中包含指令。缺点是,sidecar 在服务间的通信上带来了不可忽视的开销。由于这个原因,替代 sidecar 的方法越来越多,特别是 Cilium,它使用 eBPF(扩展的伯克利包过滤器, extended Berkeley Packet Filters)在内核内而不是在 sidecar 中实现可观测性、安全性和网络数据平面特性。
关于 Istio 服务网格的更多信息,我们推荐 Calcote 和 Butcher 的书,同时 Cilium 项目在其网站上有大量的文档和教程。
延伸阅读:
L. Calcote and Z. Butcher Istio: Up and Running. October 2019.
你好,我是俞凡,在 Motorola 做过研发,现在在 Mavenir 做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI 等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。
微信公众号:DeepNoMind
版权声明: 本文为 InfoQ 作者【俞凡】的原创文章。
原文链接:【http://xie.infoq.cn/article/03ca93cda23bfef2b6b6f1289】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论