使用 OpenTelemetry OpAMP 动态修改服务遥测

博客文章在发布后不会更新。这篇文章已经发布一年多了,其内容可能已过时,部分链接可能无效。在依赖任何信息之前,请务必核实。

您的服务遥测应该有多详细?服务是否应该 100% 的时间输出所有跟踪、指标和日志?服务流量的多少应该被采样?我想建议的答案是“这取决于”。服务在其生命周期中,从开发到持续部署,所需的遥测数据是不同的。当客户遇到错误或服务面临大规模扩展时,这些数据也会随之变化。

可以更改服务遥测配置或采样率。通常,这只需要最小的代码更改和部署过程。这可能看起来不算什么,但当整个系统面临这样的更改时,我们往往会避免它。相反,通常的做法是收集尽可能多的数据,而这本身就会造成问题。我们能否在没有这些障碍的情况下动态修改服务遥测?感谢 OpAMP 协议及其背后的人们,我相信答案即将改变。

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

在下面的示例中,我们将创建一个简单的 GoLang 服务器,对其进行插桩,然后使用 OpAMP 服务器和 Supervisor 来控制它。我们不会深入研究 OpAMP 本身,而是通过这些示例来关注它的应用。

首先,考虑这个基本的 go 服务器

package main

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

func httpHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hi! This action could create a trace!")
}

func main() {
	handler := http.HandlerFunc(httpHandler)
	http.Handle("/", handler)
	fmt.Println("Starting server on port 8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

接下来,添加一个名为 effective.yaml 的基本配置文件。将其放在与 main.go 文件相同的文件夹中,并进行以下配置

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, "Hi! This action could create a trace!")
}

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("Starting server on port 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, "Hi! This action could create a trace!")
}

func setHandler(handler http.Handler, config configurations) http.Handler {
   if config.Instrument {
      return otelhttp.NewHandler(handler, "instrumentation activated by 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("Starting server on port 8080")
   log.Fatal(http.ListenAndServe(":8080", nil))
}

构建并运行此应用程序

go build .
go run .

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

cd internal/examples/server
go run .

访问 https://:4321 来验证服务器是否正在运行。请注意,没有显示任何代理

No agents display on opamp server demo UI

接下来,编辑 internal/examples/supervisor/bin/supervisor.yaml 文件,使其指向我们的代理。它应该如下所示

server:
  endpoint: ws://127.0.0.1:4320/v1/opamp
agent:
  executable: <absolute|relative path to previous build>

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

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

现在我们有一个由 OpAMP 服务器 Supervisor 和我们的服务器组成的系统

OpAMP server, supervisor and agent relations

通过 Supervisor,我们现在可以在 https://:4321 上看到我们的代理正在运行。选择它,然后将其配置设置为 instrument: true

Our service configurations over opamp server

您可以在 Supervisor 的控制台日志中看到更改

Received remote config from server, hash=0008886301f3ccb3520216823cfa09a.
Effective config changed.
Config is changed. Signal to restart the agent.
Restarting the agent with the new config.
Stopping agent process, PID=19206
Agent process PID=19206 successfully stopped.
Starting agent <agent path>
Agent process started, PID=19506

最后,访问 https://:8080。跟踪信息现在应该会出现在 internal/examples/supervisor/bin/agent.log 中。

Starting server on port 8080
{
   "Name": "instrumentation activated by OpAMP server",
   "SpanContext": {
      "TraceID": "d2f76958023624d4c1def3f44899b6d4",
      "SpanID": "085510f551dc31a1",
      "TraceFlags": "01",
      "TraceState": "",
      "Remote":false
   ...

这些行就是跟踪本身!

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

这是一个非常基础的实现。将 OpAMP 包装到系统中可以作为一个插桩协调器。起点是能够外部添加和匹配为您系统量身定制的动态遥测。想象一下 AI 在这种系统上能取得什么成就。它可以收集整个系统的指标,自动动态地添加跟踪/日志收集到任何检测到的瓶颈上。使用此协议可以实现许多新的可能性,我相信它有潜力改变我们对遥测的看法。