掘金 后端 ( ) • 2024-04-19 20:48

作者:来自 Elastic Damien Mathieu

正如我们之前所分享的,Elastic 致力于帮助 OpenTelemetry(OTel)取得成功,这意味着在某些情况下构建语言 SDK 的分发版本。

Elastic 在观察性和安全数据收集方面战略性地选择了 OTel 标准。此外,Elastic 承诺与 OTel 社区合作,成为观察性生态系统中最佳的数据收集基础设施。Elastic 正在加深与 OTel 的合作关系,超越了最近将 Elastic Common Schema(ECS)贡献给 OpenTelemetry在 OTel Java 代理中调用动态语言技术(invokedynamic)以及即将捐赠的分析代理

自 Elastic 版本 7.14 起,Elastic 已通过能够直接接收基于 OpenTelemetry 协议(OTLP)的跟踪、度量和日志,原生支持 OTel。

与其他语言 SDK 不同,Go SDK 稍有不同,因为 Go 语言本身缺乏允许构建非分支(not a fork)分发的动态性。

然而,缺乏分发并不意味着你不应该使用 OTel 从 Go 应用程序收集数据到 Elastic Stack。

Elastic 目前有一个 APM Go 代理,但我们建议切换到 OTel Go SDK。在本文中,我们将介绍两种迁移方式:

  1. 通过替换应用程序代码中的所有遥测数据(“一次性大规模迁移”)并发布更改
  2. 通过将迁移拆分成原子更改,以减少回归风险

一次性大规模迁移

从我们的 APM Go agent 迁移到 OTel SDK 的最简单方法可能是移除代理提供的所有遥测数据,并用新的 SDK 替换它们。

自动化检测

你的大部分检测可能是自动进行的,因为它是你所使用的框架或库的一部分。

例如,如果你使用 Elastic Go agent,你可能像这样使用我们的 net/http 自动化检测模块:



1.  import (
2.  	"net/http"
3.  	"go.elastic.co/apm/module/apmhttp/v2"
4.  )

7.  func handler(w http.ResponseWriter, req *http.Request) {
8.  	fmt.Fprintf(w, "Hello World!")
9.  }

11.  func main() {
12.      http.ListenAndServe(
13.          ":8080",
14.          apmhttp.Wrap(http.HandlerFunc(handler)),
15.      )
16.  }


使用 OpenTelemetry,你将改用 otelhttp 模块:



1.  import (
2.  	"net/http"
3.  	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
4.  )

7.  func handler(w http.ResponseWriter, req *http.Request) {
8.  	fmt.Fprintf(w, "Hello World!")
9.  }

11.  func main() {
12.      http.ListenAndServe(
13.          ":8080",
14.          otelhttp.NewHandler(http.HandlerFunc(handler), "http"),
15.      )
16.  }


你应该对你从我们的代理使用的每个其他模块执行相同的更改。

手动检测

你的应用程序可能还有手动检测部分,这些部分是通过调用 Elastic APM agent API 直接在应用程序代码中创建跟踪和跨度(spans)的。

你可能会使用 Elastic 的 APM SDK 创建 transactions 和 spans,就像这样:



1.  import (
2.  	"go.elastic.co/apm/v2"
3.  )

5.  func main() {
6.      // Create a transaction, and assign it to the context.
7.      tx :=  apm.DefaultTracer().StartTransaction("GET /", "request")
8.      defer tx.End()
9.      ctx = apm.ContextWithTransaction(ctx, tx)

11.      // Create a span
12.      span, ctx := apm.StartSpan(ctx, "span")
13.      defer span.End()
14.  }


在 OpenTelemetry 中,无论是 transactions 还是 spans,都使用同一套 API —— 在 OTel 中,Elastic 视为 “transactions” 的内容,仅仅是没有父级的 spans(“根 spans”)。

因此,你的检测代码将变更为以下内容:



1.  import (
2.  	"go.opentelemetry.io/otel/trace"
3.  )

5.  func main() {
6.  	tracer := otel.Tracer("my library")

8.      // Create a root span.
9.      // It is assigned to the returned context automatically.
10.      ctx, span := tracer.Start(ctx, "GET /")
11.      defer span.End()

13.      // Create a child span (as the context has a parent).
14.      ctx, span := tracer.Start(ctx, "span")
15.      defer span.End()
16.  }


在进行一次性大迁移时,你需要在发布到生产环境之前迁移所有内容。你不能将迁移过程拆分成小块进行。

对于小型应用程序或只使用自动化检测的应用程序来说,这种限制可能是可以接受的。它允许你快速验证迁移并继续前进。

然而,如果你在处理一组复杂的服务、一个大型应用程序或一个有大量手动检测的应用程序,你可能希望能够在迁移过程中多次发布代码,而不是一次性全部完成。

分步迁移

分步迁移是一种你可以逐步发布原子性改变并保持应用程序正常工作的方式。然后,你可以在最后,当你准备好时,才进行最终的切换。

为了帮助进行分步迁移,我们提供了我们的 APM Go agent 和 OpenTelemetry 之间的桥梁

这座桥梁允许你同时运行我们的 agent 和 OTel,并且可以在同一个进程中使用这两个库的检测,数据将被传输到相同的位置并以相同的格式。

你可以像这样配置我们的代理与 OTel 的桥接 (bridge):



1.  import (
2.  	"go.elastic.co/apm/v2"
3.  	"go.elastic.co/apm/module/apmotel/v2"

5.  	"go.opentelemetry.io/otel"
6.  )

8.  func main() {
9.  	provider, err := apmotel.NewTracerProvider()
10.  	if err != nil {
11.  		log.Fatal(err)
12.  	}
13.  	otel.SetTracerProvider(provider)
14.  }


一旦设置了这个配置,OTel 创建的每个 span 都将被传输到 Elastic APM agent。

有了这个桥梁,你可以通过以下过程使迁移更加安全:

  • 将桥接器添加到你的应用程序中。
  • 逐个切换一个检测(自动化或手动)从代理到 OpenTelemetry,就像上面的一次性大迁移一样,但一次只进行一个。
    • 重复以上步骤,直到所有内容都已迁移。
  • 删除桥梁和我们的代理,并配置 OpenTelemetry 通过其 SDK 传输数据。

这些步骤中的每一个都可以作为应用程序中的一个单一更改,并立即投入生产。

如果在迁移过程中出现任何问题,你应该能够立即看到并修复它,然后再继续进行。

使用 OTel 构建可观察性的好处

由于 OTel 迅速成为行业标准,并且 Elastic 致力于使其变得更好,因此对工程团队来说迁移到 OTel 可能会带来很多好处。

在 Go 中,无论是通过一次性大规模迁移还是使用 Elastic 的 OTel 桥梁,这样做都将使您能够受益于全球社区维护的仪器化工具,从而使您的可观察性更加有效,并更好地了解应用程序中发生的情况。

本文中描述的任何特性或功能的发布和时间安排均由 Elastic 自行决定。 当前不可用的任何特性或功能可能无法按时交付或根本无法交付。

原文:Migrating from Elastic’s Go APM agent to OpenTelemetry Go SDK | Elastic Blog