最终用户问答系列:在 Lightstep 中迁移到 OTel
博客文章在发布后不会更新。这篇文章已经发布一年多了,其内容可能已过时,部分链接可能无效。在依赖任何信息之前,请务必核实。
由 Adriana Villela (Lightstep from ServiceNow) 贡献。
在 OpenTelemetry (OTel) 终端用户工作组 2023 年的第四次终端用户问答中,我们采访了 Jacob Aronoff,他是 Lightstep from ServiceNow 的首席软件工程师,也是 OpenTelemetry Operator 的维护者。如果您想了解一家供应商如何在内部使用 OTel,请继续阅读!
本系列访谈是每月一次与一个在生产环境中使用 OpenTelemetry 的团队进行的非正式讨论。目的是与社区分享我们了解到的他们是如何做到的,以及他们的成功和挑战,以便我们共同改进 OpenTelemetry。
概述
在本次会议中,Jacob 分享了
- 他如何从 OpenTracing 和 OpenCensus 迁移到 OpenTelemetry
TargetAllocator是什么,以及他目前如何使用它- 为什么您可能不想将 Collector 部署为 sidecar
访谈
背景故事
Jacob 在 Lightstep from ServiceNow 的遥测管道团队工作了近两年。他花了一年的时间专注于 OTel 迁移,包括在内部和使其客户更容易进行迁移。
当他加入团队时,他说:“我们仍然在使用 OpenTracing 进行跟踪,并且混合使用 OpenCensus 和一些自己开发的 Statsd 来进行指标收集。”这意味着他们必须在每个 Kubernetes Pod 上运行一个代理(代理作为 sidecar 运行在每个 Pod 上,这意味着你必须运行另一个应用程序来读取 Statsd 并转发指标)。
这大致是在 OpenTelemetry 指标发布候选版本刚刚宣布的时候,他看到了一个机会:“我们有一个内部 OTel 团队一直在努力开发,并希望立即获得关于如何改进它的反馈,所以我为我们这边启动了迁移,”他说。
OpenCensus 指标迁移
他以前做过类似的迁移,起初他计划尽可能安全地进行。由于他们在同一个单体仓库中,他们可以一次性完成所有工作,但这会冒着推送错误带来的风险。
Jacob 说:“这是应用程序数据,我们用它来进行告警,以了解我们的工作负载在所有环境中的运行情况,因此不要中断它很重要,因为那将是灾难性的。对用户来说也是一样,他们希望知道如果他们迁移到 OTel,他们不会失去告警能力。您想要一个安全简便的迁移。”
他的团队在 Kubernetes 中完成了基于功能标志的配置部分。他说:“它会禁用 sidecar 并启用一些代码,然后将 OTel 替换为指标并转发到它应该去的地方。这就是迁移路径。”
然而,在此过程中,他在他们用于监控公共环境的环境中测试时,注意到了一些“相当大的性能问题”。他与 OTel 团队合作,试图缓解一些问题,并发现一个主要的障碍是他们大量使用指标上的属性。
“进入并找出哪些指标在使用它们并删除它们很麻烦。我有一个理论,认为一个代码路径是问题所在,即我们在将我们的内部标记实现转换为 OTel 标记时,这带来了很多其他逻辑并且[转换]很昂贵,而且它几乎发生在每个调用中,”他说。“现在是时候开始从 OpenCensus 迁移到 OTel 了。”
他将此视为另一个机会:“在我们等待 OTel 人员在指标方面发布更具性能的代码和实现时,我们也可以测试一下这个理论,即如果我们完全迁移到 OTel,我们会看到更多的性能优势。”因此,他们暂停了指标工作,并开始迁移他们的跟踪。
OpenTracing 迁移
对于跟踪,Jacob 决定尝试“全有或全无”的方法。从 OpenTracing 到 OTel 的路径更广为人知,有一些文档和示例供他们参考。此外,“它们是向后兼容的,您可以将它们结合使用,”他说,“只要您正确设置了传播器(propagators)。”
正确设置传播器后,他们确保了他们所有的插件(现在都是开源的)都能正常工作。他们不得不在暂存环境中回滚了几次,但除了一个他忽略的 bug 外,没有遇到任何主要问题。
“我不得不实现一个自定义采样器(sampler),这在 OTel 中比在 OpenTracing 中容易十倍,”他说。“我能够移除一千行代码和一些危险的 hack,所以这是一件非常好的事情。”
如何开始迁移
“我从我团队负责的、流量非常小的服务开始,但流量足够高,可以保持持续性,”Jacob 说。“之所以选择这样的服务,是因为如果流量太低,例如每 10 分钟只有一个请求,您就不得不担心采样率(sample rates),[而且]您可能没有足够的数据进行比较——这是关键:您需要有一些数据进行比较。”
他早期为他们的指标迁移编写了一个脚本,该脚本查询了他们所有指标上的不同构建标签。如果新构建标签的标准差比前一个版本大 1,这可能表明您的检测库存在问题。
“我不得不检查的另一件事是,在迁移前后所有属性是否都仍然存在,这也是一个重要的问题,”Jacob 指出。有时它们不存在,例如 Statsd 自动添加了他们不关心的东西;这些可以安全地忽略。
对于跟踪,Jacob 说:“我选择了一个服务,该服务既有内部专用的跟踪(仅在单个服务内),也有跨多个服务的跟踪,这些服务具有不同类型的检测,从 Envoy 到 OTel 再到 OpenTracing。”
他解释说:“您想看到的是,之前的跟踪和之后的跟踪具有相同的结构。所以我编写了另一个脚本,用于检查这些结构是否相对相同,以及它们是否都具有相同的属性……这就是跟踪迁移的重点——重要的是所有属性都保持不变。”
数据丢失时
“‘为什么会丢失’的故事非常复杂,”Jacob 说。有时,这很简单,就是忘记“在某处添加东西”,但有时,上游库可能不会发出您期望的 OTel 内容。
他讲述了自己迁移 gRPC 工具包(现已在 Go contrib 中)时遇到的传播问题。
“我试图弄清楚这里出了什么问题。当我查看代码时——这说明我当时做这个迁移有多早——本应有一个传播器的地方,却只是一个‘TODO’,”他分享道。“这导致了暂存环境中我们所有服务的跟踪都崩溃了。”
他花了一些时间来解决这个问题,但他们反过来又在等待别的东西,如此循环往复——Jacob 说有“无休止的这类循环”。一旦他解决了问题,他就将其上游化,以便社区可以使用。
“许多指标工作为 OTel 指标带来了巨大的性能提升,”他说。“例如 OTel Go 指标。它还为 Statsd 人员提供了一些关于 API 在各种功能方面应有多具描述性的想法。因此,像视图(Views)这样的东西,以及视图的使用,是我们早期迁移中大量使用的东西。”
指标视图
“视图(View)是 OTel 中在 Meter Provider 内部运行的东西,”Jacob 解释道。有许多配置选项,例如删除属性,这是最常见的用例之一。“例如,您是一个集中的 SRE,您不希望任何人使用用户 ID 属性来检测代码,因为这是一个高基数(cardinality)的东西,它会使您的指标成本爆炸。您可以创建一个添加到您的检测中的视图,并告诉它不要记录它,拒绝它。”
还有更高级的用例,例如动态更改指标的时间属性(temporality)或聚合(aggregation)。时间属性是指指标是否包含前一个测量值(累计和增量),而聚合是指您如何发送指标。
“这对[我们的]直方图(histograms)来说最有用,”Jacob 说。“当您记录直方图时,有几种不同的类型——DataDog 和 Statsd 直方图不是真正的直方图,因为它们记录的是聚合样本。它们为您提供最小值、最大值、计数、平均值和 P95 或类似值。问题是,在分布式计算中,如果您有多个应用程序报告 P95,您就无法从该聚合的观察中获得真正的 P95,”他继续说道。
“原因在于,如果您有五个 P95 的观测值,没有一个聚合可以用来‘给出我的 P95’。您需要从原始数据中提取一些信息才能重新计算。您可以计算 P95 的平均值,但这并不是一个很好的指标,它并不能真正告诉您多少。它并不真正准确。如果您要基于某事进行告警并在夜间叫醒某人,您应该基于准确的测量值进行告警。”
起初,他们确实有几个人依赖最小值、最大值、总和、计数工具,所以他们使用 Metrics SDK 中的视图来配置自定义聚合或直方图,以输出一个分布,或者在 OpenTelemetry 中,一个指数直方图。 “我们是双重输出;这之所以有效,是因为它们是不同的指标名称,所以没有重叠。”
迁移完成后,他们能够回到任何使用最小值、最大值、总和、计数指标的仪表板或告警,并将其更改为分布。“由于我们在过去几周、几个月运行 OTel 指标的公共环境中积累了足够的数据,所以这是可能的,”Jacob 说。“这是关键功能之一,因为它非常方便,并且我们可以在应用程序中完成,无需引入任何其他组件,这真的很棒。”
日志和 Span 事件
当 Jacob 开始 OTel 迁移时,日志方面还为时过早。“我们会改变的事情,”他说,“可能是我们收集日志的方式;我们之前使用 Google 的日志代理,基本上在 GKE 集群的每个节点上运行 fluentbit,然后它们将其发送到 GCP,我们在那里进行尾部跟踪。”他指出,这可能已经有了最新的变化,而他目前还不知道。
“很长一段时间以来,我们在内部使用 Span 事件和日志,”他说。“我是它们的忠实粉丝。”他不是日志的忠实粉丝,他认为它们“繁琐且昂贵”。他建议用户尽可能选择跟踪和跟踪日志,尽管他确实喜欢日志用于本地开发,而跟踪用于分布式开发。
Kubernetes 中的遥测收集
Kubernetes 现在原生支持发出 OTel 跟踪,Jacob 有兴趣看看他们从这些跟踪中获得的数据是否足以使用 spanmetrics processor 生成更好的 Kubernetes 指标。
注意: spanmetrics processor 已弃用,应使用 spanmetrics connector。
“我非常关注基础设施指标,例如 Kubernetes 基础设施指标,我觉得它们目前的形式非常痛苦,”他说。目前,他正在使用 Prometheus API 来收集它们,这是可观测性社区中的普遍做法,因为 Kubernetes 已经原生支持了这些。
“这就是我们现在所做的,我使用我参与开发的 OTel 组件 Target Allocator 来分发这些目标(targets),这是获取所有数据的一种相当有效的方式,”Jacob 说。
“我们还使用我们集群中运行的 daemonsets 来获取额外数据,这很有效。令人沮丧的是 Prometheus。Prometheus 的抓取值(scrape values)可能是一个非常普遍的问题,当你不得不担心指标基数(cardinality)时,这会变得非常烦人,因为它可能会爆炸。”
Target Allocator
“Target Allocator 是 OTel 中 Kubernetes operator 的一个组件,它做了 Prometheus 做不到的事情:在抓取器(scrapers)池中动态分片目标,”Jacob 分享道。使用 Target Allocator 不需要运行 Prometheus 实例;但是,为了让 Target Allocator 能够拾取它们,Prometheus CRD 必须存在。
来自 文档
Allocator 拾取它们还需要 Prometheus CRD。最适合获取它们的地方是 prometheus-operator:Releases。只需要部署 Allocator 监视的 CR 的 CRD。可以从 bundle.yaml 文件中挑选出来。
他继续解释说,虽然 Prometheus 有一些实验性的分片功能,但查询方面仍然存在问题,因为 Prometheus 也是一个数据库,而不仅仅是抓取器。您必须在这些 Prometheus 实例之间进行某种程度的协调,这可能很昂贵,或者使用 Prometheus 扩展解决方案,例如 Thanos 或 Cortex——然而,这涉及到运行更多需要您监控的组件。
“在 OTel 中,我们添加了这个 Prometheus receiver 来获取所有这些数据,但因为我们想比 Prometheus 更高效,因为我们不需要存储数据,所以我们有一个名为 Target Allocator 的组件,它负责从 Prometheus 进行服务发现,”Jacob 说。“它会询问需要抓取的所有目标。然后 Target Allocator 说:用这些目标,将它们均匀地分配给正在运行的 collector 集合。”
这是该组件的主要功能,它还有助于作业发现。如果您正在使用 Prometheus 服务监视器(service monitors),这是 Prometheus operator 的一部分,这是在集群中运行 Prometheus 的流行方式,“Target Allocator 还可以提取这些服务监视器和 Pod 监视器,并更新监视器和抓取配置来完成这项工作。”
Jacob 的团队不运行任何 Prometheus 实例——他们只运行带有 Prometheus receiver 的 collector,并将数据发送到 Lightstep。“这很好,”他说。
他的团队过去曾运行一个 Prometheus sidecar,作为他们 Prometheus 安装的一部分。然后它会与他们的 Prometheus 实例位于同一个 Pod 中,并读取 Prometheus 用于持久化和批处理的写前日志。然而,如果您的 Prometheus 实例很嘈杂,它可能会效率低下。“它可能会变得非常嘈杂,而且不是最好的,”Jacob 说。“Collector 是运行此的最佳方式。”
Collector 设置
Jacob 的团队在 Lightstep 运行许多不同类型的 Collector。“我们运行指标相关的东西、跟踪相关的东西、内部的、外部的——一直都有很多不同的 collector 在运行,”他分享道。
“一切都处于剧烈变动中。”他们正在进行大量的更改来运行实验,因为他们为客户和终端用户创建功能最好的方法是首先确保它们在内部运行正常。
“我们正在运行一个单一路径,其中两个环境中的两个 collector 可能正在运行两个不同的镜像和两个不同的版本。这变得非常抽象和混乱,以至于难以谈论,”他说。“然后,如果您将 Collector A 发送到 Collector B 的环境中,Collector B 也会发出关于它自己的遥测数据,然后由 Collector C 收集,所以它就这样连锁下去。”
简而言之,您需要确保 collector 确实在工作。“这就是我们在调试这些东西时遇到的问题。当有问题时,您必须考虑问题到底出在哪里——是我们收集数据的方式有问题,还是我们发出数据的方式有问题,还是数据生成的源头有问题?总之有很多可能。”
Kubernetes 上的 OTel 模式
OTel Operator 在 Kubernetes 中支持四种 部署模式来部署 OTel Collector。
- Deployment - 示例 ingress/00-install.yaml
- DaemonSet - 示例 daemonset-features/01-install.yaml
- StatefulSet - 示例 smoke-statefulset/00-install.yaml
- Sidecar - 示例 instrumentation-python/00-install-collector.yaml
使用哪种模式取决于您的需求,例如您希望如何运行应用程序以实现可靠性。
“Sidecar 是我们使用最少的一种,也是我打赌在行业中最少使用的一种,”Jacob 说。“它们很昂贵。如果你真的不需要它们,那么你不应该使用它们。”Sidecar 的一个例子是 Istio,“运行 Istio 作为 sidecar 是很有意义的,因为它代理流量,并且它会挂接到你的容器网络来改变它的工作方式。”
如果您为所有服务都运行 Collector sidecar,您将付出更高的成本,而且您的能力也有限。他说:“如果您正在进行 Kubernetes API 调用或属性丰富化,如果您作为 sidecar 运行,那么成本将呈指数级增长。”他分享了一个例子:“……如果您在 10,000 个 Pod 上运行 sidecar [Collector 使用 k8sattributesprocessor],那么这就是对 K8s API 的 10,000 次 API 调用。这很昂贵。”
另一方面,如果您在 StatefulSets 上部署了五个 Pod,“这并不算太贵。”当您以 StatefulSet 模式运行时,您将获得一个始终存在的精确副本数量,每个副本都有一个可预测的名称——“当您想要一致的 ID 时,这是一个非常有价值的东西。”
由于一致的 ID,您可以对 Target Allocator 做一些额外的工作,而 Target Allocator 是必需的。StatefulSets 保证的另一件事是所谓的就地部署(in-place deployment),这在 DaemonSets 中也可用;这意味着在创建新 Pod 之前,您会关闭旧 Pod。
“在 Deployment 中,您通常会进行 1-up, 1-down,或称为滚动部署,或滚动更新,”Jacob 说。如果您使用 Target Allocator 进行此操作,您可能会获得更不可靠的抓取。这是因为当一个新的副本启动时,您必须重新分发所有目标,因为您在上面放置的哈希环(hash ring)已经改变,需要重新计算您分配的所有哈希。
而在 StatefulSets 中,则不需要这样做,因为您获得了统一的 ID 范围。“所以当你进行 1-down 1-up 时,每次都会保留相同的目标。就像一个占位符——您不必重新计算环,”他解释道。
他指出,这主要适用于指标用例,即您正在抓取 Prometheus。他指出,对于其他任何事情,他们很可能会将其作为 Deployment 来运行,因为该模式提供了您所需的大部分功能。Collector 通常是无状态的,因此不需要它们保留任何东西,Deployment 因此更轻便。“您可以直接运行和推出,每个人都会很高兴,”他说。“我们运行大多数 Collector 的方式就是作为 Deployment。”
对于每个节点抓取,DaemonSets 就派上用场了。“这允许您抓取运行在每个节点上的 kubelet,允许您抓取也运行在每个节点上的 node exporter,这是大多数人运行的另一个 Prometheus daemonset,”他解释道。
DaemonSets 对于扩展非常有用,因为它们保证您在每个匹配其选择器的节点上都有 Pod 在运行。“如果您有一个包含 800 多个节点的集群,运行一堆小型 collector 来获取那些微小指标比运行几个大型 stateful set Pod 更可靠,因为您的影响范围要小得多,”他说。
“如果一个 Pod 宕机,您只会丢失一点点数据,但请记住,考虑到所有基数问题,那将占用大量内存。所以如果您使用 StatefulSet 来抓取所有这些节点,那将是大量目标,大量内存,它可能更容易宕机,并且您会丢失更多数据。”
如果一个 Collector 宕机,它会很快恢复,因为它通常是无状态的,这意味着“通常的干扰很小,”Jacob 说。然而,如果您已经过了饱和点,干扰“会更加不稳定,可能会很快地上升和下降。”因此,拥有一个水平 Pod 自动缩放器(HPA)是个好主意。
这在指标方面很有用,但您也可以使用跟踪工作负载来完成。由于它们都是推送式的,因此更容易扩展,并且您可以分发目标和负载均衡。
“拉取式(Pull-based)是 Prometheus 如此普遍的原因……因为它使本地开发非常容易,您可以直接抓取您的本地端点,这正是大多数后端开发的方式,”他说。“您可以命中端点 A,然后命中您的指标端点。然后再命中端点 A,然后命中指标端点,并检查一下,所以这是一个简单的开发人员循环。它也意味着您不必向网络外部发送数据,如果您有非常严格的代理要求来发送数据,本地开发会更容易。这就是为什么 OTel 现在有一个非常好的 Prometheus exporter,所以它可以做到两者兼顾。”
中心化 OTel Collector 网关
有一个正在进行的中心化网关,它是 Jacob 之前提到的 Collector 链的一部分。这项工作围绕 Arrow 进行。Lightstep 在使用 Apache Arrow(一个用于基于列式数据的表示的项目)改进“OTel 数据的处理速度和入口成本”方面做了一些工作,Jacob 解释道。
他们目前正在进行一些概念验证(proof of implementation),以调查其性能,并确认事情按预期工作。
保持遥测最新
Jacob 指出,保持遥测最新很重要,因为库作者和维护者一直在致力于新的性能特性和软件改进。
“这也使得迁移更容易。尝试从早期版本迁移到最新版本,您可能会错过很多破坏性更改,并且您必须对此保持警惕,”他说。
他推荐使用 Dependabot,他们就在 OTel 中使用它。OTel 包同步更新,这意味着您必须“一次性更新相当多的包,但它确实为您完成了所有工作,这很好,”他说。但是,您应该对所有依赖项都这样做,因为“CVE 在行业中不断发生。如果您不及时更新漏洞修复,那么您就是在暴露自己于安全攻击之下,这是您不希望的。我的建议是‘采取行动’。”
附加资源
- 在 OTel YouTube Channel 上收听完整的对话。
- 要了解有关 OTel Operator 的更多信息,请在 CNCF Slack 的 #OTel-operator 频道中联系。
- Jacob 将于8月17日东部时间下午1点/太平洋时间上午10点在 OTel in Practice 活动中再次与终端用户工作组进行交流。请务必记下您的日历!
最后的想法
OpenTelemetry 倡导社区精神,没有贡献者、维护者和用户,我们不会有今天的成就。我们重视用户反馈——请分享您的经验,帮助我们改进 OpenTelemetry。
以下是我们联系方式
- 在 CNCF Community Slack 上的 #otel-endusers 频道
- 月度 终端用户讨论组会议
- OTel in Practice 会议
- 月度访谈/反馈会议
- OpenTelemetry 在 LinkedIn
- OpenTelemetry 博客
请务必在 Mastodon 和 X(以前称为 Twitter)上关注 OpenTelemetry,并使用 **#OpenTelemetry** 标签分享您的故事!