一、问题背景:为啥Jaeger跑在K8s上会丢追踪?

不少开发者把Jaeger(一个专门用来记录程序调用轨迹的工具,比如你点外卖时,系统从接单、派单到配送的全流程记录,就可以用Jaeger追踪)部署到K8s(用来管理一堆程序容器的工具,相当于一个大型的程序“管家”)集群后,都会遇到一个头疼的问题:明明程序正常跑,Jaeger却偶尔甚至经常丢追踪数据——要么某条用户操作的轨迹断了,要么整个微服务链路没被记录。这背后最常见的坑,就是K8s的网络策略(相当于给程序之间的通信加的“门禁”,谁能连谁、连哪个端口都要管)和Jaeger自身的配置没对上。 先给大家说个真实的踩坑场景:去年我帮一个做电商的朋友排查问题,他们的K8s集群里,前端、订单、支付三个微服务,每个服务都加了网络策略,只允许特定服务之间通信。Jaeger是单独部署在一个叫jaeger的命名空间里的,结果上线一周后,运营发现用户从加购物车到下单的链路追踪经常断,有时候甚至连Jaeger界面都看不到任何数据。最后查了三天才发现,是网络策略把微服务连Jaeger的端口给拦了,而且Jaeger的配置里还开了个容易被忽略的“重试限制”,导致丢数据后没补回来。 这篇文章就从这个真实场景出发,一步步讲怎么排查这类问题,帮大家避开网络和配置的坑。

二、核心概念铺垫:先搞懂两个基础东西

要排查问题,得先搞清楚两个最核心的东西,不然看后面的步骤会懵。

2.1 Jaeger的通信逻辑(大白话版)

Jaeger本身是一套分布式追踪系统,它的核心组件分两类:一类是客户端(比如Java的Jaeger客户端库、Python的Jaeger客户端),另一类是服务端(Jaeger Agent、Jaeger Collector、Jaeger UI)。客户端会把程序的追踪数据(比如调用时间、调用的服务名)发给Agent,Agent再把数据传给Collector,最后Collector把数据存起来,UI用来展示。 这里有个关键的点:客户端和Agent之间的通信端口是固定的,默认用的是UDP的6831端口(专门用来发追踪数据),如果用的是Agentless模式(客户端直接连Collector,不用Agent),那用的是HTTP的14268端口。

2.2 K8s网络策略的作用(大白话版)

K8s的网络策略(NetworkPolicy)是用来限制Pod之间通信的规则,相当于给每个Pod加了个“门禁卡”,只有符合规则的请求才能进来。比如你可以设置:只有前端服务的Pod能连订单服务的Pod,其他服务连不了;或者某个Pod只能连特定命名空间里的Pod。 这里有个容易踩的坑:K8s的网络策略是“白名单”模式,也就是说,只要你加了网络策略,默认所有没被允许的请求都会被拦。比如你给微服务加了网络策略,只允许连其他微服务,那微服务连Jaeger的请求就会被拦,除非你专门加规则允许。

三、排查实战步骤:从简单到复杂一步步来

排查问题的思路一定要从简单到复杂,先排除最容易犯的错,再查复杂的配置。

3.1 第一步:先查Jaeger自身的基础配置

很多人丢追踪,不是网络的问题,是Jaeger的配置没弄对,所以先查这个,省得白忙活。 这里给大家一个标准的Jaeger配置示例,技术栈统一用K8s的YAML配置(后面所有示例都用这个技术栈),大家可以对照着看自己的配置有没有问题。

# Jaeger Collector的Deployment配置,技术栈:K8s YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jaeger-collector
  namespace: jaeger # Jaeger所在的命名空间
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jaeger-collector
  template:
    metadata:
      labels:
        app: jaeger-collector
    spec:
      containers:
      - name: jaeger-collector
        image: jaegertracing/jaeger-collector:latest # 用官方最新镜像,避免旧版本bug
        ports:
        - containerPort: 14268 # Agentless模式下,客户端连Collector的HTTP端口
          name: http
        - containerPort: 14250 # 连其他组件的端口,这里不用改
          name: grpc
        env:
        # 关键配置:开启追踪数据的持久化,不然重启就丢
        - name: SPAN_STORAGE_TYPE
          value: elasticsearch # 用Elasticsearch存追踪数据,也可以用Cassandra
        # 关键配置:设置客户端连的地址,要和后面的Service对应
        - name: COLLECTOR_ZIPKIN_HTTP_PORT
          value: "9411"
        # 关键配置:设置Agent的端口,要是用Agent模式的话
        - name: AGENT_HTTP_PORT
          value: "14268"

查配置的时候,重点看这几个地方: 第一,有没有开启追踪数据的持久化?如果没开,Jaeger重启后之前的数据就丢了,会误以为是追踪丢失。 第二,配置的端口对不对?比如Agent模式下,客户端要连Agent的UDP 6831端口,要是配置里把这个端口关了,肯定收不到数据。 第三,有没有设置“重试限制”?比如有些Jaeger配置里,客户端发数据失败后,只重试3次,超过就丢数据,要是网络偶尔卡,就会丢追踪。

3.2 第二步:排查K8s网络策略的问题

这是最常见的坑,很多人加了网络策略,忘了允许微服务连Jaeger。

3.2.1 先看微服务有没有加网络策略

首先要查你的微服务所在的命名空间,有没有加网络策略。用下面的命令查:

# 技术栈:K8s Shell命令,查指定命名空间下的所有网络策略
kubectl get netpol -n <你的微服务命名空间>

如果输出有内容,说明加了网络策略,那就要看策略里有没有允许连Jaeger。

3.2.2 看网络策略的具体规则

用下面的命令看网络策略的具体内容:

# 技术栈:K8s Shell命令,查看指定网络策略的详细配置
kubectl describe netpol <网络策略名称> -n <你的微服务命名空间>

比如我之前那个电商朋友的微服务网络策略,内容是这样的:

# 电商微服务的网络策略,技术栈:K8s YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: microservice-netpol
  namespace: e-commerce # 微服务所在的命名空间
spec:
  podSelector:
    matchLabels:
      app: microservice # 匹配所有微服务的Pod
  policyTypes:
  - Ingress # 允许哪些请求进来
  - Egress # 允许哪些请求出去(重点看这个)
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: frontend # 允许连前端服务
    ports:
    - protocol: TCP
      port: 80
  - to:
    - podSelector:
        matchLabels:
          app: order # 允许连订单服务
    ports:
    - protocol: TCP
      port: 8080

这个策略里,只允许微服务连前端和订单服务,根本没提Jaeger,所以微服务连Jaeger的请求会被拦。

3.2.3 修复网络策略的示例

要修复这个问题,就要在网络策略的Egress规则里,加上允许连Jaeger的规则。比如下面这个修复后的示例:

# 修复后的电商微服务网络策略,技术栈:K8s YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: microservice-netpol
  namespace: e-commerce
spec:
  podSelector:
    matchLabels:
      app: microservice
  policyTypes:
  - Ingress
  - Egress
  egress:
  # 原来的规则不变
  - to:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 80
  - to:
    - podSelector:
        matchLabels:
          app: order
    ports:
    - protocol: TCP
      port: 8080
  # 新增的规则:允许连Jaeger的Collector
  - to:
    - namespaceSelector:
        matchLabels:
          name: jaeger # 匹配Jaeger所在的命名空间
    ports:
    - protocol: TCP # Agentless模式用TCP,端口14268
      port: 14268
    # 如果是Agent模式,加下面的规则
    # - protocol: UDP
    #   port: 6831

这里要注意:如果Jaeger用的是Agent模式,就要允许微服务连Agent的UDP 6831端口;如果是Agentless模式,就要允许连Collector的TCP 14268端口。

3.3 第三步:排查Jaeger和微服务的网络连通性

就算配置对了,有时候还是会有网络问题,比如Jaeger的Service没对,或者DNS解析有问题。

3.3.1 先查Jaeger的Service配置

Jaeger的Service是用来让微服务找到Jaeger的,相当于一个“地址簿”。用下面的命令查Jaeger的Service:

# 技术栈:K8s Shell命令,查Jaeger命名空间下的Service
kubectl get svc -n jaeger

比如输出的结果里,Jaeger Collector的Service应该是这样的:

NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
jaeger-collector   ClusterIP   10.96.123.45    <none>        14268/TCP        7d

这里的Cluster-IP是Jaeger的内部地址,微服务要通过这个地址连Jaeger。

3.3.2 测试微服务和Jaeger的连通性

可以在微服务的Pod里,直接连Jaeger的端口,测试能不能通。比如用下面的命令进入微服务的Pod:

# 技术栈:K8s Shell命令,进入指定Pod的终端
kubectl exec -it <微服务Pod名称> -n <微服务命名空间> -- bash

然后在Pod里用curl命令测试连通性:

# 技术栈:Shell命令,测试连Jaeger的14268端口
curl -v http://jaeger-collector.jaeger:14268/api/traces

如果输出“Connection refused”,说明网络不通,要么是网络策略的问题,要么是Jaeger的Service配置错了。 如果输出“Connection timed out”,说明是网络策略把请求拦了,因为K8s的网络策略默认拦请求的时候,不会返回拒绝,只会超时。

3.4 第四步:排查Jaeger的配置陷阱

除了网络问题,Jaeger自身的配置也有很多容易踩的坑,比如采样率、重试机制、存储配置。

3.4.1 采样率配置的坑

Jaeger的采样率是指,Jaeger会按一定比例收集追踪数据,比如采样率是10%,就只会收集10%的请求数据,剩下的90%会被丢。很多人会把采样率设得很低,以为能省资源,结果误以为是追踪丢失。 查采样率的配置,要看Jaeger客户端的配置,比如Java的客户端配置:

// Jaeger Java客户端配置示例,技术栈:Java
@Configuration
public class JaegerConfig {
    @Bean
    public Tracer jaegerTracer() {
        // 采样率设为100%,也就是收集所有请求的数据
        Sampler sampler = new ConstSampler(true);
        return new Tracer.Builder("e-commerce-service")
                .withSampler(sampler)
                .build();
    }
}

这里要注意:如果是生产环境,采样率可以根据情况调整,但测试环境一定要设为100%,不然测试的时候会误以为丢追踪。

3.4.2 重试机制的坑

Jaeger的客户端默认有重试机制,但如果网络偶尔卡,重试次数不够,还是会丢数据。比如Java的Jaeger客户端,默认重试次数是3次,超过就丢数据。可以把重试次数调高,比如设为5次:

// Jaeger Java客户端配置示例,技术栈:Java,调整重试次数
@Bean
public Tracer jaegerTracer() {
    Sampler sampler = new ConstSampler(true);
    return new Tracer.Builder("e-commerce-service")
            .withSampler(sampler)
            // 设置重试次数为5次
            .withReporter(new RemoteReporter.Builder()
                    .withMaxQueueSize(1000) // 队列大小,防止数据堆积
                    .withFlushInterval(1000) // 刷新间隔,1秒刷一次
                    .withMaxRetries(5) // 重试次数
                    .build())
            .build();
}

3.4.3 存储配置的坑

Jaeger的追踪数据是存在存储里的,比如Elasticsearch、Cassandra,如果存储满了,或者存储的配置错了,Jaeger就存不了数据,导致追踪丢失。比如Elasticsearch的索引满了,Jaeger就会报错,存不了数据。 查存储的配置,要看Jaeger的环境变量,比如Elasticsearch的配置:

# Jaeger Collector的环境变量配置,技术栈:K8s YAML
env:
- name: SPAN_STORAGE_TYPE
  value: elasticsearch
- name: ES_SERVER_URLS
  value: http://elasticsearch:9200 # Elasticsearch的地址
- name: ES_INDEX_PREFIX
  value: jaeger # 索引前缀
- name: ES_NUM_REPLICAS
  value: 1 # 副本数

如果Elasticsearch的地址错了,或者索引满了,Jaeger就存不了数据,要及时扩容或者清理旧数据。

四、应用场景、优缺点和注意事项

4.1 应用场景

这篇文章讲的排查方法,主要适用于以下场景: 第一,Jaeger部署在K8s集群上,微服务加了网络策略的场景; 第二,Jaeger用Agentless模式或者Agent模式,微服务和Jaeger在不同命名空间的场景; 第三,生产环境或者测试环境,Jaeger偶尔丢追踪数据的场景; 第四,刚把Jaeger部署到K8s上,上线后发现追踪丢失的场景。

4.2 技术优缺点

排查过程中用到的技术,各有优缺点: 第一,K8s网络策略的优点是能限制Pod之间的通信,提高安全性,缺点是配置复杂,容易漏规则; 第二,Jaeger Agent模式的优点是能缓存追踪数据,减少网络请求,缺点是每个微服务都要部署Agent,增加了运维成本; 第三,Jaeger Agentless模式的优点是部署简单,不用每个微服务都部署Agent,缺点是客户端直接连Collector,网络压力大; 第四,Jaeger用Elasticsearch存储的优点是查询速度快,支持复杂查询,缺点是存储成本高,需要维护Elasticsearch集群。

4.3 注意事项

排查和配置的时候,要注意以下几点: 第一,K8s网络策略是白名单模式,加了策略后一定要测试所有需要的通信; 第二,Jaeger的采样率在测试环境一定要设为100%,避免测试结果不准; 第三,Jaeger的配置要和Service对应,避免地址错了连不上; 第四,Jaeger的存储要定期清理旧数据,避免满了存不了新数据; 第五,排查的时候要从简单到复杂,先查配置,再查网络,最后查复杂的陷阱。

五、文章总结

Jaeger部署在K8s集群上丢追踪数据,大部分都是网络策略和配置的坑,只要按照正确的步骤排查,就能很快找到问题。排查的核心思路是:先排除Jaeger自身的基础配置问题,再查K8s的网络策略有没有漏规则,然后测试网络连通性,最后排查Jaeger的配置陷阱。 另外,配置的时候一定要注意细节,比如网络策略的端口要和Jaeger的端口对应,采样率要根据环境调整,重试次数要足够,存储要定期维护。只要避开这些坑,就能让Jaeger正常工作,帮你快速排查微服务的问题。