首页
/ moby OTEL 观测栈实战:用 contrib/otel 演示栈验证 dockerd 的分布式追踪

moby OTEL 观测栈实战:用 contrib/otel 演示栈验证 dockerd 的分布式追踪

2026-09-04 23:55:54作者:滑思眉Philip

contrib/otel 是 moby 仓库内置的一个演示用 Compose 栈,用于端到端验证 Moby(dockerd)的 OpenTelemetry(OTEL)追踪功能:它同时拉起 OTEL Collector、Jaeger 和 Aspire Dashboard 三个服务,让 dockerd 产生的 Trace 可以在两个可视化面板中查看。读完本文,你将掌握这套演示栈的完整配置与启动步骤,并理解 dockerd 侧从环境变量到 TracerProvider 再到 HTTP 协议导出的完整实现链路,从而能自主调通或排查 Moby 的 OTEL 可观测性。

1. 演示栈的组成与设计意图

按照 contrib/otel/README.md 的说明,这个小型 Compose 栈包含三个容器:

  • OTEL Collector:接收 dockerd 上报的 Trace(OTLP 协议);
  • Jaeger:可视化展示 Trace 的追踪系统;
  • Aspire Dashboard:另一个可选的 Trace 可视化面板;

Collector 被配置为把 Traces 同时导出到 Jaeger 和 Aspire 两个后端。目录中的两个核心文件分别是 contrib/otel/compose.yaml(服务编排)和 contrib/otel/otelcol.yaml(Collector 配置)。

2. compose.yaml:三个服务的端口与联动关系

完整的服务编排见 contrib/otel/compose.yaml,各服务的关键配置如下:

服务 镜像 宿主机端口 作用
jaeger jaegertracing/all-in-one:latest 16686 Jaeger UI,可视化 Trace
aspire-dashboard mcr.microsoft.com/dotnet/nightly/aspire-dashboard 18888 Aspire Dashboard UI
otelcol otel/opentelemetry-collector-contrib:latest 4318(OTLP HTTP 默认端口) 接收 Trace 并转发给两个后端

几个值得注意的细节:

  1. Aspire Dashboard 的匿名访问开关。Dashboard 容器通过环境变量 DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS: 'true' 允许匿名访问,这样在本地调试时无需处理 OAuth2 凭据。
  2. 端口映射不对称。宿主机只暴露了 Jaeger 的 16686 和 Aspire 的 18888(UI 端口),而 Collector 对外只暴露 4318(OTLP HTTP 端口)。Collector 到 Jaeger/Aspire 的转发走的是 compose 内部网络,对应 otelcol.yaml 中的 jaeger:4317aspire-dashboard:18889
  3. develop.watch 热加载机制。Compose 文件中为 otelcol 服务配置了:
develop:
  watch:
    - action: sync+restart
      path: ./otelcol.yaml
      target: /etc/otelcol-contrib/config.yaml

这意味着本地修改 otelcol.yaml 后,Compose 会自动把文件同步进容器并重启 Collector 服务,方便在调试 Collector 配置时省去手动拷贝、重启的循环。

3. otelcol.yaml:接收端与双路导出

Collector 的完整配置见 contrib/otel/otelcol.yaml,原文注释明确写着 “Receive signals over gRPC and HTTP, moby currently uses http”,即 moby 当前使用 HTTP 上报,但 Collector 两种协议都开了监听:

# Receive signals over gRPC and HTTP
# moby currently uses http
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

exporters:
  otlp/jaeger:
    endpoint: jaeger:4317
    tls::insecure: true
  otlp/aspire:
    endpoint: aspire-dashboard:18889
    tls::insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [otlp/jaeger, otlp/aspire]
  • receiversotlp 接收器同时监听 4317(gRPC)和 4318(HTTP),demo 栈实际使用的是 HTTP。
  • exporters:两个 otlp/* 导出器分别把 Trace 以 gRPC(OTLP)方式发给 Jaeger(jaeger:4317)和 Aspire Dashboard(aspire-dashboard:18889,注意 Aspire 的 OTLP 监听端口是 18889,与 UI 端口 18888 不同);tls::insecure: true 表示内部网络明文传输,仅适合本地演示。
  • pipelinestraces 管道把 otlp 接收器的数据同时喂给两个导出器,这就是 README 中 “export Traces to both the Jaeger and Aspire containers” 的落地实现。

此外,仓库根目录还有一份 otelcol-ci-config.yml,是 CI 场景下的 Collector 配置:同样监听 4317/4318,但经过 batch 处理器后用 file 导出器落盘为 JSON,并注释说明单文件轮转上限设为 18MB 是因为 “Jaeger will reject file uploads larger than 20MB by default”——这提示了 CI 中把 Trace 文件上传 Jaeger 调试时的体积约束。

4. 启动步骤(继承自原文档)

按照 contrib/otel/README.md “How can I use it?” 一节的完整流程:

  1. 导出覆盖 OTLP 端点的环境变量

    export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
    

    如果在 devcontainer 或其他环境中工作,可能需要调整把这个环境变量传递给 daemon 的方式。

  2. 启动你想抓取 Trace 的 moby 引擎(确保它拿到了上一步声明的环境变量);

  3. 启动 otel 演示栈:在 contrib/otel/ 目录下运行

    docker compose up -d
    
  4. 用 Docker CLI 对引擎发起一些调用,让 daemon 产生 Trace 并发送到端点;

  5. 打开可视化面板:Jaeger 在 http://localhost:16686,Aspire Dashboard 在 http://localhost:18888/traces

  6. 在面板左上角的下拉框中选择 dockerd 即可看到引擎的 Trace。

原文档同时给出一个提示:具体步骤会因你的工作方式(编译二进制后本地运行、在 devcontainer 中运行/调试等)而略有差异。

关于第 1 步的环境变量传递,仓库的 Makefile 中有对应佐证:dev/test 容器启动时通过 -e OTEL_EXPORTER_OTLP_ENDPOINT-e OTEL_EXPORTER_OTLP_PROTOCOL-e OTEL_SERVICE_NAME 将这三个 OTEL 相关变量透传进容器,这正是 devcontainer 工作流下“让 daemon 拿到环境变量”的具体机制。

清理:在 contrib/otel/ 目录运行 docker compose down 即可销毁演示栈;随后可用 unset OTEL_EXPORTER_OTLP_ENDPOINT 清掉环境中的 OTLP 变量。

5. 源码剖析:dockerd 如何产生并导出 Trace

演示栈只是“接收端”,真正决定能否看到 Trace 的是 dockerd 侧的实现。下面按调用链拆解。

5.1 启动阶段:服务名、传播器与 TracerProvider

daemon/command/daemon.go 中,daemon 启动时做了三件关键事情:

  1. 兜底设置 OTEL_SERVICE_NAME:若环境变量未设置,则用可执行文件名(filepath.Base(os.Args[0]))作为服务名——这就是为什么在 Jaeger 中能看到名为 dockerd 的服务。
  2. 设置文本传播器otel.SetTextMapPropagator(...) 组合了 W3C TraceContextBaggage 两种传播器。
  3. 初始化 TracerProvider 并挂上日志钩子:调用 otelutil.NewTracerProvider(ctx, true) 后经 otel.SetTracerProvider(tp) 全局注册,并通过 tracing.NewLogrusHook() 把 Trace 上下文注入 logrus 日志。

同文件中还有 setOTLPProtoDefault()(见 daemon/command/daemon.go#L437-L457):buildkit 的 detect 包默认协议是 gRPC(符合旧版 OTEL 规范),而现行规范默认是 http/protobuf,因此该函数在 OTEL_EXPORTER_OTLP_PROTOCOL 未设置时,把 OTEL_EXPORTER_OTLP_TRACES_PROTOCOLOTEL_EXPORTER_OTLP_METRICS_PROTOCOL 都默认设为 http/protobuf。这与 otelcol.yaml 注释 “moby currently uses http” 相互印证:demo 栈把 HTTP 端口 4318 作为默认 OTLP 端点正是因此。

5.2 导出器探测:环境变量如何触发 OTLP 导出

TracerProvider 由 daemon/internal/otelutil/provider.goNewTracerProvider 创建,其核心逻辑:

  • 调用 buildkit 的 detect.NewSpanExporter(ctx) 探测导出器;探测失败且 allowNoop 为 true 时,降级为 noop.NewTracerProvider(),并打印 “Failed to initialize tracing, skipping”;
  • 若检测到的是 none 导出器(即未配置任何 OTLP 端点),同样返回 no-op provider 并打印 “OTEL tracing is not configured, using no-op tracer provider”;
  • 否则构建 sdktrace.NewTracerProvider,配置包含:默认 Resource、detect.Recorder 同步器(供 buildkit 记录)、sdktrace.WithBatcher(exp) 批处理导出器,以及 baggagecopy 处理器(把所有 Baggage 成员复制到每个 Span 上,便于在 UI 中过滤)。

探测逻辑本身在 vendored 的 buildkit 代码 vendor/github.com/moby/buildkit/util/tracing/detect/otlp.go 中:DetectTraceExporter 只有当 OTEL_TRACES_EXPORTER=otlpOTEL_EXPORTER_OTLP_ENDPOINTOTEL_EXPORTER_OTLP_TRACES_ENDPOINT 三者之一非空时才启用 OTLP 导出;协议取 OTEL_EXPORTER_OTLP_TRACES_PROTOCOL(未设则回退 OTEL_EXPORTER_OTLP_PROTOCOL,再未设默认 grpc,支持 grpchttp/protobuf 两种取值)。由于 daemon 启动前会先执行 setOTLPProtoDefault() 把协议默认值改写成 http/protobuf,所以第 4 节中只需设置 OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 一个变量,Trace 就会走 OTLP HTTP 打到 Collector 的 4318 端口——整条链路在这里闭合。

5.3 上下文跨进程传播:TRACEPARENT / TRACESTATE

Trace 并非只停留在 daemon 内部。daemon/internal/otelutil/environ_carrier.go 实现了一个基于环境变量的 TextMapCarrier:把 traceparent/tracestate 两个 W3C 头部映射到 TRACEPARENT/TRACESTATE 环境变量(EnvironCarrier.Environ() / PropagateFromEnvironment()),用于把 Trace 上下文传给外部进程(如插件、构建后端)并在其返回时延续同一条 Trace。配合 daemon/command/httphandler.go 中为 daemon 的 gRPC 服务挂载的 otelgrpc StatsHandler(以及 TraceService/Export 的免追踪直通拦截,避免客户端上报 Trace 时造成递归),moby 的 API 请求、gRPC 调用与 buildkit 构建都能挂到统一的 Trace 树上。

6. 小结与适用边界

  • contrib/otel 是一套“最小可运行”的 OTEL 验证环境:一个 Collector + 两个后端,靠 contrib/otel/compose.yaml4318 HTTP 端点与 contrib/otel/otelcol.yaml 的双导出器,把 dockerd 的 Trace 同时送进 Jaeger(localhost:16686)与 Aspire Dashboard(localhost:18888/traces)。
  • 能否出 Trace 的关键在 daemon 环境:OTEL_EXPORTER_OTLP_ENDPOINT 决定导出目标,协议默认被 daemon/command/daemon.go#L437-L457 修正为 http/protobufOTEL_SERVICE_NAME 未设置时兜底为可执行文件名。
  • 若未配置任何 OTLP 端点,daemon/internal/otelutil/provider.go 会安全降级为 no-op TracerProvider,对 daemon 性能与行为无影响——因此该功能属于“可选启用”的观测能力。
  • 适用前提:需要能启动一个带该环境变量的 dockerd(本地二进制或 devcontainer),以及 Docker Compose v2(compose.yaml 使用了 develop.watch 语法,依赖较新版本的 Compose 支持)。清理只需 docker compose downunset OTEL_EXPORTER_OTLP_ENDPOINT
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
980
502
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384