使用 OpenTelemetry OpAMP 在运行过程中修改服务遥测

你的服务遥测应该有多详细?一个服务是否应该始终输出100%的跟踪、度量和日志?服务流量的多少应该被采样?我想提供的答案是“要看情况”。从开发到持续部署,服务生命周期中所需的遥测数据是不同的。当客户端遇到错误或服务被扩展到一个较大的规模时,遥测数据也会发生变化。

可以通过更改服务的遥测配置或采样率来修改。通常,只需要进行最小的代码更改和部署过程。虽然看起来不是很多,但当需要在整个系统中进行此类更改时,我们往往会避免这样做。相反,通常会尽可能多地收集数据,这本身就会引起问题。我们能否无需这些障碍动态修改服务遥测?感谢 OpAMP 协议及其背后的人们,我相信答案即将改变。

OpAMP 代表 Open Agent Management Protocol。它旨在管理大规模的数据收集代理,其 Go 语言的 实现 正处于 Beta 阶段。它允许进行配置更改以及软件包下载。它定义了 OpAMP 服务器和 OpAMP 客户端之间的通信,但并不假设任何特定的客户端-代理关系,从而使其具有很大的灵活性。

在以下示例中,我们将创建一个简单的 Go 服务器,并对其进行工具化,然后使用 OpAMP 服务器和监控来控制它。我们不会深入研究 OpAMP 的实现细节,而是专注于使用这些示例来探讨其影响。

首先,考虑以下基本的 Go 服务器:

package main

import (
	"fmt"
	"log"
	"net/http"
)

func httpHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "你好!此操作可能会创建一个跟踪!")
}

func main() {
	handler := http.HandlerFunc(httpHandler)
	http.Handle("/", handler)
	fmt.Println("在端口 8080 上启动服务器")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

接下来,在与我们的 main.go 文件相同的文件夹中添加一个名为 effective.yaml 的基本配置文件。并使用以下配置内容:

instrument: false

让我们为我们的服务器添加一个基本的配置处理程序:

package main

import (
   "fmt"
   "gopkg.in/yaml.v3"
   "io/ioutil"
   "log"
   "net/http"
   "path/filepath"
)

type configurations struct {
   Instrument bool
}

func httpHandler(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintf(w, "你好!此操作可能会创建一个跟踪!")
}

func main() {
   filename, _ := filepath.Abs("./effective.yaml")
   yamlFile, _ := ioutil.ReadFile(filename)

   var config configurations
   yaml.Unmarshal(yamlFile, &config)

   handler := http.HandlerFunc(httpHandler)
   http.Handle("/", handler)
   fmt.Println("在端口 8080 上启动服务器")
   log.Fatal(http.ListenAndServe(":8080", nil))
}

接下来,让我们用工具对处理程序进行包装,并根据我们的配置文件进行条件控制,例如:

package main

import (
   "context"
   "fmt"
   "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
   "go.opentelemetry.io/otel"
   "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
   sdktrace "go.opentelemetry.io/otel/sdk/trace"
   "go.opentelemetry.io/otel/trace"
   "gopkg.in/yaml.v3"
   "io/ioutil"
   "log"
   "net/http"
   "os"
   "path/filepath"
)

type configurations struct {
   Instrument bool
}

var tracer trace.Tracer

func newConsoleExporter() (sdktrace.SpanExporter, error) {
   return stdouttrace.New(
      stdouttrace.WithWriter(os.Stdout),
      stdouttrace.WithPrettyPrint(),
   )
}

func httpHandler(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintf(w, "你好!此操作可能会创建一个跟踪!")
}

func setHandler(handler http.Handler, config configurations) http.Handler {
   if config.Instrument {
      return otelhttp.NewHandler(handler, "通过 OpAMP 激活的工具")
   }
   return http.HandlerFunc(httpHandler)
}

func main() {
   filename, _ := filepath.Abs("./effective.yaml")
   yamlFile, _ := ioutil.ReadFile(filename)

   var config configurations
   yaml.Unmarshal(yamlFile, &config)

   exp, _ := newConsoleExporter()
   tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exp))
   defer func() { _ = tp.Shutdown(context.Background()) }()

   otel.SetTracerProvider(tp)

   tracer = tp.Tracer("ControlledOpAMPAgentDemo")
   handler := http.HandlerFunc(httpHandler)
   http.Handle("/", setHandler(handler, config))
   fmt.Println("在端口 8080 上启动服务器")
   log.Fatal(http.ListenAndServe(":8080", nil))
}

构建并运行此应用程序:

go build .
go run .

打开浏览器并访问 http://localhost:8080。将不会显示任何特别的内容。现在是时候添加一些 OpAMP 了。克隆 opamp-go,并使用以下命令运行服务器:

cd internal/examples/server
go run .

访问 http://localhost:4321,以验证服务器正在运行。请注意,没有显示任何代理:

opamp 服务器演示界面上没有显示代理

接下来,编辑 internal/examples/supervisor/bin/supervisor.yaml,将其指向我们的代理。它应该看起来像这样:

server:
  endpoint: ws://127.0.0.1:4320/v1/opamp
agent:
  executable: <前面构建的绝对|相对路径>

然后打开一个新的终端并运行以下命令:

cd internal/examples/supervisor/bin
go build -o ./supervisor ../main.go
./supervisor

我们现在有了一个由 OpAMP 服务器和监控组成的系统:

OpAMP 服务器、监控和代理之间的关系

通过监控,我们现在可以在 http://localhost:4321 上看到我们的代理。选择它并向其配置中传递 instrument: true

opamp 服务器上的服务配置

你可以在监控控制台日志中看到更改内容:

从服务器收到远程配置,哈希值=0008886301f3ccb3520216823cfa09a。
有效配置已更改。
配置已更改。信号以重新启动代理。
使用新配置重新启动代理。
停止代理进程,PID=19206
代理进程 PID=19206 成功停止。
启动代理 <代理路径>
代理进程已启动,PID=19506

最后,访问 http://localhost:8080。现在跟踪应该会显示在 internal/examples/supervisor/bin/agent.log 中。

在端口 8080 上启动服务器
{
   "Name": "通过 OpAMP 服务器激活的工具",
   "SpanContext": {
      "TraceID": "d2f76958023624d4c1def3f44899b6d4",
      "SpanID": "085510f551dc31a1",
      "TraceFlags": "01",
      "TraceState": "",
      "Remote":false
   ...

这些行就是跟踪本身!

总之,我们在这里有一个控制我们的服务是否生成跟踪的服务器。尝试使用 instrument: false 的配置将其关闭。

这只是一个非常基本的实现。在 OpAMP 之上构建一个系统可以充当工具编排器。起点是能够外部添加和匹配为您的系统量身定制的动态遥测。想象一下,在这种类型的系统上,AI 可以实现什么样的功能。它可以自动和动态地在任何检测到的瓶颈上添加跟踪/日志收集,并在整个系统上收集指标。使用这种协议可以带来许多新的可能性,我相信它有改变我们对遥测的看法的潜力。