把团队内部的分布式追踪系统从 1.x 迁到 Jaeger 2.0前后花了一周左右。真正动手之前我以为这只是一次常规的大版本升级最多改改部署参数、换换镜像版本。跑起来之后才发现从部署形态、配置方式到数据协议2.0 几乎把整个项目的底座换了一遍。这篇文章不打算复述官方文档而是把我实际升级过程中看到的、查到的、踩过的东西整理出来。如果你正在评估 Jaeger 2.0或者已经走在迁移路上希望这些内容能帮你少走几步弯路。先说结论Jaeger 2.0 最大的变化不是界面变好看了也不是新增了几个查询功能而是整个项目终于承认了一个现实——采集端应该交给 OpenTelemetry 生态自己专注做好存储、查询和展示这一层。这个定位调整比任何新特性都影响深远。1. 分布式追踪生态变天Jaeger 2.0顺势完成定位切换1.1 为什么一个版本迭代酝酿了这么久Jaeger 最初是从大型互联网公司内部的链路追踪系统演化而来的开源之后很快成为云原生领域最常见的追踪后端之一。1.x 时代的功能其实已经很完善多语言 SDK、agent 本地聚合、collector 异步处理、多种存储后端、丰富的查询 API都是在这个阶段沉淀下来的。但生态变化的速度比功能迭代更快。OpenTelemetry 出现之后整个行业对埋点、采集、传输这件事逐步形成了统一标准。以前每个可观测性项目都有自己的 SDK、自己的协议、自己的传输格式用户选了一个追踪系统基本就被绑死了。OpenTelemetry 的野心是把这个局面打破SDK 只负责采集和上下文传播协议统一走 OTLP后端的存储和展示可以自由选择。在这种背景下Jaeger 如果继续把 SDK、agent、collector 到 storage 的整套体系攥在自己手里就会越来越孤立。1.x 的老架构设计得再精巧也架不住标准已经变了这个现实。所以 2.0 的开发周期很长本质上不是因为功能有多难写而是需要把过去十年积累的很多设计决策推翻重来。1.2 2.0向生态让渡了哪些能力又守住了哪些核心我自己的理解是Jaeger 2.0 做了三个非常明确的取舍。第一采集端彻底让渡给 OpenTelemetry。曾经自家的 jaeger-client SDK 虽然还能用但已经不在演进主线上了。agent 组件被移除取而代之的是 OTel Collector。数据上报协议也全面转向 OTLPJaeger 本身不再维护一套私有的端到端协议栈。第二配置体系重构。1.x 里面几十个甚至上百个命令行参数加配置文件混合使用的模式被彻底推翻改为声明式 YAML 配置。这个变化表面上只是运维方式不同实际上意味着 Jaeger 开始用描述期望状态的方式来管理自身而不是让用户去记一堆启动参数。第三存储和查询能力被保留并强化。这是 Jaeger 真正的护城河。无论数据从哪里来只要是符合 OTLP 标准或者 Jaeger 模型的 trace你都可以往里塞然后在 UI 和 API 上做检索、分析、对比。官方支持的存储后端也更多元不再默认只有 Elasticsearch 一种答案。守住核心让渡边缘这就是 2.0 的底层逻辑。搞清楚这一点后面看任何具体变化都不会觉得乱。2. 组件更精简agent退场UI独立all-in-one继续保留2.1 1.x时代的经典组件拓扑回顾先简单回顾一下 1.x 的经典部署形态。标准链路是SDK → jaeger-agent → collector → 存储 → query → UI。jaeger-agent 在 1.x 里占据很重要的位置。它通常以 DaemonSet 方式部署在每个节点上业务 SDK 通过 UDP 或 HTTP 把 span 发给本机 agentagent 做一定的聚合和重试再转发给 collector。这样设计的好处是业务实例不需要知道 collector 在哪SDK 和 collector 之间的连接数也被限制在了节点级别网络开销更可控。但这个设计放在今天看问题也很明显。agent 本身是一个需要独立运维的组件版本要升级配置要管理出了故障还会影响采集链路的稳定性。更关键的是OpenTelemetry 生态已经提供了 OTel Collector 这个更强大的替代品它不仅能接收 trace还能处理 metrics、logs支持各种 filter、transform、batch 插件。完全没有必要让 Jaeger 再维护一个功能重叠的 agent。2.2 2.0部署模型里的三个明显变化2.0 部署模型最明显的变化有三个。第一个是 agent 组件消失。官方不再提供 jaeger-agent 镜像也没有对应的 DaemonSet 部署方案。原来 agent 承担的职责包括接收 SDK 数据、批处理、重试、队列等直接由 OTel Collector 接管。你的业务 SDK 只需要把 trace 发到 OTel Collector剩下的交给它处理。第二个是 UI 从 query 服务里独立出来。1.x 时代 query 服务既提供 JSON API又托管 UI 静态文件浏览器打开一个 16686 端口就能看到控制台。2.0 里 UI 单独成为一个可部署的组件query 服务专注提供 API。你可以选择把编译好的 UI 静态文件让 query 服务挂载也可以把 UI 部署成完全独立的站点甚至接入自己的鉴权体系之后再反代过去。灵活性更高但也意味着运维拓扑多了一个节点。第三个变化是 all-in-one 模式仍然保留。一条命令启动 collector、query、UI 和内存存储适合本地调试、功能验证、快速截图。生产环境当然不建议这么干但做 PoC 的时候确实方便。1.x 组件2.0 状态替代方案jaeger-agent移除OTel Collectorjaeger-collector保留原生接收 OTLP直接作为 OTel 后端jaeger-query保留聚焦 API独立部署jaeger-ui独立成服务可单独部署或由 query 托管静态文件all-in-one保留本地演示和测试2.3 对运维链路最直接的几个影响组件精简之后对运维最直接的影响有两个。一个是网络链路变短了。以前是 SDK → agent → collector现在是 SDK → OTel Collector → collector路径上没有本质变化但少维护一个 Jaeger 自己的 agent 版本。如果你以前就被 agent 的升级节奏和配置问题折磨过这个变化体会会非常深。另一个是端口和入口的语义变了。2.0 collector 默认监听 4317 和 4318分别是 OTLP/gRPC 和 OTLP/HTTP 的端口。如果你以前习惯把 SDK 指向 agent 的 6831 或者 5778现在需要改成指向 OTel Collector 或直接指向 collector 的 OTLP 端口。升级期间最容易出的问题就是端口配置没改过来数据根本没进到新链路。3. 配置方式翻篇命令行参数退位声明式conf.yaml上位3.1 旧版配置体系的问题flag太多根因难查用过 1.x 的人应该都有印象它的配置体系是命令行参数为主配置文件为辅。启动一个 collector光端口就有 gRPC、HTTP、管理端口还有存储类型、存储地址、队列大小、采样策略路径…… 如果不封装一层 Helm values用户就要在 systemd unit 或者 docker run 命令里塞几十个--xxx.yyy参数。这种设计在单体时代还能忍但到了 Kubernetes 环境就很痛苦。配置散落在各种 Values 文件里模板套模板最后到底哪些参数生效、优先级怎么样经常要跑到容器里去验证。出了问题排查链路特别长因为根本没法快速说清楚当前进程到底用了哪一组配置。3.2 v2配置文件的骨架与命名空间逻辑2.0 把配置统一收敛到一份 YAML 文件里靠命名空间区分组件。下面是一个最基础的 collector 配置示例jaeger: collector: service: host-port: 4317 http-port: 4318 storage: type: elasticsearch elasticsearch: server-urls: http://elasticsearch:9200 query: service: host-port: 16686结构很清晰jaeger.collector管采集jaeger.storage管存储jaeger.query管查询。每个组件关心的配置都收敛在自己对应的命名空间里不会出现以前那种存储参数散落在不同 flag 分组里的情况。环境变量的机制仍然保留这样在做容器化部署时可以结合 Kubernetes Secret 使用。例如下面的写法可以在运行时覆盖存储地址export JAEGER_STORAGE_TYPEelasticsearch export JAEGER_STORAGE_ELASTICSEARCH_SERVER_URLShttp://elasticsearch:9200注意环境变量的层级要和 YAML 的嵌套结构对应命名方式是大写下划线分隔。这里容易踩坑环境变量优先级高一旦设置它YAML 里对应的配置就会被覆盖。3.3 配置迁移阶段容易忽略的三个细节实际迁移过程中有三个细节特别容易忽略。第一个旧 flag 不会再被读取。有些团队会在启动命令里加上--collector.otlp.enabledtrue这类 1.x 参数指望新版本兼容识别结果进程起不来或者根本没有任何 OTLP 端点监听。2.0 只认新式配置老参数要么删除要么逐项映射到 YAML。第二个UI 的配置不再是 query 配置的一部分。如果独立部署 UI它的监听端口、api 地址都需要在 UI 自己的配置里指定而不是挂在jaeger.query下。这个细节文档里有写但很容易被忽略因为大家已经习惯了打开 query 端口就有 UI。第三个远程采样配置的结构发生了调整。1.x 里通过/api/sampling接口下发采样策略配置方式比较固定。2.0 里采样策略的配置路径和表达方式要重新确认特别是如果你用了自定义采样规则不要直接复制旧配置先确认新版本还认不认这套格式。4. 数据入口倒向OTLP原生协议冻结之后4.1 默认端口与原生协议OTLP带来的直接变化Jaeger 2.0 collector 原生接收 OTLP这带来几个直接变化。首先是端口问题。默认情况下 collector 监听 4317 和 4318分别对应 OTLP/gRPC 和 OTLP/HTTP。如果你用 OpenTelemetry SDK 上报 trace直接把 endpoint 指向这里就行不需要额外的协议转换层。其次是数据模型的对齐。OTLP 的 span 结构几乎原样映射到了 Jaeger 的存储模型里attribute 对应 tagevent 对应 log。以前 Jaeger 内部有自己的一套 protobuf 定义现在这套定义被冻结在 jaeger-idl 里不再跟随新需求演进。也就是说未来所有新特性都会基于 OpenTelemetry 的数据模型来增长旧格式将停留在兼容但不发展的状态。第三是语义约定semantic conventions的影响。以前 Jaeger 的原生 SDK 和 agent 有自己的 tag 命名习惯比如http.url、peer.service。OTLP 生态里这些字段有统一的命名和组织方式如果埋点 SDK 升级到了 OTel进入 Jaeger 的 span 会携带更多标准化字段对跨团队协作和统一查询是好事。4.2 老SDK的兼容路径能跑但不要当长期方案升级时最现实的问题是存量服务如果还在用旧版的 jaeger-client SDK2.0 还认不认官方给出的答案是提供兼容端点主要是 jaeger.api-v1 这一层。老的 thrift 格式上报数据仍然可以被接收所以短期内你不必把所有 SDK 全部切换完业务服务可以先继续跑旧 SDK数据一样能从 UI 里查到。但我不建议把这个状态维持太久。原因有两点第一旧 SDK 背后的通信格式和协议已经冻结社区不再继续在上面投入精力出了数据丢失、采样异常这类问题只能靠自己的经验去排查第二OpenTelemetry SDK 在自动埋点、上下文传播、指标和日志联动上做得越来越好继续留在旧协议上等于放弃了这些能力。我的建议是给一个明确的切换周期把核心服务的 SDK 逐步换成 OTel旧的兼容端点作为过渡手段保留而不是长期依赖。4.3 数据模型对齐OTel后的连带影响字段变化还会带来连带的运维影响。最典型的是 Elasticsearch 索引 mapping 的变化。Jaeger 存储在 ES 里的索引结构是跟随数据模型走的2.0 的数据模型对齐 OTLP 之后索引里的字段、类型、嵌套结构都可能有差异。你不能指望把 1.x 的索引拷贝到新集群里然后让 2.0 query 直接查。团队如果打算保留历史数据一定要提前做索引重建和数据迁移的评估而不是简单复用旧索引。另一个连带影响是查询 API 返回的 JSON 结构。虽然 v1 API 还保持着主要兼容但字段名和层次可能微调依赖 API 做告警或者二次开发的系统要重新跑一遍接口测试。我们团队就有一个基于 trace API 做的内部诊断工具升级后字段路径变了不得不跟着改了一版。5. 存储后端不再默认ES多存储选型的现实考量5.1 当前存储后端支持情况一览Jaeger 历来在存储层给了很多选择但不同后端的成熟度差异很大。2.0 时代的支持情况可以大致列成下面这个表后端支持级别部署复杂度适合规模一句话评价Elasticsearch / OpenSearch官方中高中大型生态成熟查询灵活运维负担重Cassandra官方高超大规模写入扩展性好运维门槛偏高Badger官方实验低单机适合本地和边缘场景内存存储官方极低演示测试数据不持久重启即丢ClickHouse社区中大规模 分析压缩率高聚合快但配套不够完善5.2 ClickHouse看起来很香落地前要掂量ClickHouse 在可观测性领域一直是热门话题它的列式存储特性天然适合 trace 数据这种高吞吐、低更新、分析型查询的场景。压缩率高存储成本能省不少聚合查询快按服务、按接口统计耗时占比很顺手。但我建议你在引入之前认真掂量几件事。第一社区方案往往不是官方维护版本兼容性要自己盯着。Jaeger 的版本每升级一次ClickHouse 的存储插件是否及时跟上、是否需要改表结构这些都要自行验证。我们团队在做选型时专门搭建了一套测试环境模拟了三个月的数据量跑了一轮查询演练才确认它符合预期。第二ClickHouse 本身擅长的是大批量写入和分析查询但在链路追踪场景里UI 的一个搜索请求往往会带上复杂过滤条件这跟 ClickHouse 擅长的场景不一定完全匹配。需要花时间调表结构、索引、分区策略甚至要考虑是否引入物化视图来服务常见查询。第三运维技能栈的问题。很多团队对 ES 很熟但对 ClickHouse 的运维经验几乎是零。选一个新存储等于同时引入一个新数据库去维护评估成本一定要算进去。5.3 按规模做存储选型的实操建议结合不同规模我给一个实操建议。本地开发和个人试验直接用 all-in-one 带内存存储简单直接。小规模微服务比如十几个服务以内、每天几千万 span 的量级一个单节点 ES 加上索引生命周期管理就够用了不要一上来就上集群。中大型规模建议 ES 至少三节点起步开启索引分片和生命周期策略给查询留足性能余量。如果你确实需要保留很长时间的数据并且有深度的聚合分析需求再认真评估 Cassandra 或者 ClickHouse。每个存储都有各自的取舍没有一个后端是通吃所有场景的。选型之前一定要拿自己的真实数据量、查询模式、保留周期跑一轮 PoC只看宣传文章是不靠谱的。6. 从1.x迁到2.0的实操步骤、兼容坑与验证清单6.1 迁移之前先把这三个问题问清楚动手迁移之前我建议先回答三个问题。第一你现在依赖哪些 1.x 特有功能比如 agent 的本地限流规则、某些自定义 tag 的解析逻辑、旧版 UI 里已经配置好的仪表盘。这些功能在 2.0 里可能不复存在或者以完全不同的方式提供。如果团队对这些功能有强依赖迁移成本会比预想的高很多。第二历史数据是否需要完整保留如果需要必须提前评估索引重建和数据导出导入的方案。Jaeger 2.0 的数据模型和旧 ES 索引结构不兼容不是简单把旧索引 alias 改一下就能解决的。如果不需要保留直接冷启动新集群让 2.0 开始接收新数据反而省事很多。第三下游系统对 Jaeger API 的依赖程度如何。告警对接、内部诊断工具、自动化的 trace 分析脚本这些都建立在对 query API 的调用上。升级之前把相关代码的接口调用梳理一遍能大幅降低上线后的意外。6.2 我们采用的低风险并行迁移路径我们实际采用的是并行迁移的策略整体分成了三步。第一步先用 all-in-one 模式本地跑一个 2.0接入几条测试服务链路验证 SDK 上报、UI 查询、采样配置这些基础功能是否正常。这一步成本很低半天就能做完主要是让自己熟悉新配置和新的部署方式。第二步部署独立的 collector query 新 ES 索引集群。业务测试环境全部把 trace 发到新 collector连续跑几天确认数据量、查询耗时、组件稳定性都符合预期。这一步的关键是不要动生产环境只让测试流量进入新链路。第三步确认无误后再切换生产入口。我们保留了旧的 1.x 集群运行了一段时间新老数据同时存在。等所有业务都切到新链路并且稳定运行后再关停旧集群。这个路径的好处是每一步都有验证节点即使出现问题也能随时回退到旧链路。比起大版本原地升级操作上多花了一点时间但风险低很多。6.3 上线前的验证清单和常见故障定位最后分享一份我们上线前使用的验证清单照着跑一遍基本能覆盖主要风险SDK 上报链路验证检查 collector 日志能看到新 span 进入UI 能查到对应 trace。OTLP 端口验证分别测试 4317 的 gRPC 和 4318 的 HTTP 两种上报方式。采样策略验证远程采样配置下发后确认采样率按预期生效。UI 连接验证独立部署的 UI 能否正确访问 query API搜索、Trace 详情页是否正常。历史数据验证如果需要导入旧数据抽样比对查询结果是否一致。下游 API 验证告警和内部工具调用 query API 的链路是否全部通过。还有一个常见故障值得单独说如果你在升级后发现新集群启动正常但 UI 里一直没有新数据先别急着查网络直接看 collector 是不是真的在使用你配置的存储。我们当时漏掉了一个自定义存储参数的映射导致 collector 启动后默认往内存存储里写数据进程一重启全部清空。这种问题日志里不一定会报错最有效的排查方式是先插入一条测试数据然后去存储后端里确认数据是否真的落盘了。这次升级走完我最大的体会是2.0 的变化不是新功能堆叠而是把 Jaeger 放回了它该在的位置。采集交给 OpenTelemetry 生态自己专注存储、查询和 UI。对使用者来说这意味着部署链路更干净也更符合可观测性领域当下的主流标准。如果你正准备升级建议先按上面这份清单做一轮摸底把依赖项、历史数据、下游调用梳理清楚再动手别看到一个新版本就直接切。