去年秋天,当我们在 Sprint 2.0 上推出 NGINX 现代应用参考架构(MARA)项目时,我们强调了我们的意图,它不是像某些架构那样的“玩具”,而是一个“可靠、经过测试并准备部署在 Kubernetes 环境中运行的实时生产应用中”的解决方案。 对于这样的项目,可观察性工具是绝对必要的。 MARA 团队的所有成员都亲身体验过由于缺乏对状态和性能的洞察,应用开发和交付如何变得令人沮丧。 我们立即达成共识,即 MARA 必须包含用于在生产环境中调试和跟踪的工具。
MARA 的另一个指导原则是优先考虑开源解决方案。 在这篇文章中,我们描述了我们如何寻找多功能、开源可观察性工具并找到了OpenTelemetry ,然后详细介绍了我们用来将 OpenTelemetry 与使用 Python、Java 和 NGINX 构建的微服务应用集成的权衡、设计决策、技术和技术。
我们希望我们的经验可以帮助您避免潜在的陷阱并加速您自己对 OpenTelemetry 的采用。 请注意,该帖子是一份时间敏感的进度报告——我们预计我们讨论的技术将在一年内成熟。 此外,尽管我们指出了某些项目目前存在的缺点,但我们对正在进行的所有开源工作深表感谢,并很高兴看到它的进展。
作为与可观察性解决方案集成的应用,我们选择了Bank of Sirius ,这是我们从 Google 开发的Bank of Anthos示例应用程序的分支。 它是一个具有微服务架构的 Web 应用程序,可以通过基础设施即代码进行部署。 有许多方法可以在性能和可靠性方面改进此应用,但它已经足够成熟,可以合理地被视为棕地应用。 因此,我们相信它是展示如何将 OpenTelemetry 集成到应用中的一个很好的例子,因为理论上分布式跟踪可以对应用架构的缺点提供有价值的见解。
如图所示,支持该应用的服务相对简单。
我们选择 OpenTelemetry 的道路相当曲折,经历了几个阶段。
在评估可用的开源可观察性工具本身之前,我们确定了我们关心的可观察性方面。 根据我们过去的经验,我们制定了以下清单。
当然,我们并不期望单一的开源工具或方法能够包含所有这些功能,但至少它为我们提供了一个比较可用工具的理想基础。 我们查阅了每个工具的文档以了解它支持我们愿望清单中的七个功能中的哪一个。 下表总结了我们的发现。
技术 | 日志记录 | 分布式跟踪 | 指标 | 错误聚合 | 健康检查 | 运行时自省 | 堆/核心转储 |
---|---|---|---|---|---|---|---|
ELK + Elastic APM | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
格拉法纳 | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
格雷洛格 | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
耶格尔 | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
开放人口普查 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
开放遥测 | 测试版 | ✅ | ✅ | ✅ | ✅ * | ❌ | ❌ |
普罗米修斯 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
统计局 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
齐普金 | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
*作为扩展
建造这张桌子让我大吃一惊。 各种工具在功能和预期用途上差异很大,我们不能将它们视为同一类别——这就像将苹果与烤面包机进行比较!
举例来说, ELK ( Elasticsearch-Logstash-Kibana ,加上Filebeat )和Zipkin所做的事情有着根本的不同,试图比较它们会比任何事情都更令人困惑。 不幸的是,“任务蔓延”只会让情况更加混乱——毫无疑问是为了响应用户的要求,该工具添加了一些次要的功能,这些功能与该工具的主要目的无关,并且与其他工具重叠。 从表面上看,ELK 进行日志存储和可视化,而 Zipkin 进行分布式跟踪。 但是,当您深入了解 Elastic 产品组合时,您很快就会遇到Elastic APM ,它支持分布式跟踪,甚至具有Jaeger兼容性。
除了任务蔓延问题之外,许多工具可以相互集成,从而产生收集器、聚合器、仪表板等的不同组合。 有些技术彼此兼容,有些则不兼容。
因此,该比较表未能为我们提供足够准确的图像以供我们做出选择。 我们需要对每个项目的目标、指导原则和可能的未来方向进行定性调查,推断项目的价值观与我们的价值观越相似,我们就越有可能随着时间的推移保持兼容。 访问项目页面时,我们立即在OpenCensus页面上注意到了这一点。
OpenCensus 和 OpenTracing 合并形成 OpenTelemetry,作为 OpenCensus 和 OpenTracing 的下一个主要版本。 OpenTelemetry 将提供与现有 OpenCensus 集成的向后兼容性,并且我们将在两年内继续为现有的 OpenCensus 库提供安全补丁。
就我们而言,这是一个关键数据点。 我们知道我们无法保证我们的选择不会过时,但我们至少想知道它在未来会得到社区的坚实支持。 有了这些信息,我们就可以将 OpenCensus 从候选名单中划掉。 使用 Jaeger 可能也不是一个好主意,因为它是现已正式弃用的OpenTracing项目的参考实现——大部分新的贡献都将落在 OpenTelemetry 中。
然后我们阅读了有关OpenTelemetry Collector 的资料。
OpenTelemetry Collector 提供了一种与供应商无关的实现方式,用于接收、处理和导出遥测数据。 此外,它无需运行、操作和维护多个代理/收集器,即可支持将开源遥测数据格式(例如 Jaeger、Prometheus 等)发送到多个开源或商业后端。
OpenTelemetry Collector 充当聚合器,允许我们将不同的可观察性收集和检测方法与不同的后端混合和匹配。 基本上,我们的应用可以使用 Zipkin 收集跟踪并从Prometheus收集指标,然后我们可以将其发送到可配置的后端并使用Grafana进行可视化。 此设计还可以有其他几种排列组合,因此我们可以尝试不同的方法来看看哪种技术适合我们的用例。
基本上,我们之所以选择 OpenTelemetry Collector,是因为理论上它允许我们在可观察性技术之间切换。 尽管该项目相对不成熟,我们还是决定大胆深入研究并仅使用开源集成的 OpenTelemetry,因为了解形势的唯一方法是使用这些技术。
然而,OpenTelemetry Collector 缺少一些部分,因此我们必须依赖其他技术来实现这些功能。 以下部分总结了我们的选择及其背后的原因。
日志记录是可观察性中看似简单的部分,但却会迅速导致复杂的决策。 它很简单,因为您只需从容器中收集日志输出,但很复杂,因为您需要决定在哪里存储数据、如何将数据传输到该存储、如何索引数据以使其有用以及保留数据多长时间。 为了实用,日志文件需要能够根据足够多的不同标准轻松搜索,以满足不同搜索者的需求。
我们研究了 OpenTelemetry Collector 的日志支持,在撰写本文时,它还处于测试阶段。 我们决定短期内使用ELK进行日志记录,同时继续研究其他选项。
如果没有任何令人信服的理由,我们默认使用Elasticsearch工具,这使我们能够将部署分为摄取、协调、主和数据节点。 为了更容易部署,我们使用了Bitnami 图表。 为了传输数据,我们将Filebeat作为 Kubernetes DaemonSet 的一部分进行部署。 通过部署Kibana并利用预加载的索引、搜索、可视化和仪表板添加了搜索功能。
很快就发现,虽然这个解决方案有效,但默认配置非常耗费资源,这使得它很难在资源占用较小的环境(如K3S或Microk8s)中运行。 增加调整每个组件副本数量的功能可以解决这个问题,但是确实导致了一些可能由于资源耗尽或数据量过多而导致的故障。
我们非但没有对此感到失望,反而将其视为一个机会,利用不同的配置对我们的日志系统进行基准测试,并研究其他选项,如Grafana Loki和Graylog 。 我们可能会发现,轻量级的日志解决方案无法提供某些用户需要的全套功能,而这些功能只能从耗费更多资源的工具中获取。 鉴于MARA的模块化特性,我们可能会为这些选项构建额外的模块,以便为用户提供更多选择。
除了确定哪种工具提供我们想要的跟踪功能之外,我们还需要考虑如何实施解决方案以及需要与之集成哪些技术。
首先,我们要确保任何仪器都不会对应用本身的服务质量产生负面影响。 我们所有人都曾使用过这样的系统,在导出日志时,系统的性能每小时会出现一次可预测的下降,我们不想再经历这样的经历。 OpenTelemetry Collector 的架构在这方面非常引人注目,因为您可以在每个主机上运行一个实例。 每个收集器从主机上运行的所有不同应用(容器化或其他)中的客户端和代理接收数据。 收集器在将数据发送到存储后端之前会对其进行聚合并可能压缩,这听起来很理想。
接下来,我们评估了应用中使用的不同编程语言和框架对 OpenTelemetry 的支持。 事情变得有点棘手了。 尽管仅使用表中所示的两种编程语言和相关框架,但复杂程度却出奇的高。
语言 | 框架 | 服务数量 |
---|---|---|
Java | Spring Boot | 3 |
Python | 烧瓶 | 3 |
为了添加语言级别的跟踪,我们首先尝试了 OpenTelemetry 的自动检测代理,但发现它们的输出混乱且令人困惑。 我们确信,随着自动检测库的成熟,这种情况会有所改善,但目前我们排除了 OpenTelemetry 代理并决定将跟踪编织到我们的代码中。
在直接在代码中实现跟踪之前,我们首先连接 OpenTelemetry Collector 以将所有跟踪数据输出到本地运行的 Jaeger 实例,在那里我们可以更轻松地查看输出。 这非常有用,因为我们可以通过跟踪数据的视觉呈现来弄清楚如何完全集成 OpenTelemetry。 例如,如果我们发现 HTTP 客户端库在调用依赖服务时没有包含跟踪数据,我们会立即将该问题列入修复列表中。 Jaeger 很好地展示了单条轨迹内所有不同跨度:
在我们的 Python 代码中添加跟踪相对简单。 我们添加了两个所有服务引用的 Python 源文件,并更新了相应的requirements.txt文件以包含相关的opentelemetry-instrumentation-*
依赖项。 这意味着我们可以对所有 Python 服务使用相同的跟踪配置,并在日志消息中包含每个请求的跟踪 ID,并将跟踪 ID 嵌入对依赖服务的请求中。
接下来,我们将注意力转向 Java 服务。 在 greenfield 项目中直接使用 OpenTelemetry Java 库相对简单——您需要做的就是导入必要的库并直接使用跟踪 API 。 但是,如果您像我们一样使用 Spring,那么您需要做出其他决定。
Spring 已经有一个分布式跟踪 API,即 Spring Cloud Sleuth 。 它在底层分布式跟踪实现上提供了一个外观,执行以下操作,如文档中所述:
spring-cloud-sleuth-zipkin
可用,... [生成并报告] 通过 HTTP 兼容 Zipkin 的跟踪。 默认情况下,它将它们发送到本地主机(端口 9411)上的 Zipkin 收集器服务。 使用spring.zipkin.baseUrl
配置服务的位置。该 API 还允许我们向@Scheduled
注释的任务添加跟踪。
换句话说,仅使用 Spring Cloud Sleuth,我们就可以获得开箱即用的 HTTP 服务端点级别的跟踪,这是一个很好的好处。 由于我们的项目已经使用 Spring,我们决定将所有内容保留在该框架内并利用所提供的功能。 然而,当我们用Maven将所有内容连接在一起时,我们发现了一些问题:
opentelemetry-instrumentation-api
的过时 alpha 版本。 目前尚无该库的最新非 alpha 1.x版本。spring-cloud-build
。这使得我们的 Maven 项目定义变得更加复杂,因为我们必须从 Spring 存储库以及 Maven Central 中提取 Maven,这清楚地表明了 Spring Cloud 中 OpenTelemetry 支持还处于早期阶段。 尽管如此,我们仍继续前进并创建了一个通用遥测模块,以使用 Spring Cloud Sleuth 和 OpenTelemetry 配置分布式跟踪,并配备了各种与遥测相关的辅助函数和扩展。
在通用遥测模块中,我们通过提供以下内容扩展了 Spring Cloud Sleuth 和 OpenTelemetry 库提供的跟踪功能:
自动配置
类,为项目设置跟踪和扩展功能,并加载额外的跟踪资源属性。此外,我们实现了一个由 Apache HTTP 客户端支持的与Spring 兼容的 HTTP 客户端,因为我们希望在服务之间进行 HTTP 调用时获得更多指标和可定制性。 在此实现中,当调用依赖服务时,跟踪和跨度标识符作为标头传入,从而允许它们包含在跟踪输出中。 此外,该实现还提供了由 OpenTelemetry 聚合的 HTTP 连接池指标。
总而言之,使用 Spring Cloud Sleuth 和 OpenTelemetry 进行跟踪是一项艰巨的任务,但我们相信这是值得的。 我们希望这个项目和这篇文章能够为其他想要走这条路的人指明方向。
为了获取连接请求整个生命周期内所有服务的跟踪信息,我们需要将 OpenTelemetry 集成到 NGINX 中。为此,我们使用了OpenTelemetry NGINX 模块(仍处于测试阶段)。 由于预计获取适用于所有版本 NGINX 的模块工作二进制文件可能会有一定困难,因此我们创建了一个GitHub 存储库,其中包含不受支持的 NGINX 模块的容器镜像。 我们每晚运行构建并通过易于导入的 Docker 镜像分发模块二进制文件。
我们尚未将此过程集成到MARA 项目中NGINX Ingress Controller的构建过程中,但我们计划很快这样做。
完成 OpenTelemetry 的跟踪集成后,接下来我们关注指标。 我们的基于 Python 的应用没有现有的指标,因此我们决定暂时推迟添加它们。 对于 Java应用,原始Bank of Anthos源代码使用Micrometer与 Google Cloud 的Stackdriver结合来支持指标。 然而,在分叉 Bank of Anthos 之后,我们从 Bank of Sirius 中删除了该代码,因为它不允许我们配置指标后端。 尽管如此,指标挂钩已经存在的事实表明需要进行适当的指标集成。
为了提出一个可配置且实用的解决方案,我们首先研究了OpenTelemetry Java 库和 Micrometer 中的度量支持。 通过对这些技术进行比较的搜索,许多结果列举了使用 OpenTelemetry 作为 JVM 中的指标 API 的缺点,尽管在撰写本文时 OpenTelemetry 指标仍处于 alpha 阶段。 Micrometer 是 JVM 的一个成熟的指标外观层,类似于slf4j ,它提供一个通用 API 包装器,该包装器面向可配置的指标实现,而不是它自己的指标实现。 有趣的是,它是 Spring 的默认指标 API。
此时,我们正在权衡以下事实:
经过几次实验后,我们决定最实用的方法是使用带有 Prometheus 支持实现的 Micrometer 外观,并配置 OpenTelemetry Collector 以使用 Prometheus API 从应用中提取指标。 我们从大量文章中了解到,OpenTelemetry 中缺少指标类型可能会导致问题,但我们的用例不需要这些类型,因此这种折衷是可以接受的。
我们发现了有关 OpenTelemetry Collector 的一个有趣的事情:即使我们已将其配置为通过 OTLP 接收跟踪并通过 Prometheus API 接收指标,它仍然可以配置为使用 OTLP 或任何其他支持的协议将这两种类型的数据发送到外部数据接收器。 这使我们能够轻松地使用LightStep尝试我们的应用。
总的来说,用 Java 编写指标相当简单,因为我们编写它们符合 Micrometer API,该 API 有大量可用的示例和教程。 对于指标和跟踪而言,最困难的事情可能是在pom.xml文件中正确获取telemetry-common 的
Maven 依赖关系图。
OpenTelemetry 项目本身并不包括错误聚合,并且它不像Sentry或Honeybadger.io等解决方案那样提供优雅的错误标记实现。 尽管如此,我们还是决定短期内使用 OpenTelemetry 进行错误聚合,而不是添加其他工具。 使用 Jaeger 这样的工具,我们可以搜索error=true
来查找所有具有错误情况的跟踪。 这至少让我们了解了通常出现的问题。 将来,我们可能会考虑添加Sentry集成。
在我们的应用环境中,健康检查让 Kubernetes 知道服务是否健康或是否已完成启动阶段。 如果服务不健康,可以配置 Kubernetes 以终止或重新启动实例。 在我们的应用中,我们决定不使用 OpenTelemetry 健康检查,因为我们发现文档不足。
相反,对于 JVM 服务,我们使用一个名为Spring Boot Actuator的 Spring 项目,它不仅提供健康检查端点,还提供运行时自省和堆转储端点。 对于 Python 服务,我们使用Flask Management Endpoints 模块,它提供了 Spring Boot Actuator 功能的一个子集。 目前它仅提供可定制的应用信息和健康检查。
Spring Boot Actuator 挂接到 JVM 和 Spring 以提供自省、监控和健康检查端点。 此外,它还提供了一个框架,用于将自定义信息添加到其端点呈现的默认数据中。 端点提供对缓存状态、运行时环境、数据库迁移、健康检查、可定制应用信息、指标、定期作业、HTTP 会话状态和线程转储等内容的运行时自省。
Spring Boot Actuator 实现的健康检查端点具有模块化配置,使得服务的健康状况可以由多个单独的检查组成,这些检查被归类为活跃性或就绪性。 还可以进行显示所有检查模块的完整健康检查,通常如下所示。
信息端点在 JSON 文档中定义为单个高级 JSON 对象和一系列分层键和值。 通常,该文档指定服务名称和版本、架构、主机名、操作系统信息、进程 ID、可执行文件名称以及有关主机的详细信息(例如机器 ID 或唯一服务 ID)。
您可能还记得,在“工具功能与愿望清单”的比较表中,没有任何工具支持运行时自省或堆/核心转储。 然而,Spring作为我们的底层框架支持这两者——尽管将可观察性特性连接到应用中需要一些工作。 如上一节所述,对于运行时自省,我们将 Python 模块与 Spring Boot Actuator 结合使用。
同样,对于堆转储,我们使用 Spring Boot Actuator 提供的线程转储端点来完成我们想要的部分功能。 我们无法按需获取核心转储,也无法以理想的细粒度级别获取 JVM 的堆转储,但我们只需付出很少的额外努力就可以获得一些所需的功能。 不幸的是,Python 服务的核心转储需要大量额外工作,我们已将其推迟到以后进行。
经过多次哭泣和质疑我们的人生选择之后,我们决定在 MARA 中使用以下技术实现可观察性(以下, OTel代表 OpenTelemetry):
错误
的跟踪此实现是时间的一个快照。 随着发展的不断进行,它肯定会发生变化和演变。 很快我们将对该应用进行广泛的负载测试。 我们希望了解我们的可观察性方法的缺点并添加额外的可观察性功能。
请尝试现代应用程序参考架构和示例应用(Sirius 银行)。 如果您对我们如何做得更好有任何想法,我们欢迎您在我们的GitHub repo上做出贡献!
这篇文章是系列文章的一部分。 随着我们不断增加 MARA 的功能,我们会在博客上发布详细信息:
“这篇博文可能引用了不再可用和/或不再支持的产品。 有关 F5 NGINX 产品和解决方案的最新信息,请探索我们的NGINX 产品系列。 NGINX 现在是 F5 的一部分。 所有之前的 NGINX.com 链接都将重定向至 F5.com 上的类似 NGINX 内容。”