You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenTelemetry问题:Kubernetes中Zipkin副本间追踪数据拆分

问题描述

在Scala项目中使用OpenTelemetry实现链路追踪,通过Zipkin查看追踪数据,当前遇到同一请求的追踪数据被拆分到两个Zipkin副本中的问题,具体情况如下:

项目代码

OpenTelemetry Tracer初始化代码

object OpenTelemetry {

  val urlForZipkin ="http://zipkin:9411/api/v2/spans"

  private val exporter: ZipkinSpanExporter = ZipkinSpanExporter.builder()
    .setEndpoint(applicationConfig.getString("urlForZipkin"))
    .build()

  private val labelSet: Attributes = Attributes.builder().put("service.name", "my-service").build()

  private val tracerProvider: SdkTracerProvider = SdkTracerProvider.builder()
    .addSpanProcessor(SimpleSpanProcessor.create(exporter))
    .setResource(Resource.create(labelSet))
    .build()

  private val openTelemetry: OpenTelemetry = OpenTelemetrySdk.builder()
    .setTracerProvider(tracerProvider)
    .build()

  val tracer: Tracer = openTelemetry.getTracer("myTracer")
}

Span使用示例

val span = tracer.spanBuilder("my-route: Span").startSpan()

val childSpan = tracer.spanBuilder("child-span")
      .setParent(Context.current.`with`(span))
      .startSpan()

Kubernetes部署配置

Zipkin Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: zipkin-deployment
  namespace: my-ns

spec:
  replicas: 2
  selector:
    matchLabels:
      app: zipkin-label
  template:
    metadata:
      name: zipkin-pod
      labels:
        app: zipkin-label
    spec:
      containers:
        - name: zipkin
          image: openzipkin/zipkin
          ports:
            - containerPort: 9411

Zipkin Service

---
apiVersion: v1
kind: Service
metadata:
  name: zipkin
  namespace: my-ns

spec:
  type: NodePort
  selector:
    app: zipkin-label
  ports:
    - port: 9411
      targetPort: 9411

问题现象

同一请求的所有Span拥有相同的Trace ID,但由于Kubernetes Service默认的轮询负载均衡策略,这些Span会被分发到不同的Zipkin副本,导致无法在单个Zipkin实例中查看完整链路;当Zipkin副本数设为1时,所有追踪数据能正常聚合。

需求:让同一请求的所有追踪数据发送到同一个Zipkin副本。

解决方案

方案1:修改Kubernetes Service的会话亲和性

通过设置Service的会话亲和性,让同一个应用Pod的所有请求都路由到同一个Zipkin副本,这样同一请求的所有Span(来自同一个应用Pod)会被发送到同一个Zipkin实例。

修改Zipkin Service的配置,添加会话亲和规则:

apiVersion: v1
kind: Service
metadata:
  name: zipkin
  namespace: my-ns

spec:
  type: NodePort
  selector:
    app: zipkin-label
  ports:
    - port: 9411
      targetPort: 9411
  # 添加会话亲和配置
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 3600  # 可根据业务需求调整超时时间

优势:无需修改应用代码,仅通过Kubernetes配置实现;缺点:亲和性基于应用Pod的IP,若应用本身有多个副本,同一请求的Span可能分布在不同应用Pod,仍会被发送到不同Zipkin副本。

方案2:应用层基于Trace ID哈希选择Zipkin实例

在应用中实现基于Trace ID的哈希路由逻辑,确保同一Trace ID的所有Span发送到同一个Zipkin副本。

步骤1:维护Zipkin实例地址列表

通过Kubernetes DNS服务发现或配置中心获取所有Zipkin实例的地址,示例:

// 示例:两个Zipkin实例的具体地址
val zipkinEndpoints = List(
  "http://zipkin-0.zipkin.my-ns.svc.cluster.local:9411/api/v2/spans",
  "http://zipkin-1.zipkin.my-ns.svc.cluster.local:9411/api/v2/spans"
)

步骤2:实现自定义SpanProcessor

基于Trace ID哈希选择对应的Zipkin Exporter:

// 自定义SpanProcessor,根据Trace ID路由到指定Zipkin实例
class TraceIdAwareSpanProcessor(exporters: List[ZipkinSpanExporter]) extends SpanProcessor {
  override def onStart(parentContext: Context, span: ReadWriteSpan): Unit = {}

  override def onEnd(span: ReadOnlySpan): Unit = {
    val traceId = span.getTraceId
    // 基于Trace ID哈希计算索引,选择对应的Exporter
    val index = Math.abs(traceId.hashCode) % exporters.size
    exporters(index).export(List(span))
  }

  override def isStartRequired: Boolean = false
  override def isEndRequired: Boolean = true
}

// 初始化对应每个Zipkin实例的Exporter
private val exporters = zipkinEndpoints.map(endpoint => 
  ZipkinSpanExporter.builder().setEndpoint(endpoint).build()
)

// 使用自定义SpanProcessor初始化TracerProvider
private val tracerProvider: SdkTracerProvider = SdkTracerProvider.builder()
  .addSpanProcessor(new TraceIdAwareSpanProcessor(exporters))
  .setResource(Resource.create(labelSet))
  .build()

优势:能确保同一Trace ID的所有Span发送到同一个Zipkin副本,不受应用副本数影响;缺点:需要修改应用代码,且需维护Zipkin实例列表的更新逻辑。

方案3:配置Zipkin共享存储后端

不限制Span发送到哪个Zipkin副本,而是让所有Zipkin副本共享同一个存储后端(如MySQL、Redis、Elasticsearch),查询时Zipkin会从共享存储中聚合所有Span,展示完整链路。

以MySQL为例,修改Zipkin Deployment配置:

spec:
  template:
    spec:
      containers:
        - name: zipkin
          image: openzipkin/zipkin
          ports:
            - containerPort: 9411
          env:
            - name: STORAGE_TYPE
              value: mysql
            - name: MYSQL_HOST
              value: mysql-service.my-ns.svc.cluster.local
            - name: MYSQL_PORT
              value: "3306"
            - name: MYSQL_DB
              value: zipkin
            - name: MYSQL_USER
              value: zipkin_user
            - name: MYSQL_PASS
              value: zipkin_password

优势:无需修改应用代码,Zipkin副本可水平扩展;缺点:需要额外部署和维护共享存储服务。

内容的提问来源于stack exchange,提问作者Asif khAn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 04:44:53