Go Web 应用的 instrumentation

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

在这篇博文中,您将亲手学习如何在没有任何先验知识的情况下,使用 OpenTelemetry Go 创建和可视化跟踪。

我们将从创建一个使用 Mongo 和 Gin 框架的简单待办事项应用开始。然后,我们将把跟踪数据发送到 Jaeger Tracing 进行可视化。您可以在这个 GitHub 仓库 中找到所有相关文件。

OpenTelemetry Go - The Mandalorian

Hello world: OpenTelemetry Go 示例

我们将从创建我们的待办事项服务开始,并安装两个库(Gin 和 Mongo)来理解 instrumentation 的工作原理。

步骤 1:为我们的待办事项应用程序创建 main.go 文件

  1. 安装 Gin 和 Mongo-driver

    go get -u github.com/gin-gonic/gin
    go get go.mongodb.org/mongo-driver/mongo
    
  2. 设置 gin 和 mongo 以监听“/todo”

  3. 创建一些待办事项来填充 Mongo

    package main
    import (
        "context"
        "net/http"
        "github.com/gin-gonic/gin"
        "go.mongodb.org/mongo-driver/bson"
        "go.mongodb.org/mongo-driver/mongo"
        "go.mongodb.org/mongo-driver/mongo/options"
    )
    
    var client * mongo.Client
    
    func main() {
        connectMongo()
        setupWebServer()
    }
    
    func connectMongo() {
        opts: = options.Client()
        opts.ApplyURI("mongodb://:27017")
        client, _ = mongo.Connect(context.Background(), opts)
        //Seed the database with todo's
        docs: = [] interface {} {
            bson.D {
                    {
                        "id", "1"
                    }, {
                        "title", "Buy groceries"
                    }
                },
                bson.D {
                    {
                        "id", "2"
                    }, {
                        "title", "install Aspecto.io"
                    }
                },
                bson.D {
                    {
                        "id", "3"
                    }, {
                        "title", "Buy dogz.io domain"
                    }
                },
        }
        client.Database("todo").Collection("todos").InsertMany(context.Background(), docs)
    }
    
    func setupWebServer() {
        r: = gin.Default()
        r.GET("/todo", func(c * gin.Context) {
            collection: = client.Database("todo").Collection("todos")
            //Important: Make sure to pass c.Request.Context() as the context and not c itself - TBD
            cur, findErr: = collection.Find(c.Request.Context(), bson.D {})
            if findErr != nil {
                c.AbortWithError(500, findErr)
                return
            }
            results: = make([] interface {}, 0)
            curErr: = cur.All(c, & results)
            if curErr != nil {
                c.AbortWithError(500, curErr)
                return
            }
            c.JSON(http.StatusOK, results)
        })
        _ = r.Run(":8080")
    }
    

现在我们的待办事项小应用已经准备好了,让我们引入 OpenTelemetry。

步骤 2:安装 OpenTelemetry Go

我们将配置 OpenTelemetry 来 instrumentation 我们的 Go 应用。

  1. 要安装 OTel SDK,请运行

    go get go.opentelemetry.io/otel /
    go.opentelemetry.io/otel/sdk /
    
  2. instrumentation 我们的 Gin 和 Mongo 库以生成跟踪。

  3. Gin & Mongo instrumentation: 安装 otelgin & otelmongo

    go get go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin /
    go get go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo
    

Gin instrumentation: gin.Context

我们之前讨论过上下文传播的概念——即在分布式服务之间传递元数据以关联我们系统中事件的方式。

Gin 框架有自己的类型 gin.Context,它作为参数传递给 HTTP 处理程序。但是,应该向下传递给 mongo 操作的上下文是标准 Go 库 Context 对象,它可以在 gin.Context.Request.Context 中找到。

//Make sure to pass c.Request.Context() as the context and not c itself
cur, findErr := collection.Find(c.Request.Context(), bson.D{})

因此,请确保将 Context 传递给 MongoDB 操作。有关更多信息,请查看此 [issue](https://github.com/gin-gonic/gin/issues/1335)。

现在我们的待办事项应用已经准备好并且进行了 instrumentation。是时候充分利用 OpenTelemetry 了。我们可视化跟踪的能力正是这项技术真正的故障排除力量所在。

为了可视化,我们将使用开源的 Jaeger Tracing。

使用 Jaeger 进行可视化

OpenTelemetry Go 和 Jaeger Tracing:将跟踪导出到 Jaeger

[Jaeger Tracing](https://www.aspecto.io/blog/jaeger-tracing-the-ultimate-guide/) 是一个开源项目套件,负责管理整个分布式跟踪“堆栈”:客户端、收集器和 UI。Jaeger UI 是最常用的开源工具,用于可视化跟踪。

设置如下所示

  1. 安装 Jaeger exporter

    go get go.opentelemetry.io/otel/exporters/jaeger
    
  2. 创建一个 tracing 文件夹和一个 jaeger.go 文件

  3. 将以下代码添加到文件中

    package tracing
    import (
        "go.opentelemetry.io/otel/exporters/jaeger"
        "go.opentelemetry.io/otel/sdk/resource"
        sdktrace "go.opentelemetry.io/otel/sdk/trace"
        semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
    )
    
    func JaegerTracerProvider()(*sdktrace.TracerProvider, error) {
        exp, err: = jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("https://:14268/api/traces")))
        if err != nil {
            return nil, err
        }
        tp: = sdktrace.NewTracerProvider(
            sdktrace.WithBatcher(exp),
            sdktrace.WithResource(resource.NewWithAttributes(
                semconv.SchemaURL,
                semconv.ServiceNameKey.String("todo-service"),
                semconv.DeploymentEnvironmentKey.String("production"),
            )),
        )
        return tp, nil
    }
    
  4. 回到 main.go 文件,修改我们的代码以使用我们刚刚创建的 JaegerTracerProvider 函数

    func main() {
        tp, tpErr: = tracing.JaegerTracerProvider()
        if tpErr != nil {
            log.Fatal(tpErr)
        }
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext {}, propagation.Baggage {}))
        connectMongo()
        setupWebServer()
    }
    

    接下来,我们将连接我们安装的 instrumentations。

  5. 添加 Mongo instrumentation。在我们的 connectMongo 函数中,通过添加这一行

    opts.Monitor = otelmongo.NewMonitor()
    

    函数应该如下所示

    func connectMongo() {
        opts: = options.Client()
        //Mongo OpenTelemetry instrumentation
        opts.Monitor = otelmongo.NewMonitor()
        opts.ApplyURI("mongodb://:27017")
        client, _ = mongo.Connect(context.Background(), opts)
        //Seed the database with some todo's
        docs: = [] interface {} {
            bson.D {
                    {
                        "id", "1"
                    }, {
                        "title", "Buy groceries"
                    }
                },
                bson.D {
                    {
                        "id", "2"
                    }, {
                        "title", "install Aspecto.io"
                    }
                },
                bson.D {
                    {
                        "id", "3"
                    }, {
                        "title", "Buy dogz.io domain"
                    }
                },
        }
        client.Database("todo").Collection("todos").InsertMany(context.Background(), docs)
    }
    

    现在,添加 Gin instrumentation。

  6. 转到 startWebServer 函数,在创建 gin 实例后立即添加这一行

    r.Use(otelgin.Middleware("todo-service"))
    

    函数应该如下所示

    func startWebServer() {
        r: = gin.Default()
        //Gin OpenTelemetry instrumentation
        r.Use(otelgin.Middleware("todo-service"))
        r.GET("/todo", func(c * gin.Context) {
            collection: = client.Database("todo").Collection("todos")
            //make sure to pass c.Request.Context() as the context and not c itself
            cur, findErr: = collection.Find(c.Request.Context(), bson.D {})
            if findErr != nil {
                c.AbortWithError(500, findErr)
                return
            }
            results: = make([] interface {}, 0)
            curErr: = cur.All(c, & results)
            if curErr != nil {
                c.AbortWithError(500, curErr)
                return
            }
            c.JSON(http.StatusOK, results)
        })
        _ = r.Run(":8080")
    }
    

    有关完整的 `main.go` 文件,请参阅下文。现在我们终于可以导出到 Jaeger 了。

    package main
    import (
        "context"
        "log"
        "net/http"
        "github.com/aspecto-io/opentelemetry-examples/tracing"
        "github.com/gin-gonic/gin"
        "go.mongodb.org/mongo-driver/bson"
        "go.mongodb.org/mongo-driver/mongo"
        "go.mongodb.org/mongo-driver/mongo/options"
        "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
        "go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo"
        "go.opentelemetry.io/otel"
        "go.opentelemetry.io/otel/propagation"
    )
    
    var client * mongo.Client
    
    func main() {
        //Export traces to Jaeger
        tp, tpErr: = tracing.JaegerTracerProvider()
        if tpErr != nil {
            log.Fatal(tpErr)
        }
        otel.SetTracerProvider(tp)
        otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext {}, propagation.Baggage {}))
        connectMongo()
        startWebServer()
    }
    
    func connectMongo() {
        opts: = options.Client()
        //Mongo OpenTelemetry instrumentation
        opts.Monitor = otelmongo.NewMonitor()
        opts.ApplyURI("mongodb://:27017")
        client, _ = mongo.Connect(context.Background(), opts)
        //Seed the database with some todo's
        docs: = [] interface {} {
            bson.D {
                    {
                        "id", "1"
                    }, {
                        "title", "Buy groceries"
                    }
                },
                bson.D {
                    {
                        "id", "2"
                    }, {
                        "title", "install Aspecto.io"
                    }
                },
                bson.D {
                    {
                        "id", "3"
                    }, {
                        "title", "Buy dogz.io domain"
                    }
                },
        }
        client.Database("todo").Collection("todos").InsertMany(context.Background(), docs)
    }
    
    func startWebServer() {
        r: = gin.Default()
        //gin OpenTelemetry instrumentation
        r.Use(otelgin.Middleware("todo-service"))
        r.GET("/todo", func(c * gin.Context) {
            collection: = client.Database("todo").Collection("todos")
            //Make sure to pass c.Request.Context() as the context and not c itself
            cur, findErr: = collection.Find(c.Request.Context(), bson.D {})
            if findErr != nil {
                c.AbortWithError(500, findErr)
                return
            }
            results: = make([] interface {}, 0)
            curErr: = cur.All(c, & results)
            if curErr != nil {
                c.AbortWithError(500, curErr)
                return
            }
            c.JSON(http.StatusOK, results)
        })
        _ = r.Run(":8080")
    }
    

导出跟踪到 Jaeger

  1. 使用 `go run main.go` 运行 todo-service。
  2. 要生成一些跟踪,请向 https://:8080/todo 发送 HTTP GET 请求。
  3. 要查看跟踪,请在 https://:16686/search 打开 Jaeger。

您现在可以看到 Jaeger UI。选择 todo-service 并点击 Find traces。您应该在右侧看到您的跟踪。

Jaeger UI displays opentelemetry traces in go for our todo-service

Jaeger UI 显示了我们的 todo-service 的 OpenTelemetry Go 跟踪。通过点击跟踪,您可以深入了解更多关于它的详细信息,从而使您能够进一步自行调查。

Jaeger UI. To-do service drill down

摘要

这就是全部内容!希望本指南内容丰富且易于遵循。您可以在我们的 GitHub 仓库 中找到所有可用的文件。

本文的一个版本 最初发布 在 Aspecto 博客上。