概述与安装
Jaeger 是 CNCF 的分布式追踪系统(Distributed Tracing),把一个请求在多个服务之间往返的完整路径画成一棵树,告诉你每一跳花了多少时间、在哪里出错。它不是单纯的"看图工具"——接入协议、采样策略、存储后端、查询 API、Web UI 这一整条链路都在它的管辖范围内。
本手册基线是 Jaeger v2.22.0(2026-10-06 发布)。这一章解决两个问题:Jaeger v2 到底是什么,以及怎么在五分钟内把它跑起来并确认它是活的。
Jaeger 是什么
一句话概括 v2 的定位:Jaeger v2 = OpenTelemetry Collector 的定制发行版 + Jaeger UI + 一套为追踪优化的存储抽象。
拆开说:
- 接入侧,它原生消费 OTLP(OpenTelemetry Protocol),同时兼容 Zipkin 和 Jaeger 自家的旧协议(Thrift/Protobuf,官方标注 Stable 但已 Deprecated,见
01-核心概念.md)。 - 架构侧,v2 不再维护独立的 collector/query/agent 三个二进制,而是把全部角色编译进单一的
jaeger二进制,由一份 YAML 配置决定这个进程扮演什么角色。这个配置框架就是 OpenTelemetry Collector 的 service/receiver/processor/exporter 体系——官方文档原话是 “Jaeger is a customized distribution of the OpenTelemetry Collector with different roles”。 - 查询侧,Jaeger UI 和查询 API(gRPC + HTTP)保留,这是 Jaeger 相比"裸 Collector + 后端存储"最有价值的增量。
与三支柱的分工,用侦查类比最直观:metrics 告诉你"出事了"(P99 涨了),logs 告诉你"为什么出事"(某行报错),traces 告诉你"出事在哪条链路的哪一跳、每跳各花了多久"。三者靠 traceID 互相跳转缝合。三者如何在 Grafana/OTel 生态里联动,见 08-可观测性联动.md,这里不展开。
分布式追踪到底解决什么:三类生产问题
先看它在真实排障里长什么样,再决定要不要上这套系统。
第一类:跨服务延迟归因。 用户投诉"下单慢",网关监控只有整体 P99。有了 trace,你能直接看到 210ms 里谁吃掉了时间:
GET /dispatch frontend 210ms
├─ POST /profile user-svc 60ms
└─ POST /route trip-svc 150ms
├─ SELECT geo MySQL 120ms <- 大头在这
└─ GET /pricing pricing-svc 15ms没有 trace 时,这段路径只能靠每个服务的日志对时间戳;有 trace 时是一棵展开的树,慢节点一眼定位。这类"单请求慢"正是 head-based 采样最擅长保留的场景(为什么,见 06-采样.md)。
第二类:错误路径定位。 一个 500 背后可能是链路上任何一跳的失败。span 的 error status 把"错在哪一跳"标出来,再结合该服务日志(用 traceID 关联)定位原因。Jaeger UI 按标签/服务过滤 trace、再下钻到出错 span 的时间线,就是这条工作流。
第三类:依赖拓扑。 系统文档永远落后于代码,而 trace 是"数据驱动"的真实调用图——谁在调谁、调用量多大。HotROD demo 里的 System Architecture 页就演示这个(下文有)。
如果你还没有这三个问题中的任何一个,先别急着部署 Jaeger——埋点和运维是有成本的(性能开销、存储、采样策略维护)。反过来,一旦上了微服务,多数团队的结论是"比预想的有用":它同时是排障工具、性能分析工具和架构考古工具。
版本基线:先校准口径再读教程
这是读 Jaeger 资料最容易踩的时间坑,先把时间线摆清楚(均已按官方 release 与 CNCF blog 核实):
| 时间 | 事件 |
|---|---|
| 2024-11-12 | Jaeger v2.0.0 GA,CNCF 官方博客标题即《Jaeger v2 released: OpenTelemetry in the core!》 |
| 2025-12-03 | v1 分支最后一个 release:1.76.0 |
| 2025-12-31 | v1 正式 EOL。issue #6321 原文:“From Jan 01 2026 there will be no new releases of Jaeger v1 binaries” |
| 2026-10-06 | 当前基线 v2.22.0 发布,docs 站 latest 即 2.22 |
由此推出三条阅读纪律:
- 2024 年之前、教 “jaeger-agent + jaeger-collector + jaeger-query 三件套部署” 的教程全是旧口径。v2 只有单一
jaeger镜像,角色由--config决定。 - v1 的
COLLECTOR_*/QUERY_*等 flag 与环境变量在 v2 全部失效。v2 服务端只认 YAML 配置(YAML 内可以做环境变量插值,下文有)。 - jaeger-operator 仓库已 archived。v2 的官方部署路径是 Helm chart 或 OpenTelemetry Operator,不再有"Jaeger CRD 一把梭"的说法。K8s 部署细节见
03-部署与Kubernetes.md。
注意:docs 站上 1.76 版本标注 archive,遇到搜索结果混着 1.x 文档(URL 带
/docs/1.76/或/docs/1.x/)时,先确认再采信,1.x 的配置示例在 v2 上大概率跑不通。
v2 内部则是快速迭代(docs 侧栏可见 2.19/2.20/2.21/2.22 连续在发),每个发行版对应某一版 OTel Collector 内核。查上游 Collector 文档定位组件行为时,注意你装的 Jaeger 里打包的组件版本可能与 upstream main 有差异;本手册统一以 2.22.0 为口径。
v2 的运行形态:一个二进制,几种角色
官方架构页定义的角色就四个半:
- all-in-one:collector 与 query 合并在一个进程,默认用内存存储,适合开发调试。
- collector:接收 span 并写入存储后端。
- query:对外提供查询 API 与 UI。
- ingester:从 Kafka 读 span 写入存储,配合 collector-Kafka-ingester 的缓冲架构(见
02-v2架构与组件.md)。 - (agent 角色技术上仍可配置,但官方明确建议改用标准 OpenTelemetry Collector 承担这个角色,因为你多半还需要它处理 metrics/logs。)
启动规则很简单:
# 不带 --config:自动进入 all-in-one(内存存储),并在日志里打印
jaeger
# 打印原文:No '--config' flags detected, using default All-in-One configuration with memory storage
# 带 --config:YAML 决定这个进程扮演 collector/query/ingester 中的哪种角色
jaeger --config /path/to/config.yaml两个补充口径:
- 命令行还支持
--set key=value在配置文件之外覆盖个别键,调试时很省事:
# 例:临时把批处理大小改小验证 flush 行为,而不改 config.yaml
jaeger --config /jaeger/config.yaml --set processors.batch.timeout=200ms- 子命令只有三个:
version、docs(生成配置文档)、mappings(生成 Elasticsearch 索引 mapping)。
注意:v2 的整体配置没有热加载——改 config.yaml 后必须重启进程,SIGHUP 不会生效。唯一例外是 remote_sampling 策略用 file 类型时,策略 JSON 文件会按
reload_interval(官方示例配到 1s)自动重载。采样细节见06-采样.md。
因为 v2 本质是 Collector 发行版,你还可以用 OCB(OpenTelemetry Collector Builder)构建自己的自定义发行版,manifest 里直接引用 gomod: github.com/jaegertracing/jaeger v2.x.y,把上游 Collector 生态的 receiver/exporter 拼进来。
Docker 快速安装
官方 getting-started 的一行命令(2.22 文档实测原文,镜像仓库以 cr.jaegertracing.io 为主推,Docker Hub 与 Quay 同步发布):
docker run --rm --name jaeger \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
-p 5778:5778 \
-p 9411:9411 \
cr.jaegertracing.io/jaegertracing/jaeger:2.22.0镜像仓库的选择:官方主推 cr.jaegertracing.io/jaegertracing/jaeger,同一 tag 在 Docker Hub(jaegertracing/jaeger)和 Quay(quay.io/jaegertracing/jaeger)同步发布。生产环境建议固定版本号 tag,不要用 latest——Jaeger 月度节奏发版,latest 漂移会把"镜像升级"混进故障排查现场。国内网络拉取受阻时走正常的 registry mirror 机制即可,与 Jaeger 本身无关。
跑起来后各端口的用途(对照官方 APIs 页的 Default Ports 表核实):
| 端口 | 协议 | 用途 | 状态 |
|---|---|---|---|
| 16686 | HTTP | Jaeger UI;查询 JSON API(/api/v3/* 为 Stable 对外口径,/api/* 是 UI 内部用、官方明言随时可变) |
Stable |
| 4317 | gRPC | OTLP Protobuf 接入,SDK 主通道 | Stable |
| 4318 | HTTP | OTLP,/v1/traces,接受 Protobuf 或 JSON |
Stable |
| 9411 | HTTP | Zipkin 格式:/api/v1/spans(JSON/Thrift)、/api/v2/spans(JSON/Protobuf) |
Stable |
| 5778 | HTTP | 采样策略下发 /sampling(protobuf-to-JSON) |
Stable |
| 5779 | gRPC | 采样策略下发 jaeger.api_v2.SamplingManager |
Stable |
| 14250 | gRPC | 旧 Jaeger Protobuf 接入 jaeger.api_v2.CollectorService |
Stable, Deprecated |
| 14268 | HTTP | 旧 Jaeger Thrift 接入 POST /api/traces(Thrift binary 编码,Content-Type: application/vnd.apache.thrift.binary) |
Stable, Deprecated |
| 6831 | UDP | 旧 Jaeger Thrift compact 编码 | Stable, Deprecated |
| 6832 | UDP | 旧 Jaeger Thrift binary 编码(少数不支持 compact 的老客户端用,如老 Node.js) | Stable, Deprecated |
| 16685 | gRPC | 查询 jaeger.api_v3.QueryService(程序化取 trace 的推荐方式,兼容 api_v2) |
Stable |
对照上面这张表你会发现一个细节:all-in-one 的一行命令并没有映射 6831/6832/14250/14268。容器镜像的 EXPOSE 清单是 5778 5779 4317 4318 14268 14250 9411 16686 13132 13133;如果真有存量 Jaeger 原生客户端要走 UDP Thrift,得自己补 -p 6831:6831/udp。但更正确的做法是把这些老客户端迁到 OTel SDK——老协议标注了 Deprecated 不是摆设。
新部署统一 --config 方式(容器内配置路径惯例是挂到 /jaeger/config.yaml):
docker run --rm --name jaeger \
-p 16686:16686 -p 4317:4317 -p 4318:4318 -p 5778:5778 -p 9411:9411 \
-v /path/to/local/config.yaml:/jaeger/config.yaml \
cr.jaegertracing.io/jaegertracing/jaeger:2.22.0 \
--config /jaeger/config.yaml注意:容器里监听要绑
0.0.0.0。官方 all-in-one 配置内置了JAEGER_LISTEN_HOST=0.0.0.0;自写 config.yaml 时如果把 endpoint 写成localhost:4317,端口映射会表现为"宿主机能看见端口但连接被拒",非常迷惑。
顺带把"数据存哪"想清楚:all-in-one 的内存存储不落盘,docker rm 之后 trace 归零,且默认上限 max_traces: 100000(超出按淘汰策略丢弃,具体见 04-存储后端.md)。想在容器里用 Badger 持久化,就得再加卷挂载(如 -v jaeger-data:/badger,路径与 config.yaml 中 badger 的 path 一致),否则重启等于换了一个空库。开发机随便,但别把这套默认值原样搬进任何长命的环境。
验证它活着
三个层次,由浅入深:
- UI:浏览器打开
http://localhost:16686,能看到 Jaeger UI 即前端与 query 角色正常。 - 健康检查:
curl -s http://localhost:13133/status,进程活着且就绪时返回 200。K8s 部署里 readiness/liveness 探针就指向 13133(配置里 health_check extension 的默认端点),细节见03-部署与Kubernetes.md。 - 内部遥测:
curl http://localhost:8888/metrics,看的是 Jaeger 自身的运行指标(接收量、丢弃、各 exporter 状态等),Prometheus 抓取这个端口做监控。注意 8888 是 Collector 体系的内部遥测端口,不是业务指标。
另外一组常用内部端口:27777(expvar)、27778(zpages)、1777(pprof,官方 config.yaml 示例里的配置值)、17271/17270(remote storage gRPC/admin,见 04-存储后端.md)。
HotROD 官方演示应用
光有 UI 没有 trace 看不出什么。官方 getting-started 推荐的演示应用是 HotROD(Rides on Demand)——一个由多个微服务组成的"网约车"demo,官方文档实测的启动方式是 clone 仓库后用 docker compose:
git clone https://github.com/jaegertracing/jaeger.git jaeger
cd jaeger/examples/hotrod
docker compose up
# Ctrl-C 退出然后打开 http://localhost:8080 是 HotROD 的前端页面,http://localhost:16686 看 trace。按文档的说法,这个 demo 足够演示:数据驱动的依赖图、请求时间线与错误、延迟与并发不足的来源、日志关联、用 baggage 诊断跨请求排队。第一次接触 Jaeger 建议花十五分钟把它跑通——比对着空白 UI 凭空理解概念效率高得多。
HotROD 的源码同时就是一份"OTel Go SDK 埋点参考答案":driver/customer/routes/notification 四个小服务,每个的 instrumentation 都放在仓库里可以对照抄。它的埋点写法逐行拆解见 05-Go客户端埋点.md。
注意:compose 方式起的 HotROD 自带它的 Jaeger 服务定义,如果你本机已经手动跑了一个 all-in-one(16686/4317 被占),两边会端口冲突;二选一,或者改 compose 文件里的端口映射。
二进制安装与发布物
不想跑容器的话,从 GitHub release 页下载资产(v2.22.0 实测命名):
jaeger-2.22.0-{os}-{arch}.tar.gz:主二进制包(linux/darwin/windows,amd64/arm64)。解包后目录里有jaeger、jaeger-all-in-one.yml等默认配置样例,直接./jaeger --config ...使用。jaeger-tools-2.22.0-{os}-{arch}.tar.gz:独立工具包,内含 es-rollover、es-index-cleaner、anonymizer、remote-storage、tracegen 等二进制。ES 运维相关的几个工具在04-存储后端.md展开。- 每个 release 附带 SBOM、provenance attestation 与 PGP 签名,生产引入前可以在 download 页核对。
curl -sLO https://github.com/jaegertracing/jaeger/releases/download/v2.22.0/jaeger-2.22.0-linux-amd64.tar.gz
tar xzf jaeger-2.22.0-linux-amd64.tar.gz
./jaeger-2.22.0-linux-amd64/jaeger versionWindows/macOS 同理换 {os}-{arch} 后缀;官方 docs 还专门有 Windows Service Deployment 一节,讲把 jaeger 注册成 Windows 服务的姿势。二进制方式跑 all-in-one 与容器版行为一致:不带 --config 就是内存存储,带 --config 进入自定义角色。
从 v1 迁移:旧资产怎么映射到新体系
存量 v1 环境升级 v2 时,脑子里建立这张映射表(官方 docs 的 Migration 一节有更细的步骤):
| v1 世界的东西 | v2 的对应物 | 备注 |
|---|---|---|
jaeger-agent / jaeger-collector / jaeger-query 三个二进制 |
单一 jaeger + --config |
镜像同理,v2 只有一个 jaeger 镜像 |
COLLECTOR_* / QUERY_* 命令行 flag 与环境变量 |
YAML 配置(可 ${env:VAR} 插值) |
旧变量一个都不生效,静默失效最危险 |
| jaeger-agent 边车/守护进程 | 标准 OpenTelemetry Collector(sidecar/DaemonSet) | 官方明言推荐;v2 保留 agent 角色但不再演化 |
jaeger-operator(CRD Jaeger) |
Helm chart 或 OpenTelemetry Operator | operator 仓库已 archived |
采样策略 flag(--sampling-strategies 等) |
remote_sampling extension + strategies JSON |
唯一支持热重载的配置块,见 06-采样.md |
| Jaeger 原生 Thrift/UDP 客户端 | 继续可用(6831/6832/14250/14268 仍监听),但应迁 OTel SDK | 状态是 Stable, Deprecated——没删,但不修新问题 |
| 内存/Badger/ES/Cassandra 存储概念 | 沿用,配置写法换成 Collector exporter 风格 | ClickHouse 在 v2 转正为实验性后端 |
两个容易想当然的点,提前说清:
注意:坊间流传"v2 移除了 Jaeger Thrift 接收端口"——不实。APIs 页明确列出 6831/6832/14250/14268 及其 “Stable, Deprecated” 状态,v2 只是不再为它们开发新特性。
注意:v2 查询端能否直读 v1 时代写入的 ES 索引(mapping/文档版本兼容),官方文档没有给出明确承诺的页面。跨大版本带着旧 ES 数据迁移前,先在测试环境用同版本存储验证,别在生产上试。
冒烟测试:tracegen 打一波流量
官方压测/验证工具 tracegen(源码在 operations/tools/tracegen,镜像 cr.jaegertracing.io/Docker Hub 的 jaegertracing/jaeger-tracegen:2.22.0)。往 all-in-one 灌流量:
docker run --rm \
--network host \
-e OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
jaegertracing/jaeger-tracegen:2.22.0 \
-duration=30s -workers=2 -pause=100ms参数就三个常用的:-duration 总时长、-workers 并发协程数、-pause 每个 trace 之间的间隔;再加 firehose 模式可以不限速猛灌(压测用)。不用容器也可以——jaeger-tools 包里就带 tracegen 二进制,用法相同。跑完后 UI 里 Expected results 是这样:
jaeger-ui: localhost:16686
Search: service = tracegen
→ 若干条 trace,每条包含 root span "tracegen"(或类似操作名)
与它派生的子 span,duration 与 -pause 节奏吻合看到 trace 树即宣告:接入(4318)、内存存储、查询(16686)三段链路全部打通。如果 UI 里没有:先确认 tracegen 日志无 OTLP 导出报错(常见是 endpoint 写错或端口没映射),再确认它发的是当前进程的角色允许接收的协议。
tracegen 走的是 OTLP 环境变量 OTEL_EXPORTER_OTLP_ENDPOINT(SDK 侧的 OTEL_* 环境变量在任何部署形态下都照常有效);firehose 压测的更多姿势见 09-性能与运维.md。
配置里的环境变量插值
v2 的 YAML 配置支持在值里引用环境变量并带默认值,语法来自 Collector confmap:
# 例:存储密码从环境变量取,未设置则用空串
password: ${env:JAEGER_ES_PASSWORD:-}记住两个口径的分工:服务端用 ${env:VAR:-default} 从 YAML 读;客户端/SDK 侧用标准 OTEL_* 环境变量(OTEL_EXPORTER_OTLP_ENDPOINT、OTEL_TRACES_SAMPLER 等),跟 Collector 配置无关。v1 那套 COLLECTOR_ZIPKIN_HOST_PORT 之类的环境变量不要指望还能用。
部署形态与存储怎么选(先看一眼)
官方 FAQ 与架构页给出的判断标准,这里只做导入,细节在后续章节:
- all-in-one + memory:开发测试。内存存储默认
max_traces: 100000,重启即失,生产禁用。 - all-in-one + Badger:官方原话是"可以用于生产,但仅限中低数据量"——单实例、不能水平扩展。
- collector 与 query 分离直写存储:最常见的生产形态。官方架构页点明了分离的收益:读写流量可以各自独立水平扩缩,还能施加不同的访问与安全策略——查询是面向工程师的高频读、偶发大扫描,接收是面向全量应用的高频写,两者的容量曲线根本不是一回事。
- collector → Kafka → ingester → 存储:存储扛不住峰值时用 Kafka 做持久缓冲,多 ingester 自动分摊。
- 存储后端主力是 Elasticsearch / OpenSearch、Cassandra、Badger;ClickHouse 需要开 feature gate
storage.clickhouse,属实验性;Kafka 只做队列不当存储。大规模生产官方推荐 OpenSearch 优于 Cassandra(理由:Cassandra 写放大、其搜索客户端实现受限),见04-存储后端.md。
安装阶段常翻的车
按出现频率排,多数是环境问题而非 Jaeger 问题:
- 端口占用:4317/4318 常被本机其他 OTel Collector 或 DevSuite 占用;16686 偶尔撞上别的 UI。
docker run时改宿主侧映射(-p 14317:4317)即可,容器内端口不要动。 - “能连端口但没数据”:客户端把 OTLP endpoint 写成了
localhost:4317,但客户端在另一个容器/远程机器上——容器网络的 localhost 不是你的 localhost。跨容器场景写服务名或宿主机 IP。 - Windows 下路径挂载失效:
-v /path/to/config.yaml:/jaeger/config.yaml在 Docker Desktop 需要目录在允许共享的路径里,否则容器里看到的是一个空目录,报"config not found"。 - 用了 v1 教程的启动参数:比如给容器加
--collector.zipkin.host-port=...之类的 flag,v2 的单一二进制不认识(它只吃--config/--set/三个子命令),进程直接起不来。这是最典型的"教程时区错误"。 - UDP 老客户端静默丢数据:6831/6832 没映射时,UDP 不会报错——客户端觉得自己发成功了,数据实际蒸发。验证阶段一律用 OTLP gRPC/HTTP,它们有明确的连接失败。
初见 Jaeger UI:三分钟导览
http://localhost:16686 打开后,值得先摸清楚的四个动作:
- Find Traces 的服务下拉:列表由存储中实际存在的
service.name聚合而来。如果你的服务不在下拉里,九成是埋点侧 resource 没设 service.name,而不是 Jaeger 丢了数据——排查顺序见05-Go客户端埋点.md。 - 时间窗与样本量:搜索默认只看最近一段时间、返回有限条数。找不到"很久以前"的 trace 通常是时间窗问题,其次是采样根本没保留(见
06-采样.md),不要第一反应怀疑存储坏了。 - 点开一条 trace:瀑布图按父子层级展开 span,横条长度即 duration,error span 有红色标记;右侧详情面板能看到 tags、logs、references——这些字段的物理含义在
01-核心概念.md里逐个讲。 - Dependencies 页:数据驱动的调用拓扑。注意 all-in-one 演示时它是实时的,生产环境这页为空往往因为没部署依赖图生成任务(
01-核心概念.md结尾与04-存储后端.md各有一段)。
程序化取数请走 gRPC 16685(jaeger.api_v3.QueryService)或 HTTP 的 /api/v3/*,UI 背后的 /api/* 是内部接口,官方明言随时可变,别写进自己的脚本。完整 API 面见 07-查询UI与API.md。
下一步阅读路径
- 总览与阅读索引见
README.md,先扫一眼全书地图再逐章深入也可以。 01-核心概念.md:span/trace/上下文传播/采样这些名词在 v2 里的准确形态,建议先读——后面所有配置项都建立在这套概念上。02-v2架构与组件.md:单一二进制内部怎么由 Collector 管线拼出各角色。03-部署与Kubernetes.md:Helm chart 与 OpenTelemetry Operator 两条官方路径。04-存储后端.md:五种存储的选型与运维。05-Go客户端埋点.md:应用侧真正接起来的那一步。- 之后按需:
06-采样.md、07-查询UI与API.md、08-可观测性联动.md、09-性能与运维.md、10-常见陷阱与最佳实践.md。
安装自检清单
上线(哪怕是本地环境定稿)前逐条过:
- 确认所有教程/示例来自 v2 口径(docs URL 带
/docs/2.x/;出现三二进制、operator CRD、COLLECTOR_*flag 的直接判旧) -
jaeger version/ 容器标签与计划版本一致(当前基线 2.22.0) - all-in-one 仅用于开发;生产形态的角色分离与存储选型已确定(memory 不上生产)
- 自写 config.yaml 时 OTLP/查询等 endpoint 监听绑
0.0.0.0而非localhost - 若用旧协议客户端:补齐对应端口映射(6831/6832/14250/14268),并排期迁 OTel SDK
-
13133/status健康检查可探、8888/metrics已被 Prometheus 抓取 - tracegen 或真实应用能进 UI 看到完整 trace 树
- 记住改配置需重启(没有 SIGHUP 热加载),变更纳入滚动重启流程
- 存量 v1 环境:对照上文映射表逐项迁移,v1 时代的环境变量/flag 全部从编排清单里清除,避免"配了但不生效"的幽灵
- 监控侧:13133 探活与 8888 自监控指标接入完成,Jaeger 自己也要被观测
- 版本记录:部署清单里写明
2.22.0这样的精确 tag,方便日后回溯"当时跑的是哪版"
装完只是起点。下一层问题是"span 到底由什么构成、怎么跨进程不丢"——进入 01-核心概念.md。