资讯动态

Kubernetes部署Jaeger实战:微服务链路追踪与生产实践

发布时间:2026/10/16 13:29:45 来源:尧图企业网站定制
在微服务体系跑进生产环境之后一个绕不开的话题就是排查链路问题。Kubernetes集群里的服务动辄几十个一个用户请求从入口到最终响应中间要跨过网关、鉴权、订单、库存等多个服务光靠看日志完全拼不出完整调用关系。前几年我开始在Kubernetes集群里做可观测性建设首先要解决的就是部署分布式追踪系统用实时trace把请求串联起来。这篇文章梳理我在Kubernetes上部署Jaeger的完整过程以及如何把trace管理做成标准化流程包括组件选型、部署、参数配置、业务接入和常见排障方法内容偏向生产实践适合正在搭建可观测性体系的开发者和运维同学参考。1. 为什么生产环境需要这套链路追踪体系1.1 微服务排查请求的痛点单体应用时代定位问题很简单一个请求ID查日志一条SQL、一个函数调用问题就在那几层调用里。微服务化之后完全不是这么回事一个用户请求可能要经过API网关、鉴权中心、订单服务、库存服务、支付回调五六个环节每个服务还有自己的数据库连接、缓存读取和消息队列订阅。此时只要某个环节偶发超时或报错不要说定位根因就连问题是发生在哪个服务里都要靠几个团队来回对日志才能确认。我在维护一个电商类系统时遇到过典型场景客户端反馈某个下单接口偶发超时网关日志显示上游响应时间超过三秒但网关只记录了最终状态码没有中间过程。订单服务的日志里看到一次超时异常却不知道这次请求在前面的鉴权服务和商品服务里到底做了什么耗时分布更是一头雾水。后来接上分布式追踪之后同样的超时现象在Jaeger UI里一眼就能看明白——原来是商品服务在调用外部价格接口时触发了重试整个链路时长被拖长。这就是trace管理最直接的价值把一次完整请求的路径、时间分布和关键事件全部串起来。1.2 Jaeger的核心组件和各自作用Jaeger是CNCF旗下的开源分布式追踪系统设计目标是覆盖链路数据从产生、采集、存储到展示的完整闭环。它由几个核心组件组成理解每个组件的定位部署时才知道哪些需要重点保障。jaeger-agent运行在节点上的轻量级代理负责接收业务进程发来的UDP数据批量转发给Collector。它存在的意义是让业务进程不用直接感知存储后端。但在Kubernetes环境里这个组件的地位正在下降因为直接发送gRPC到Collector在实现上更简单还避免了UDP丢包问题。jaeger-collector接收链路数据负责验证、清洗、索引然后写入存储后端。Collector是水平扩展的关键点生产环境必须多副本所有trace数据都从它这里过。jaeger-query对外提供查询API和Web UI用户通过服务名、操作名、标签就能搜索并查看一条完整调用的瀑布图。默认监听16686端口也是我们日常排查问题的主要入口。jaeger-ingester可选组件用于从Kafka消费trace数据并写入存储适合在数据量特别大、需要在Collector和存储之间加缓冲层的场景。存储后端支持内存、Elasticsearch、Cassandra、Badger等。内存模式只适合本地演示生产环境基本以Elasticsearch为主。从数据模型上看一次请求的完整路径称为一个Trace由多个Span组成。每个Span代表一次服务调用单元通过traceID和parentSpanID保持父子关系最终形成一棵调用树。Jaeger UI里的瀑布图就是把这棵树按照时间和依赖关系画出来。1.3 部署形态怎么选Jaeger在Kubernetes上部署有几种常见方式。直接跑all-in-one镜像。UI、Collector、Agent全在一个进程里数据存在内存中适合本地开发调试。重启后trace数据全部丢失生产环境千万不要这么干。手工定义Deployment、Service、ConfigMap分别搭建Collector、Query、Agent。这种方式灵活但每个参数都要自己维护升级和回滚也要手动处理工作量大。使用Jaeger Operator。通过CRD声明式管理Jaeger实例由Operator自动创建Deployment、Service、Ingress等配套资源还能自动注入agent sidecar。生产环境我推荐这种方式声明式管理能跟Kubernetes的运维习惯自然融合。所以下面整套方案都是基于Operator展开的。接下来从环境准备开始把安装和配置过程完整拆开。2. 在Kubernetes里安装Jaeger Operator并初始化存储2.1 准备Kubernetes环境与命名空间开始之前确认集群版本在1.24以上。Jaeger Operator本身对Kubernetes版本要求不算激进但版本太低会导致部分API不可用比如某些策略和网络配置就无法生效。我目前维护的集群是1.29版本跑Jaeger 1.53.0没有任何问题。建议单独建一个可观测性命名空间不要把Jaeger跟业务应用混在一起。独立命名空间在资源限制、网络策略、权限隔离这些方面都好管理后续要清理组件也方便一条命令删掉整个命名空间即可不会误删业务资源。kubectl create namespace observability2.2 安装Jaeger Operator安装Operator有两种常用方式。第一种是直接用官方release YAML简洁直观kubectl apply -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.53.0/jaeger-operator.yaml这条命令会创建observability命名空间并部署一个名为jaeger-operator的Deployment。默认配置下Operator会监听整个集群的Jaeger CRD也就是说你在任意命名空间创建Jaeger对象它都能感知并协调创建相关资源。装好之后确认状态kubectl get deployment jaeger-operator -n observability kubectl logs deployment/jaeger-operator -n observability看到的日志里如果没有任何Error说明Operator已经准备好可以开始创建Jaeger实例了。第二种方式是通过OLMOperator Lifecycle Manager安装。如果集群里已经装了OLM直接从OperatorHub订阅Jaeger即可好处是后续升级操作比较自动化。但OLM依赖CatalogSource在线拉取清单在离线或网络受限环境里容易卡在InstallPlan阶段。我自己的经验是生产环境用release YAML更可控升级时手动更换镜像tag并核对CRD变更反而更稳妥。2.3 准备Elasticsearch存储Jaeger的生产部署必须配持久化存储ES是最常用的选择。如果你的团队已经运维了独立ES集群直接把地址写进Jaeger的CR即可。如果没有可以在集群里用Helm先装一套测试用ES但单节点没有高可用真正上线还是建议用多节点ES或云厂商托管ES服务。这里假设你已经有ES地址比如http://elasticsearch-master:9200。需要特别注意的是ES版本兼容性Jaeger 1.x系列对ES 7.x支持最完善我用的Jaeger 1.53.0配ES 7.x十分稳定。如果集群里已经升级到ES 8要选支持ES8的Jaeger版本否则collector启动后写入数据会报类型映射或索引兼容性错误排查起来比较头疼。数据目录和索引模板这块先让Jaeger的Collector在启动时自动创建索引模板即可。等系统跑起来后再手工调整分片数、副本数和生命周期策略后面第3章会详细说。2.4 通过CRD创建生产级Jaeger实例Operator装好后在observability命名空间创建一个Jaeger自定义资源。下面这份YAML是我实际使用过的生产配置apiVersion: jaegertracing.io/v1 kind: Jaeger metadata: name: jaeger namespace: observability spec: strategy: production storage: type: elasticsearch options: es: server-urls: http://elasticsearch-master:9200 max-num-spans: 250000 index-prefix: jaeger sampling: options: default_strategy: type: probabilistic param: 0.01 collector: replicas: 2 resources: limits: cpu: 500m memory: 1Gi query: replicas: 2 resources: limits: cpu: 500m memory: 1Gi ingress: enabled: true hosts: - jaeger.example.com几个关键配置我解释一下strategy: production强制使用生产模式Operator会分别创建Collector、Query组件而不是启动all-in-one。storage.type: elasticsearch指定ES存储。sampling设置默认采样策略param: 0.01表示默认采样率是1%。这个值要谨慎设置定的太高会把ES写入打爆定的太低又可能漏掉问题链路。collector和query都设置了replicas为2并配了资源限制避免流量高峰时无节制占用内存。ingress.enabled开启Query UI的外部访问。保存为jaeger.yaml后执行kubectl apply -f jaeger.yaml然后观察Operator把组件依次创建出来kubectl get pods -n observability -l appjaeger kubectl get jaeger -n observability正常情况下会看到jaeger-collector和jaeger-query的Pod都处于Running状态。如果Pod卡在Pending优先检查节点资源如果是CrashLoopBackOff去collector日志里看最常见的就是连不上ES或者ES版本不兼容。到这里Jaeger本身已经跑起来了。但真正要让它发挥价值还需要把采样策略、存储生命周期、访问方式这些细节调好并且让业务应用把trace数据送进来。这些是下一章的重点。3. 部署细节的配置解析采样、存储、Ingress与高可用3.1 采样策略配置采样策略是Jaeger生产环境里最容易被忽略、但影响最大的配置。如果全量trace都入库ES的写入压力和数据量会非常夸张。如果采样率太低线上问题刚好没采到排查时依然两眼一抹黑。Jaeger支持几种采样策略固定率采样probabilistic按百分比随机采样最简单适合大多数场景。速率限制采样rate_limiting每秒最多采集N条trace适合流量很平稳的场景。自适应采样adaptive根据每个服务的吞吐量动态调整采样率Jaeger 1.x通过remote sampling方式下发策略。尾部采样tail-based属于更进阶的方案一般配合OpenTelemetry Collector实现按完整trace来决定是否采样效果更好但成本更高。我在生产环境里优先用固定率采样线上默认param设为0.01也就是1%。核心网关服务流量大可以降到0.001低流量的内部管理服务采样率可以提高到1。以我们的订单链路为例每天约百万请求1%采样每天也才一万条traceES存储压力完全可以接受。这里有个容易踩的坑如果你用OpenTelemetry SDK接入SDK默认采样策略是100%。也就是说就算Jaeger侧配置了1%的采样率SDK端不设置采样策略的话所有span都会被发到Collector数据量必然爆炸。所以接入时一定要在SDK端明确设置采样率或者通过远端配置统一下发。不要默认SDK会听Jaeger的采样配置。3.2 存储参数与索引生命周期ES存储的几个参数需要提前规划。es.max-num-spans单个索引里最大span数量超过后会自动滚动索引。生产环境一般设250000左右具体还要看分片大小。es.index-prefix给索引加前缀。同一套ES如果还跑其他监控系统加jaeger前缀能避免索引冲突后续管理也方便。es.number-of-shards和es.number-of-replicas可以在CR里覆盖ES默认值。数据量大的场景分片数要调高副本数至少1避免单点丢失。另一个必须提前做的是ES索引生命周期管理ILM。Jaeger默认创建的是按天分割的索引比如jaeger-span-2025-01-01。如果不做任何清理索引无限堆积ES磁盘迟早被写满。我用一个ILM策略来管理hot阶段保留最近7天索引超过50GB自动滚动90天后删除冷数据。在ES侧创建策略PUT _ilm/policy/jaeger-ilm { policy: { phases: { hot: { min_age: 0ms, actions: { rollover: { max_size: 50gb, max_age: 7d } } }, delete: { min_age: 90d, actions: { delete: {} } } } } }然后把Jaeger索引模板关联到这个ILM策略上。这样ES就能自动滚动和清理历史索引运维同学不需要每个月手动删一次数据。建议在Jaeger刚部署时就配好不要等磁盘告警了再临时处理。3.3 UI与Collector的暴露方式Jaeger UI是开发排查问题的入口必须能以某种方式访问到。Collector则主要给集群内部业务应用发送trace数据通常不需要暴露到集群外部。UI暴露最常用的是Ingress。上面的CR配置开启了Ingresshost是jaeger.example.com。要注意不同集群的IngressClass不一样如果访问不通先检查ingressClassName是否填了你集群实际使用的Ingress Controller的类名。安全方面要特别提醒Jaeger UI本身没有用户体系任何能访问到该地址的人都可以查看全链路的调用关系和业务信息。直接裸奔暴露到公网肯定不行。我在生产环境里用nginx ingress的basic auth挡了一层或者在公司内部前面挂OAuth2 Proxy统一走企业登录认证。对稍微敏感一点的业务链路内网加认证是底线。Collector的暴露方式则分为两种情况集群内部服务直接通过http://jaeger-collector.observability.svc:4317访问这是OTLP/gRPC端口。如果外部系统也要上报trace就得创建NodePort或LoadBalancer类型的Service。但暴露给外部后任何人能往Collector灌数据轻则污染索引重则造成存储资源耗尽所以开外网端口前要三思。3.4 高可用与资源规划生产环境的Jaeger本身不能成为单点。Collector和Query都应该多副本并且和普通应用一样配置PodDisruptionBudget确保节点维护或滚动升级时至少有一个副本能继续提供服务。资源规划上我给Collector单副本的基线是500m CPU、1Gi内存。当集群内接入的服务超过50个建议Collector至少3副本。Query无状态但需要扛并发查询和UI加载至少2副本。Agent如果坚持用DaemonSet形式部署每个节点都会有一个Pod在跑资源占用会随着节点数线性增长这也是我倾向不用Agent模式的原因之一。高可用之外还要关注网络策略。如果集群里启用了NetworkPolicy务必放通业务命名空间到observability命名空间之间4317端口的访问否则业务App发出去的trace数据会被网络策略直接拦截但应用本身没什么报错排查起来很隐蔽。这个坑我在后面会专门提到。4. 业务应用接入trace管理以OpenTelemetry为例4.1 三种接入模式对比Jaeger提供存储和查询能力数据要来自业务应用本身。常见的接入方式有三种Jaeger原生SDKjaeger-client需要在应用代码里创建Tracer、手工埋点或加拦截器。老项目用这种方式的多但新项目不建议再踩这条船因为它和OpenTelemetry生态的融合度不够好。OpenTelemetry SDK OTLP导出当前主流方式。应用端接入OpenTelemetry SDK通过OTLP协议把span发给Jaeger Collector。从Jaeger 1.35版本开始Collector原生支持OTLP/gRPC接入不需要额外的转换组件。链路最清晰。Java探针OpenTelemetry Java AgentJava服务可以用探针方式接入不改业务代码通过JVM参数启动自动为常用框架埋点。对遗留系统改造非常友好。Agent模式现在已经不是官方首推了每个Pod里挂Agent的方式会引入额外资源开销和网络拓扑复杂度。但Java探针还有它的价值代码侵入极小老团队接手遗留服务时很吃香。我这里主要讲OpenTelemetry SDK直连Collector的方式这也是Kubernetes环境下最省心的路径。4.2 Java/Go/Node接入实操先看Java探针方式。假设你有一个订单服务order-service启动脚本加一行参数java -javaagent:/opt/opentelemetry-javaagent.jar \ -Dotel.service.nameorder-service \ -Dotel.traces.exporterotlp \ -Dotel.exporter.otlp.endpointhttp://jaeger-collector.observability:4317 \ -Dotel.metrics.exporternone \ -Dotel.logs.exporternone \ -jar order-service.jar几个关键参数说明otel.service.name是服务名Jaeger UI按这个字段聚合展示。服务命名规范一定要团队内统一别一个服务在Jaeger里叫order-service日志里却叫订单服务后面检索时很难对齐。otel.exporter.otlp.endpoint指向Collector的OTLP gRPC端口4317。这里用了集群内部service的FQDN应用和Jaeger在同一个集群网络内解析没问题。otel.metrics.exporter和otel.logs.exporter都设为none是因为我们这期只做trace管理指标和日志走其他体系不要让OTLP一把梭全传进来。如果是Go项目用SDK接入ctx : context.Background() exporter, err : otlptracegrpc.New(ctx, otlptracegrpc.WithEndpoint(jaeger-collector.observability:4317), otlptracegrpc.WithInsecure(), ) if err ! nil { log.Fatal(err) } tp : trace.NewTracerProvider( trace.WithBatcher(exporter), trace.WithSampler(trace.ParentBased(trace.TraceIDRatioBased(0.01))), ) defer tp.Shutdown(ctx) otel.SetTracerProvider(tp)注意TraceIDRatioBased(0.01)就是采样率1%。Go和Java一样SDK默认都是全采样不改的话数据量会超预期。Node.js项目用opentelemetry/sdk-node初始化核心配置逻辑一致指定serviceName、sampler和OTLP endpoint。只要是标准HTTP框架SDK都会自动完成上下文传播和span创建不需要手写埋点。4.3 在Jaeger UI中验证trace是否正确串联应用接入后最重要的验证动作是确认trace真的出来了。操作步骤很简单先制造一次真实请求比如调用order-service的下单接口。然后打开Jaeger UI点击左侧SearchService选择order-service时间范围选最近5分钟点Find Traces。第一次查不到数据很正常不要慌。先看右上角有没有红色报错没有的话把时间范围扩大到最近30分钟排除时间选择问题。还是搜不到就回到第5章的排查清单逐项检查。能看到一条trace后点进去就是瀑布图。从上到下每一行是一个Span归属哪个服务、每个Span耗时多少一目了然。我验证时重点看两点跨服务调用的父子关系是否正确耗时最高的Span在哪。如果是HTTP调用展开Span能看到HTTP method、URL、status code这些标签排查问题基本够用。如果你同时接入了order-service和inventory-service在同一个trace里应该能看到两个服务各自的Span这说明trace上下文在服务间正确传播了。如果同一个请求在Jaeger里显示成两条独立的trace那就说明上下文传播有问题通常原因是HTTP客户端没有自动注入traceparent头。需要在服务间的HTTP客户端加上OpenTelemetry的埋点中间件比如Java的okhttp拦截器或Spring Boot的RestTemplate拦截器。5. 常见问题与排查技巧实录5.1 trace始终查询不到这是接入后遇到最多的一个问题。我的排查顺序大致是先看Collector是否收到数据。查看collector日志如果有span写入记录说明数据链路是通的问题大概率在UI查询环节。确认应用端SDK配置。最常见的错误是endpoint写成了localhost容器内访问不到或者写了不存在的service name导致DNS解析失败。确认端口协议。4317是OTLP/gRPC14268是Jaeger原生HTTP端口。endpoint里协议和端口对不上数据根本发不出去。确认采样率。SDK端采样率设成了0或者极低产生的trace数量很少UI上自然搜不到。确认UI搜索的时间范围。UI默认时间范围可能是最近15分钟查昨天的trace却忘了改时间也会表现成“没有数据”。这几个问题里最狡猾的是SDK端默认全采样和Jaeger端默认低采样不一致的那个逻辑上两个配置都“正常”但结果就是数据量超预期或者没数据只有把两端配置放在一起看才能发现矛盾。5.2 collector频繁报错与数据丢包使用agent模式时数据从业务Pod通过UDP发给本节点agent再由agent转发Collector。UDP本身不保证可靠传输网络拥堵或agent处理不过来就会丢数据而且很难察觉。我维护期间遇到过agent Pod频繁重启的问题根源是Agent资源不足队列写满后丢弃数据。解决方式有两个一是提高Agent的资源配额二是彻底放弃Agent模式让应用直接通过OTLP/gRPC发到Collector。gRPC基于TCP自带重传机制数据可靠性高很多。这也是社区演进的主流方向。Collector日志里如果频繁出现context deadline exceeded九成是Collector到ES的网络延迟过高或者ES当时正在做大批量分段合并。优化思路是给Collector到ES的写入超时调大一点同时检查ES分片数量和数据分布避免单个节点出现热点。5.3 ES存储压力过大trace数据是典型的写多读少高峰期所有服务都在写spanES如果分片规划不合理很容易出现节点CPU和磁盘双高。优化手段优先级排序降低采样率尤其是网关和低频接口的采样率。合理设置ILM滚动周期避免产生大量小分片。索引过小会导致ES性能下降7天滚动是一个比较平衡的选择。数据量很大的情况下在Jaeger前面引入Kafka做缓冲。Collector收到数据后写入Kafka由Ingester异步消费再写入ES能扛住突发流量峰值。裁剪上传的标签数据。很多服务默认会把所有环境变量和进程标签都上报其实大部分查询根本用不到可以通过Collector配置关闭冗余tag收集。5.4 版本与兼容性Jaeger和ES的版本匹配问题比较隐蔽。比如Jaeger 1.53配ES7没问题直接拿它连ES8就会在写入阶段报错或者UI里出现索引字段类型不匹配。升级Jaeger之前先去官方文档确认支持矩阵再动CR里的镜像tag。还有Operator和CRD版本的联动问题。升级了Operator但CRD没跟着升级CR里新增字段可能不被识别导致配置静默失效。我习惯升级后先跑一遍kubectl get crd jaegers.jaegertracing.io -o yaml确认operator期望的CRD版本确实已更新。5.5 安全与鉴权最后说安全。Collector和Query端口暴露范围要严格限制。我见过有团队把14268端口用NodePort暴露到公网被人灌了一堆伪造traceES索引在一天之内涨了十几个GB存储告警直接打爆。这个后果非常严重。安全配置建议Collector只允许集群内部访问外部上报流量统一走Ingress加认证。UI访问必须有认证基础认证挡一下也行企业SSO更好。ES的账号密码不要明文写在CR里。通过Secret挂载用EnvFrom方式注入避免配置泄露。6. 生产实践心得与后续扩展6.1 踩坑记录汇总一下维护这套系统以来印象最深的几个教训。第一次把Jaeger跑起来时我没有配ILM策略结果一个月后ES磁盘红了差点把历史trace全丢。从那以后我养成一个习惯所有ES索引创建前先关联生命周期策略先用2分钟配置后面省下至少一个通宵。还有一次业务反馈trace数据总是断断续续排查了很久最后发现是业务Pod的网络策略拦了到Collector的4317端口。Kubernetes NetworkPolicy平时不容易注意但上线后真的会拦截流量排查链路问题时把头几条网络策略先列出来看一眼能省很多时间。另外OpenTelemetry Java Agent版本更新非常快升级后默认配置可能会变。我们有一次升级Agent后所有trace都变成了全采样查了两个小时才发现是新版Agent把默认采样率从原来的按Jaeger远端配置改成了全采样。升级前最好在测试环境先验证一下。6.2 后续扩展方向Jaeger这套体系搭好之后可观测性建设还能继续往前走几步。把trace和日志联动起来。在业务日志里注入traceID然后在Jaeger UI里从某个Span跳转到对应日志查询。这样排查问题时一条请求从trace到日志就能一次定位不用在多个系统之间来回切换。对接OpenTelemetry Collector做数据管道增强。在应用和Jaeger之间加一层OpenTelemetry Collector可以做尾部采样、指标聚合、敏感字段过滤还能做统一的数据出口管理。这个架构对多集群场景特别友好。再往后就是trace与告警联动。Jaeger本身不产生告警但可以结合Prometheus指标和Loki日志做关联告警比如某个服务在trace中出现高错误率时自动通知值班同学。这些属于可观测性体系的进阶玩法等基础链路稳定之后可以逐步加进来。从我个人的经验看链路追踪的落地价值不在于把工具装起来而在于让团队养成一个习惯出了问题先看trace再翻日志。这个习惯一旦养成微服务的排障效率会提升一个量级。如果你正在给自己的Kubernetes集群搭建可观测性体系我建议先把这套最小闭环跑通然后在实践中逐步迭代。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑