为了更好地与行业可观测性标准保持一致,eBay 做了一项关键调整,转向了 OpenTelemetry。
引言
可观测性为组织提供了眼睛和耳朵。可观测性的一个主要好处是,通过有效揭示关键工作流中持续存在的、可能影响客户体验的问题来预防收入损失。可观测性生态圈在不断发展,OpenTelemetry 世界的最新进展使我们不得不重新思考我们的策略,并转而使用它。eBay 的观测平台 Sherlock.io 为开发人员和站点可靠性工程师(SRE)提供了一套强大的云原生服务,可用于观测支撑 eBay 生态系统的各种应用程序。Sherlock.io 支持可观测性的三大支柱:指标、日志和跟踪。平台的指标存储是 Prometheus 存储引擎的一个集群和分片实现。我们使用 Metricbeat 代理每分钟抓取大约 150 万个 Prometheus 端点,采集到的数据存入指标存储中。这些端点每秒可以产生大约 4000 万条符合记录规则的样本数据。那些采集到的样本形成了存储在 Prometheus 上的 30 亿个活跃的样本序列。因此,eBay 的可观测性平台规模非常大,这带来了新的挑战。
作为一个可观测平台提供商,eBay 是最早使用代理抓取指标端点和跟踪日志文件的公司之一。正如我们在之前的博文中所讨论的那样,我们大量依赖 Elastic Beats 把信号接收到平台。Beats 是一个轻量级的运维数据(例如指标和日志)传输工具。从 2016 年到 2020 年的五年时间里,我们在所有 Kubernetes 集群上将 Filebeat 和 Metricbeat 作为守护进程集(DaemonSets)来运行。守护进程集允许用户在 Kubernetes 集群的每个节点上部署给定的工作负载。然而,我们在一次内部黑客周期间进行了一项实验,得出了一些令人惊讶的结论,并促使我们重新考虑守护进程集的使用。在这篇博文中,我们讨论了我们遇到的一些问题,特别是指标抓取方面的问题,以及我们如何改进自己的解决方案。我们还将详细讨论我们如何在许可方面驾驭不断变化的开源生态,以及我们打算如何就使用 OpenTelemetry 这一方案达成一致。
指标检测
eBay 的指标检测已经或多或少地在 Prometheus 端点上标准化了。各种应用程序的端点已通过各种检测实践暴露出来,例如(但不限于):
Prometheus 官方客户端(包括 Java、Go、Python 等)
Micrometer
OTEL SDK with Prometheus Exporter
当请求时放出一个 Prometheus 端点的自定义代码
eBay 平台工程小组提供的框架内置了一个检测客户端,还暴露了各种指标端点,分别代表服务器端、客户端和数据库客户端指标。哪些 Prometheus 端点需要暴露出来以供抓取取决于应用程序的性质。应用程序所有者还可以暴露自己的端点来测量业务 KPI。
Autodiscover
支持 eBay 生态系统的大多数应用程序都运行在 eBay 的内部 Kubernetes 提供程序 Tess 上。eBay 运行着数百个基于 Tess 的 Kubernetes 集群,应用程序可以在任意数量的集群及集群的任意组合上运行。应用程序所有者可以选择将他们的应用程序指标与框架级工具中提供的指标放在一起。我们的代理需要确切地知道当前运行的 Kubernetes Pod 暴露了哪些端点。为了给代理提供这些信息,我们帮助丰富了 Beats 平台的功能,为的是可以执行一个叫做“基于线索的 autodiscover”的任务。Autodiscover 是一种 Beats 结构,它允许像 Kubernetes API 服务器这样的动态源向代理传递如下信息:
需要抓取的端点是什么?
端点是什么类型的——Dropwizard、Prometheus、Foobar,还是别的什么?
多久抓取一次?
代理还需要额外知道其他的信息吗,比如 SSL 证书?
随着所需的发现模式越来越复杂,我们与 Beats 开源社区建立了合作关系,以增强自动发现能力,满足我们的特有需求。以下是我们贡献的一些功能:
发现多组配置:传统的基于注解的抓取功能非常有限,因为用户只能为抓取管理器提供简单的配置。考虑到每个端点都可能有动态需求,如不同的处理方式和抓取间隔,我们增强了 autodiscover,让它可以接受多组配置。
从命名空间注解中发现目标:声明抓取目标的指定方法要求在 Pod 规范中添加注解。然而,在那里添加注解意味着要重启 Pod。如果更改的目的是针对框架上正在测量的指标,并且在每个已部署的应用程序上都可用,那么这是不可取的。为了避免重启所有的 Pod,我们添加了 autodiscover 支持,以便可以额外查看命名空间级的注解。
在识别部署在 Kubernetes 集群上的目标方面,这些特性使 Beats Autodiscover 成为一种更通用、功能更丰富的发现机制之一。
通过守护进程集抓取指标的局限性
我们第一次尝试大规模运行 Metricbeat 是在每个 Kubernetes 集群上将其作为守护进程集运行。每个 Pod 都有一个 CPU 和 1GB 的内存用于处理该节点上暴露的所有指标。当 Metricbeat 启动时,它向 API 服务器请求该集群上的所有命名空间以及运行它的节点上部署的 Pod。有了这些信息,它就可以整理每个 Pod 及其命名空间上的注解并创建配置。下面是我们观察到的一些结果:
资源碎片:假设我们为 N 个节点集群运行 N 个 Beat,如果单个 Beat 管道的自举成本(bootstrapping cost)为 50MB,那么我们实际浪费了 50*N MB 的资源。在有 3000 个节点的 Kubernetes 集群上,这加起来是 150GB!
轮询大型端点时的 OOM 问题:我们看到,在客户公开的端点中,有的端点有多达 15 万个条目。一些巨大的端点,如“kube-state-metrics”,可达 300 万个条目,每次轮询会生成 600MB 的数据。当一个节点上出现这样的用例时,抓取就变得不可靠了。
下图展示了当 Beats 实例(如 Metricbeat、Filebeat 和 Auditbeat)部署为 DaemonSet 时,如何与 Sherlock.io 平台交互:
转向集群本地抓取
在处理一个不相关的项目时,我们使用了一种快捷方式,为集群中的所有目标运行单个 Metricbeat 实例。当我们观察运行 Metricbeat 所使用的 CPU 和内存时,看到的数值简直令人震惊。在部署过程中,我们看到了以下情况:
Kubernetes 节点数:2851
CPU 使用量:29 核
内存使用量:57GB
摄入速度:每秒 238K 样本
每个节点监控的端点数:4
监控的每个节点的平均内存使用量:20MB
监控的每个节点的平均 CPU 使用量:0.01 核
单个 Metricbeat 实例以 DaemonSet 模式监控一个节点上数量相近的端点,消耗大约 200MB 内存(10 倍)和大约 0.6 核 CPU(60 倍)。整个集群累计消耗将达 570GB 内存和大约 1700 个 CPU。迁移到集群本地实例总共可以节省大约 90% 的硬件资源。
这使我们不得不重新考虑处理抓取的方法。在整个集群中运行一个实例意味着当该实例升级或发生故障时,所有的抓取在那个时间点都将停止。为了减少故障,我们将 Metricbeat 部署为具有 N 个副本的 StatefulSet。整个 Pod 列表根据 Metricbeat 实例的数量进行 N 路分片,每个实例监控其分配到的分片:
xxHash(pod uid) % statefulset_size == instance number
每个实例都会对 API 服务器做一次完整的扫描,但除了它应该监控的内容之外,其他内容会被忽略。这个模型很适合 Metricbeat,因为它主要抓取 Prometheus 端点,并且这个活动可以发生在 Tess 节点之外。一个有 3000 个节点的大型 Kubernetes 集群有多达 30 个实例,CPU 和内存的数量也更多,与作为节点上的守护进程相比,这让它能够抓取的端点多许多。如果一个 Metricbeat 实例重启,则只有该实例监控的端点的抓取工作会短暂停止,故障百分比将降到实例总数的 1/N。
新的部署模式如下图所示:
解耦 Autodiscover
虽然与使用守护进程集相比,采用集群局部抓取的可扩展性要好许多,但这个模式还有改进的空间。新的问题出现了,特别是在集群规模比较大、Pod 密度比较高时。考虑到每个 Metricbeat 实例都必须扫描所有的 Pod 并选择它需要监控的 Pod,初始扫描花费的时间可能会很长,这取决于给定集群中 Pod 的数量。在推广过程中,这非常成问题,因为 Metricbeat Pod 一旦重启,就需要长达 10 分钟的时间才能重启抓取。因为对各种资源 Metricbeat 请求设置了大量的 WATCH,所以这种技术还会给 API 服务器带来沉重的负担,而这取决于实例的数量。
经过进一步的评估,我们决定将 Autodiscover 移出代理并实现它自己的控制循环。这个循环将完成以下工作:
实现一个逻辑和 Beats Autodiscover 类似的解析器;
发现所有可以进行抓取工作的代理;
选择其中一个代理;
并将配置传递给所选代理以监视目标。
这个控制循环会做出重要的决策,如在代理崩溃、代理过度分配和其他故障场景下调整工作负载。考虑到解析注解的逻辑已经从代理解耦,只要新代理一一实现了 Beats 提供的功能,为任何代理生成配置就都非常简单了。
OpenTelemetry 出现
2019 年,Open Tracing 和 Open Census 社区同意联合实现 OpenTelemetry。OpenTelemetry 计划的目标是提供与供应商无关的 API、SDK 和工具,用于采集、转换数据并发送给任何可观测后端。考虑到我们选择了 Kubernetes,它同样提供了与供应商无关的 API 来管理云上的容器,因此,投资这样一个项目似乎是很自然地选择,这非常适合我们在 eBay 使用开源软件的方式。2021 年,我们开始试验分布式跟踪,看看它对我们的开发人员有什么用。
当时,在 OpenTelemetry Collector 的代码库中,我们看到了它的一些功能的巨大潜力,包括预定义的指标类型、日志和跟踪文件,以及使用 Prometheus 抓取管理器从 OpenMetrics 端点收集指标。为了实现分布式跟踪,我们选择了 OpenTelemetry Collector 以及 OpenTelemetry SDK。接下来,我们应该弄清楚如何将指标和日志收集移到 OpenTelemetry Collector 中。这项工作可不简单,因为我们需要在不停机的情况下补全所有欠缺的功能,与新的开源社区建立关系,并替换一个大规模的指标收集基础设施。2022 年初,我们开始了将指标抓取向 OpenTelemetry Collector 迁移的艰巨任务。
迁移
因为我们将发现逻辑从代理中解耦了,所以实际的迁移工作就只是生成 OpenTelemetry Collector 可以理解的配置。每次有新的 Pod 启动时,都必须把这些配置推送给它,并在 Pod 死亡时进行清理。然而,OpenTelemetry Collector 有一个严重的缺陷:不能动态地重新加载配置。OpenTelemetry 收集器有一个“管道”的概念,它定义了如何接收、处理和导出指标。为了方便动态地重新加载管道,我们提出了一个“filereloadreceiver”,它可以查看一个包含“局部管道”描述文件的目录,这些局部管道可以插入到收集器的整个管道中。每个需要抓取指标的 Pod 都有一个局部管道,由 Autodiscover 控制器生成并推送到收集器。在这个过程中,另一项比较复杂的工作是,将我们依赖的每个功能在 Beats 平台和 OpenTelemetry Collector 之间创建一个映射表。例如,Beats 中的字段会被转换为 OpenTelemetry 上的属性处理器。有了这样的映射和 filereloadreceiver,我们就能够为 OpenTelemetry Collector 生成新的配置,如下所示:
如上所示,我们能够保持 Pod/Namespace 注解的终端用户契约相同,并在底层简单地替换代理。这大大简化了推出新代理的实际工作。最后一个障碍是 Elastic Beats、OpenTelemetry,有时甚至是 Prometheus 抓取管理器之间的语义不匹配。我们花了几个月的时间,才最终替换掉了生产环境中的所有 Metricbeat。我们在 OpenTelemetry Collector 项目中发现并帮助修复的一些差异包括:
将以“_”开头的消毒标签和指标名称与 Prometheus 对齐;
能够禁用标签消毒( label sanitization);
正确处理以“:”开头的指标名称;
能够使用正则表达式提取 Pod 标签。
事实证明,这些问题很难发现,有时只有当我们为了使用 OpenTelemetry Collector 而设法升级 Kubernetes 集群时才会发现这些问题。一旦遇到这样的问题,回滚就成了唯一的选项,我们不得不回到绘图板旁。一个权宜之计是编写一个比较脚本,可以使用 Metricbeat 和 OpenTelemetry Collector 抓取端点,将它们采集到指标存储中,并比较指标名称和标签,以确保抓取的内容彼此相同。这大大增强了我们继续前进的信心。
有时,向前推进仅仅意味着放弃对某些功能的支持。对于 Dropwizard Metrics 支持,我们就是这样做的,并让用户做了迁移。除了语义差异之外,我们还在积极地为项目添加我们认为至关重要的功能,如支持 Exemplar。
经过几个月的努力工作,再加上社区的支持,我们很高兴地宣布,Metricbeat 已经完全停用,并为 OpenTelemetry Collector 所取代。我们现在正忙着对 Filebeat 做同样的事,早期的性能基准测试看上去非常有希望。到目前为止,我们已经为这个项目做出了 10 多项贡献,但这只是一个非常富有成果的合作的开始。
小结
过去 5 年,eBay 遇到了几次需求激增,迫使我们对一些传统观念进行重新思考。我们从守护进程集入手,发现在规模比较大时,它的成本过高,而且也不可靠。我们迁移到了集群本地模型,将代理的成本降低了约 90%,但在 API 服务器和代理上,我们还是做了一些多余的工作。我们将发现逻辑解耦,迁移到执行调度的控制循环中,并将代理变为可以接受抓取目标的无状态进程。鉴于 OpenTelemetry 日益成熟,我们转而使用 OpenTelemetry Collector 来收集指标,并积极努力地在日志上做同样的事。我们将继续积累大规模运行代理的经验,并根据需要进行调整。我们将继续与 OpenTelemetry 社区合作,因为它将继续为可观测生态系统的标准化铺平道路。现在我们使用了 OpenTelemetry 为开发人员提供一个行业认可的开放性标准,并向 Sherlock.io 发送遥测数据。随着社区的发展,我们将支持性能分析等新特性,并把它们引入平台,让整个开发社区受益。
- END -
往期回顾
◆一支不足百人的团队创造了 ChatGPT :90 后挑大梁,应届生 11 人,华人抢眼
技术交流,请加微信: jiagou6688 ,备注:Java,拉你进架构群