OpenTelemetry Go SDK 是替代已归档 opentracing-go 的标准方案;K8s 链路断裂主因是 exporter endpoint 配置未适配服务发现,如误用 localhost 而非 otel-collector.default.svc.cluster.local,且必须严格匹配协议、host、port 和路径(如 http://otel-collector.default.svc.cluster.local:4318/v1/traces)。

别用 opentracing-go 了,它已归档,collector 不再接收其数据,升级路径断裂,新项目直接切 OpenTelemetry Go SDK。
为什么本地能看到 trace,上 K8s 就断链
根本不是代码问题,而是 exporter endpoint 配置没对齐环境。本地直连 localhost:4317 能通,K8s 里服务发现走的是 otel-collector.default.svc.cluster.local:4317,但你的代码或环境变量还写着 localhost 或硬编码 IP。
-
OTEL_EXPORTER_OTLP_ENDPOINT必须写全协议 + host + port + path,例如http://otel-collector.default.svc.cluster.local:4318/v1/traces(注意路径/v1/traces) - 用
otlphttp.NewClient时 endpoint 缺路径会静默失败;用otlptracegrpc.New则默认走/v1/traces,但需确保 collector 的 gRPC receiver 已启用 - K8s 中 collector 的 service name、namespace、port name 都要和 client 配置严格一致,DNS 解析失败不会报错,只会丢 span
HTTP handler 怎么自动注入 trace context
otelhttp.NewHandler 只负责入向解析,不处理出向透传——这是最常被误解的一点。它从 traceparent header 提取上下文并注入到 req.Context(),但不会帮你把当前 span 注入下游 HTTP 请求头。
- 入向:必须用
otelhttp.NewHandler(yourMux, "/")包裹最终 handler,不能包在其他中间件之后(比如不能包在 Gin 的gin.Recovery()外层) - 出向:所有下游 HTTP 调用必须用
otelhttp.NewClient构建的 client,或显式包装http.DefaultClient.Transport = otelhttp.NewTransport(http.DefaultTransport) - 别信 “Gin 自动集成” 第三方包,90% 没重写
Context()透传逻辑,实际仍用原始req.Context(),导致下游收不到traceparent
数据库调用为什么没出现在 trace 里
因为 database/sql 原生不读 context 里的 span,db.QueryContext(ctx, ...) 这个 ctx 参数只是控制超时,不是为了埋点。
立即学习“go语言免费学习笔记(深入)”;
- 必须用
go.opentelemetry.io/contrib/instrumentation/database/sql替换 driver,且显式调otel.WrapDriver(driver, driverName) - PostgreSQL 用户若用
pgx,得配pgxpool.Config.AfterConnect注入 tracer,或直接用pgxpool.WithTracer(&tracing.Tracer{}) - ORM 如 GORM v2+ 支持
Config.Plugin,但需手动注册otelgorm.NewPlugin(),否则仍是黑盒 - 忘记 wrap driver 的典型表现:HTTP handler 有 span,但里面
rows, _ := db.QueryContext(ctx, "SELECT ...")这一行之后就断了,后续无子 span
goroutine 启动后 trace 断掉怎么办
Go 的 context 不跨 goroutine 自动传播,闭包捕获的外层 ctx 无法携带 span 状态,除非你显式注入。
- 启动新 goroutine 时,必须用
trace.ContextWithSpan(ctx, span)生成带 span 的新 context,再传进去:go doWork(trace.ContextWithSpan(ctx, span)) - 不能只传
ctx或用context.Background(),否则tracer.Start(ctx, ...)会创建孤立 root span - 异步任务(如消息消费、定时 job)返回后,
span.End()前要确认仍在同一 context 下,否则 span 会提前结束或丢失父子关系 - 单元测试里
span常为nil,因为没初始化TracerProvider,defer span.End()会 panic,加if span != nil { defer span.End() }
最易被忽略的点:span 生命周期完全绑定 context 传递链,而 context 一旦在某处没被显式向下传(哪怕只漏一层中间件、一个 goroutine、一次 db 调用),整条链就碎成独立片段——它不报错,也不警告,只安静地失去意义。


















