安装带 Elasticsearch 的 Alauda Distributed Tracing

安装 Alauda Distributed Tracing 平台包括以下步骤:

  1. 安装 Alauda Build of OpenTelemetry v2 Operator
  2. 部署 Alauda Build of Jaeger v2
  3. 部署 OpenTelemetry Collector,将 trace 转发到 Jaeger

安装 Alauda Build of OpenTelemetry v2 Operator

Alauda Build of OpenTelemetry v2 Operator 管理 Jaeger v2 和 OpenTelemetry Collector 实例的生命周期。在部署任何 tracing 组件之前,必须先安装此 Operator。

通过 Web 控制台安装

按照 Alauda Build of OpenTelemetry v2 文档中 通过 Web 控制台安装 部分的说明进行操作。

通过 CLI 安装

按照 Alauda Build of OpenTelemetry v2 文档中 通过 CLI 安装 部分的说明进行操作。

部署 Alauda Build of Jaeger v2

Jaeger v2 以由 Alauda Build of OpenTelemetry v2 Operator 管理的 OpenTelemetryCollector 自定义资源形式部署。它使用一个后端存储,并通过 OAuth2 Proxy sidecar 与 Alauda Container Platform 身份验证系统集成。

前提条件

  • 已安装 Alauda Container Platform Application Observability(之前称为 Alauda Build of OpenTelemetry v2)Operator。
  • 有可用的 Elasticsearch 8.x 实例,并且已获取其 endpoint URL、用户名和密码。
  • 集群管理员已登录活动 ACP CLI (kubectl) 会话,并具有 cluster-admin 角色。
  • 已安装 jq 命令行工具。

操作步骤

  1. 设置用于连接 Elasticsearch 的用户可配置环境变量:

    export ES_ENDPOINT='<Elasticsearch endpoint URL>'
    export ES_USER='<Elasticsearch username>'
    export ES_PASS='<Elasticsearch password>'

    将占位值替换为实际的 Elasticsearch 凭据。

  2. 从集群中获取平台配置和 Jaeger 相关容器镜像:

    export PLATFORM_URL=$(kubectl -nkube-public get configmap global-info -o jsonpath='{.data.platformURL}')
    export CLUSTER_NAME=$(kubectl -nkube-public get configmap global-info -o jsonpath='{.data.clusterName}')
    export ALB_CLASS_NAME=$(kubectl -nkube-public get configmap global-info -o jsonpath='{.data.systemAlbIngressClassName}')
    
    export OIDC_ISSUER=$(kubectl -nkube-public get configmap global-info -o jsonpath='{.data.oidcIssuer}')
    OIDC_CLIENT_SECRET_REF=$(kubectl -nkube-public get configmap global-info -o jsonpath='{.data.oidcClientSecretRef}')
    if [ -n "$OIDC_CLIENT_SECRET_REF" ]; then
      SYSTEM_NAMESPACE=$(kubectl -nkube-public get configmap global-info -o jsonpath='{.data.systemNamespace}')
      export OIDC_CLIENT_ID=$(kubectl -n"$SYSTEM_NAMESPACE" get secret "$OIDC_CLIENT_SECRET_REF" -o go-template='{{index .data "client-id"}}' | base64 -d)
      export OIDC_CLIENT_SECRET=$(kubectl -n"$SYSTEM_NAMESPACE" get secret "$OIDC_CLIENT_SECRET_REF" -o go-template='{{index .data "client-secret"}}' | base64 -d)
    else
      export OIDC_CLIENT_ID=$(kubectl -nkube-public get configmap global-info -o jsonpath='{.data.oidcClientID}')
      export OIDC_CLIENT_SECRET=$(kubectl -nkube-public get configmap global-info -o jsonpath='{.data.oidcClientSecret}')
    fi
    
    JAEGER_RELATED_IMAGES=$(kubectl get csv -n opentelemetry-operator2 \
      -l 'operators.coreos.com/opentelemetry-operator2.opentelemetry-operator2=' \
      -o jsonpath='{.items[0].spec.relatedImages}')
    export JAEGER_IMAGE=$(echo "$JAEGER_RELATED_IMAGES" | jq -r '.[] | select(.name=="component.jaeger") | .image')
    export JAEGER_ES_ROLLOVER_IMAGE=$(echo "$JAEGER_RELATED_IMAGES" | jq -r '.[] | select(.name=="component.jaeger-es-rollover") | .image')
    export JOAUTH2_PROXY_IMAGE=$(echo "$JAEGER_RELATED_IMAGES" | jq -r '.[] | select(.name=="component.oauth2-proxy") | .image')
    NOTE

    确认命令执行完成且没有错误。

  3. 设置默认环境变量。可以根据部署要求调整这些值:

    # Namespace for the Jaeger instance
    export JAEGER_NS="jaeger-system"
    # Name of the Jaeger instance
    export JAEGER_INSTANCE_NAME="jaeger"
    # Elasticsearch index prefix for Jaeger data
    export JAEGER_ES_INDEX_PREFIX="acp-${CLUSTER_NAME}"
    # Base path for the Jaeger UI
    export JAEGER_BASEPATH="/clusters/${CLUSTER_NAME}/jaeger"
  4. 创建 Jaeger namespace 和 Elasticsearch 凭据 Secret:

    kubectl get namespace ${JAEGER_NS} &> /dev/null || kubectl create namespace ${JAEGER_NS}
    
    kubectl create secret generic es-credentials \
      --namespace=${JAEGER_NS} \
      --from-literal=ES_USER=${ES_USER} \
      --from-literal=ES_PASS=${ES_PASS} \
      --dry-run=client -o yaml | kubectl apply -f -

    验证 Secret 是否已创建:

    kubectl get secret es-credentials -n ${JAEGER_NS}
  5. 在 Elasticsearch 中创建 ILM(Index Lifecycle Management)Policy。Jaeger 使用 ILM 管理索引滚动和保留:

    curl -k -u "${ES_USER}:${ES_PASS}" -X PUT \
      "${ES_ENDPOINT}/_ilm/policy/jaeger-ilm-policy" \
      -H 'Content-Type: application/json' \
      --data-binary @- << 'EOF'
    {
      "policy": {
        "phases": {
          "hot": {
            "min_age": "0ms",
            "actions": {
              "rollover": {
                "max_primary_shard_size": "50gb",
                "max_age": "1d"
              },
              "set_priority": {
                "priority": 100
              }
            }
          },
          "delete": {
            "min_age": "7d",
            "actions": {
              "delete": {}
            }
          }
        }
      }
    }
    EOF

    关键字段:

    • policy.phases.hot.actions.rollover.max_primary_shard_size:单个 primary shard 的最大大小。当 shard 超过此大小时,会触发滚动以创建新索引。默认值:50gb。
    • policy.phases.hot.actions.rollover.max_age:触发滚动前索引的最大存留时间。默认值:1d(1 天)。
    • policy.phases.delete.min_age:滚动后等待删除旧索引的时间。默认值:7d(7 天)。

    验证 ILM Policy:

    curl -k -u "${ES_USER}:${ES_PASS}" "${ES_ENDPOINT}/_ilm/policy/jaeger-ilm-policy?pretty"

    输出应显示 ILM Policy 的详细信息,包括 hot 和 delete 阶段。

  6. 使用 jaeger-es-rollover 工具初始化索引别名和模板。这将为 Jaeger 数据存储准备 Elasticsearch:

    kubectl apply -n ${JAEGER_NS} -f - <<EOF
    apiVersion: batch/v1
    kind: Job
    metadata:
      name: jaeger-es-rollover-init
    spec:
      template:
        spec:
          containers:
          - name: es-rollover-init
            image: "${JAEGER_ES_ROLLOVER_IMAGE}"
            args:
              - init
              - "${ES_ENDPOINT}"
            env:
            - name: INDEX_PREFIX
              value: "${JAEGER_ES_INDEX_PREFIX}"
            - name: ES_USE_ILM
              value: "true"
            - name: ES_TLS_ENABLED
              value: "true"
            - name: ES_TLS_SKIP_HOST_VERIFY
              value: "true"
            - name: ES_USERNAME
              valueFrom:
                secretKeyRef:
                  name: es-credentials
                  key: ES_USER
            - name: ES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: es-credentials
                  key: ES_PASS
          restartPolicy: Never
      backoffLimit: 3
    EOF

    等待 Job 完成,然后验证索引模板和别名是否已创建:

    kubectl wait --for=condition=complete job/jaeger-es-rollover-init \
      -n ${JAEGER_NS} --timeout=120s
    
    # Verify index templates
    curl -k -sS -u "${ES_USER}:${ES_PASS}" "${ES_ENDPOINT}/_index_template?pretty" \
      | grep ${JAEGER_ES_INDEX_PREFIX}-jaeger-
    
    # Verify index aliases
    curl -k -sS -u "${ES_USER}:${ES_PASS}" "${ES_ENDPOINT}/_alias?pretty" \
      | grep ${JAEGER_ES_INDEX_PREFIX}-jaeger-

    预期结果如下:

    • Job 状态为 Complete。
    • Elasticsearch 中存在匹配 ${JAEGER_ES_INDEX_PREFIX}-jaeger-* 的索引模板。
    • Elasticsearch 中存在 ${JAEGER_ES_INDEX_PREFIX}-jaeger-*-read 和 ${JAEGER_ES_INDEX_PREFIX}-jaeger-*-write 的索引别名。
  7. Job 完成后清理初始化 Job:

    kubectl delete job jaeger-es-rollover-init -n ${JAEGER_NS}
  8. 为 OAuth2 Proxy 创建 Secret,该 Proxy 用于将 Jaeger UI 与 Alauda Container Platform 身份验证集成:

    # Generate a cookie secret for the OAuth2 Proxy:
    OAUTH2_PROXY_COOKIE_SECRET=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d -- '\n' | tr -- '+/' '-_')
    # Create the Secret:
    kubectl create secret generic ${JAEGER_INSTANCE_NAME}-oauth2-proxy \
      --namespace=${JAEGER_NS} \
      --from-literal=OAUTH2_PROXY_CLIENT_SECRET=${OIDC_CLIENT_SECRET} \
      --from-literal=OAUTH2_PROXY_COOKIE_SECRET=${OAUTH2_PROXY_COOKIE_SECRET} \
      --dry-run=client -o yaml | kubectl apply -f -
  9. 创建名为 jaeger.yaml 的文件,内容如下:

    jaeger.yaml
    apiVersion: opentelemetry.io/v1beta1
    kind: OpenTelemetryCollector
    metadata:
      labels:
        prometheus: kube-prometheus
      name: ${JAEGER_INSTANCE_NAME}
      namespace: ${JAEGER_NS}
    spec:
      image: "${JAEGER_IMAGE}"
      mode: deployment
      replicas: 1
    
      resources:
        requests:
          cpu: 100m
          memory: 256Mi
        limits:
          cpu: "2"
          memory: 2Gi
    
      ports:
        - name: oauth2-proxy
          port: 4180
        - name: jaeger-grpc
          port: 16685
    
      observability:
        metrics:
          enableMetrics: true
    
      volumes:
        - name: es-credentials
          secret:
            secretName: es-credentials
            items:
              - key: ES_PASS
                path: pass
        - name: oauth2-proxy-secrets
          secret:
            secretName: ${JAEGER_INSTANCE_NAME}-oauth2-proxy
            items:
              - key: OAUTH2_PROXY_CLIENT_SECRET
                path: client-secret
              - key: OAUTH2_PROXY_COOKIE_SECRET
                path: cookie-secret
      volumeMounts:
        - name: es-credentials
          mountPath: /etc/jaeger/es-credentials
          readOnly: true
    
      config:
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: "0.0.0.0:4317"
              http:
                endpoint: "0.0.0.0:4318"
    
        processors:
          batch: {}
          memory_limiter:
            check_interval: 1s
            limit_percentage: 80
            spike_limit_percentage: 20
    
        exporters:
          debug: {}
          jaeger_storage_exporter:
            trace_storage: es_storage
    
        extensions:
          healthcheckv2:
            use_v2: true
            http:
              endpoint: "0.0.0.0:13133"
    
          jaeger_storage:
            backends:
              es_storage:
                elasticsearch:
                  server_urls:
                    - "${ES_ENDPOINT}"
                  auth:
                    basic:
                      username: "${ES_USER}"
                      password_file: /etc/jaeger/es-credentials/pass
                  tls:
                    insecure_skip_verify: true
                  use_aliases: true
                  use_ilm: true
                  service_cache_ttl: 12h
                  create_mappings: false
                  indices:
                    index_prefix: "${JAEGER_ES_INDEX_PREFIX}"
                    spans:
                      shards: 5
                      replicas: 1
                    services:
                      shards: 5
                      replicas: 1
                    dependencies:
                      shards: 5
                      replicas: 1
                    sampling:
                      shards: 5
                      replicas: 1
    
          jaeger_query:
            storage:
              traces: es_storage
            ui:
              config_file: ""
            base_path: "${JAEGER_BASEPATH}"
            http:
              endpoint: 0.0.0.0:16686
    
        service:
          extensions: [healthcheckv2, jaeger_storage, jaeger_query]
          pipelines:
            traces:
              receivers: [otlp]
              processors: [memory_limiter, batch]
              exporters: [debug, jaeger_storage_exporter]
          telemetry:
            resource:
              service.name: jaeger
            metrics:
              level: detailed
              readers:
                - pull:
                    exporter:
                      prometheus:
                        host: "0.0.0.0"
                        port: 8888
                        without_scope_info: true
                        without_type_suffix: true
                        without_units: true
            logs:
              level: info
    
      additionalContainers:
        - name: oauth2-proxy
          image: ${JOAUTH2_PROXY_IMAGE}
          args:
            - --http-address=0.0.0.0:4180
            - --upstream=http://127.0.0.1:16686
            - --proxy-prefix=${JAEGER_BASEPATH}/oauth2
            - --redirect-url=${PLATFORM_URL}${JAEGER_BASEPATH}/oauth2/callback
            - --provider=oidc
            - --oidc-issuer-url=${OIDC_ISSUER}
            - --scope=openid profile email groups ext
            - --email-domain=*
            - --code-challenge-method=S256
            - --insecure-oidc-allow-unverified-email=true
            - --cookie-secure=false
            - --skip-provider-button=true
            - --ssl-insecure-skip-verify=true
            - --skip-jwt-bearer-tokens=true
            - --client-id=${OIDC_CLIENT_ID}
            - --client-secret-file=/etc/oauth2-proxy/client-secret
            - --cookie-secret-file=/etc/oauth2-proxy/cookie-secret
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 500m
              memory: 256Mi
          volumeMounts:
            - name: oauth2-proxy-secrets
              mountPath: /etc/oauth2-proxy
              readOnly: true
          ports:
            - containerPort: 4180
              name: oauth2-proxy
              protocol: TCP
    1. prometheus: kube-prometheus 标签会被自动创建的 ServiceMonitor 资源继承,从而使 ACP Prometheus 能够抓取 Jaeger 指标。
    2. Jaeger v2 容器镜像。这不是默认的 OpenTelemetry Collector 镜像,而是基于 OpenTelemetry Collector framework 构建的自定义 Jaeger binary。
    3. 为 Jaeger 实例启用 Prometheus metrics endpoint。
    4. Jaeger 容器的资源 requests 和 limits。请根据预期的 trace volume 进行调整;吞吐量较高的环境可能需要更多 CPU 和内存。
    5. jaeger_storage 扩展用于配置存储 trace 数据的 Elasticsearch backend。
    6. service_cache_ttl 控制 service name cache 的保留时间。默认值为 12h。如果 ILM hot-to-delete interval 较短,请减小此值,以确保 Jaeger UI 能够及时发现服务。
    7. 使用 ILM mode 时,必须将 create_mappings 设置为 false,因为 index mappings 由 rollover initialization 管理。
    8. index_prefix 必须与第 6 步中 jaeger-es-rollover initialization 使用的 prefix 匹配。有关 shard 和 replica 调优的更多详细信息,请参阅 Shards and Replicas。
    9. jaeger_query 扩展提供 Jaeger Query API 和 Jaeger UI。
    10. additionalContainers 部分定义 OAuth2 Proxy sidecar,该 sidecar 通过与 Alauda Container Platform Dex identity provider 集成来处理 Jaeger UI 的身份验证。
    11. OAuth2 Proxy sidecar 的资源 requests 和 limits。由于此容器仅代理身份验证请求,因此资源需求较低。
  10. 使用 envsubst 渲染 manifest 并应用配置:

    envsubst < jaeger.yaml | kubectl apply -f -
  11. 等待 Jaeger Pod 就绪:

    kubectl rollout status deployment/${JAEGER_INSTANCE_NAME}-collector \
      -n ${JAEGER_NS} --timeout=180s
  12. 为 namespace 添加标签并创建 Ingress 以公开 Jaeger UI:

    kubectl label namespace ${JAEGER_NS} cpaas.io/project=cpaas-system --overwrite
    kubectl apply -n ${JAEGER_NS} -f - <<EOF
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: ${JAEGER_INSTANCE_NAME}
      annotations:
        nginx.ingress.kubernetes.io/enable-cors: "true"
    spec:
      ingressClassName: ${ALB_CLASS_NAME}
      rules:
        - http:
            paths:
              - path: ${JAEGER_BASEPATH}
                pathType: ImplementationSpecific
                backend:
                  service:
                    name: ${JAEGER_INSTANCE_NAME}-collector
                    port:
                      number: 4180
    EOF

    等待 Ingress 就绪:

    kubectl wait --for=jsonpath='{.status.loadBalancer.ingress}' ingress/${JAEGER_INSTANCE_NAME} \
      -n ${JAEGER_NS} --timeout=180s

验证

访问 <platform-url>/clusters/<cluster-name>/jaeger 处的 Jaeger UI,其中 <platform-url> 是 Alauda Container Platform URL,<cluster-name> 是集群名称。

运行以下命令以打印 Jaeger UI URL:

echo "Jaeger UI: ${PLATFORM_URL}${JAEGER_BASEPATH}"

部署 OpenTelemetry Collector

Jaeger v2 运行后,部署一个 OpenTelemetry Collector 实例,以接收来自已插桩应用的跟踪数据并将其转发到 Jaeger。

  1. 创建名为 otel-collector.yaml 的文件,内容如下:

    otel-collector.yaml
    apiVersion: opentelemetry.io/v1beta1
    kind: OpenTelemetryCollector
    metadata:
      labels:
        prometheus: kube-prometheus
      name: otel
      namespace: ${JAEGER_NS}
    spec:
      mode: deployment
      replicas: 1
      resources:
        requests:
          cpu: 100m
          memory: 256Mi
        limits:
          cpu: "2"
          memory: 2Gi
      observability:
        metrics:
          enableMetrics: true
      config:
        receivers:
          otlp:
            protocols:
              grpc:
                endpoint: 0.0.0.0:4317
              http:
                endpoint: 0.0.0.0:4318
          zipkin: {}
        processors:
          batch: {}
          memory_limiter:
            check_interval: 1s
            limit_percentage: 80
            spike_limit_percentage: 20
        exporters:
          debug: {}
          otlp/traces:
            endpoint: "${JAEGER_INSTANCE_NAME}-collector.${JAEGER_NS}.svc.cluster.local:4317"
            tls:
              insecure: true
          prometheus:
            add_metric_suffixes: false
            endpoint: "0.0.0.0:8889"
            resource_to_telemetry_conversion:
              enabled: true # by default resource attributes are dropped
        service:
          pipelines:
            traces:
              receivers: [otlp, zipkin]
              processors: [memory_limiter, batch]
              exporters: [debug, otlp/traces] 
            metrics:
              receivers: [otlp]
              processors: [memory_limiter, batch]
              exporters: [debug, prometheus]
          telemetry:
            metrics:
              readers:
              - pull:
                  exporter:
                    prometheus:
                      host: 0.0.0.0
                      port: 8888
                      without_scope_info: true
                      without_type_suffix: true
                      without_units: true
    1. prometheus: kube-prometheus 标签使 ACP Prometheus 能够通过自动创建的 ServiceMonitor 抓取 Collector 指标。
    2. Collector 容器的资源请求和限制。请根据预期的跟踪吞吐量进行调整。
    3. 使 Operator 能够为 Collector 的指标端点自动创建 ServiceMonitor 资源。
    4. OTLP 接收器通过 gRPC(端口 4317)和 HTTP(端口 4318)接收来自已插桩应用的跟踪数据。
    5. otlp/traces 导出器将接收到的跟踪数据转发到 Jaeger collector 服务。该端点根据 Jaeger 实例名称和命名空间派生。
    6. 跟踪管道通过 OTLP 和 Zipkin 接收数据,经由 memory_limiter 和 batch 处理,然后同时导出到 debug 导出器(用于日志记录)和 otlp/traces(用于转发到 Jaeger)。
  2. 使用 envsubst 渲染清单并应用配置:

    envsubst < otel-collector.yaml | kubectl apply -f -
  3. 等待 Collector Pod 就绪:

    kubectl rollout status deployment/otel-collector \
      -n ${JAEGER_NS} --timeout=180s

验证

安装所有组件后,通过生成示例跟踪数据验证端到端跟踪管道。

  1. 将 telemetrygen 部署为测试客户端,以生成示例跟踪数据:

    kubectl apply -f - <<EOF
    apiVersion: v1
    kind: Pod
    metadata:
      name: telemetrygen
      namespace: ${JAEGER_NS}
    spec:
      restartPolicy: Never
      containers:
        - name: telemetrygen
          image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:latest
          args:
            - traces
            - --otlp-endpoint=otel-collector.${JAEGER_NS}.svc.cluster.local:4317
            - --otlp-insecure
            - --duration=150s
            - --interval=5s
            - --child-spans=3
            - --rate=2
            - --service=telemetrygen
            - --workers=1
    EOF
    # Wait for telemetrygen to complete, then clean up the test Pod
    kubectl wait -n ${JAEGER_NS} --for=jsonpath='{.status.phase}'=Succeeded pod/telemetrygen --timeout=10m
    kubectl delete pod -n ${JAEGER_NS} telemetrygen
    NOTE

    --otlp-endpoint 必须指向上一步部署的 OpenTelemetry Collector 服务。

  2. 在 <platform-url>/clusters/<cluster-name>/jaeger 打开 Jaeger UI。

    从 Service 下拉菜单中选择 telemetrygen 服务,然后单击 Find Traces,验证是否可以看到生成的跟踪数据。

    运行以下命令打印 Jaeger UI URL:

    echo "Jaeger UI: ${PLATFORM_URL}${JAEGER_BASEPATH}"

(可选)启用服务性能监控(SPM)

服务性能监控(SPM)以 Jaeger UI 中的 Monitor 标签页呈现,它会聚合 span 数据以生成 RED(请求、错误、持续时间)指标。借助此功能,无需事先了解服务名称或操作名称,即可识别性能问题。有关详细信息,请参阅服务性能监控(SPM)。

启用 SPM 需要在 Jaeger 内部运行 SpanMetrics Connector 来生成 RED 指标,并通过 loadbalancing exporter 将 span 从前端 OpenTelemetry Collector 路由到 Jaeger,以确保即使两个组件都运行多个副本,指标仍然准确。Jaeger 会在 Prometheus endpoint 上公开这些指标,并通过兼容 PromQL 的后端将指标提供给 Monitor 标签页。

前提条件

  • 已部署 Jaeger v2 和 OpenTelemetry Collector(已完成所有前述步骤)。
  • 集群中已提供 ACP 监控。

操作步骤

  1. 从集群中获取监控 endpoint 和凭据:

    export MONITORING_URL=$(kubectl get feature monitoring -o jsonpath='{.spec.accessInfo.database.address}')
    MONITORING_SECRET_NAME=$(kubectl get feature monitoring -o jsonpath='{.spec.accessInfo.database.basicAuth.secretName}')
    
    export MONITORING_USERNAME=$(kubectl -ncpaas-system get secret "$MONITORING_SECRET_NAME" -o jsonpath="{.data.username}" | base64 -d)
    export MONITORING_PASSWORD=$(kubectl -ncpaas-system get secret "$MONITORING_SECRET_NAME" -o jsonpath="{.data.password}" | base64 -d)
  2. 为监控凭据创建 Secret:

    kubectl create secret generic monitoring-credentials \
      --namespace=${JAEGER_NS} \
      --from-literal=username=${MONITORING_USERNAME} \
      --from-literal=password=${MONITORING_PASSWORD} \
      --dry-run=client -o yaml | kubectl apply -f -
  3. 修补 OpenTelemetry Collector,使其通过 loadbalancing exporter 将 span 转发到 Jaeger,而不是使用直接的 otlp/traces exporter。借助 routing_key: service,给定服务的所有 span 都会一致地发送到同一个 Jaeger 副本,因此在 Jaeger 内部运行的 SpanMetrics Connector(下一步)会对每个服务准确聚合一次。这样,当 Collector 或 Jaeger 运行多个副本时,指标仍能保持准确:

    kubectl patch opentelemetrycollector otel -n ${JAEGER_NS} --type=merge -p "
    spec:
      config:
        exporters:
          loadbalancing:
            routing_key: service
            protocol:
              otlp:
                tls:
                  insecure: true
            resolver:
              dns:
                hostname: ${JAEGER_INSTANCE_NAME}-collector-headless.${JAEGER_NS}.svc.cluster.local
                port: '4317' # the dns resolver requires the port as a string
        service:
          pipelines:
            traces:
              exporters: [debug, loadbalancing]
    "

    dns resolver 会通过 OpenTelemetry Operator 自动配置的无头 Service(${JAEGER_INSTANCE_NAME}-collector-headless)发现每个 Jaeger collector Pod,因此无需额外配置 Service 或 RBAC。

    等待 Collector 重启:

    kubectl rollout status deployment/otel-collector \
      -n ${JAEGER_NS} --timeout=180s
  4. 创建名为 jaeger-spm-patch.yaml 的文件,内容如下。此补丁会在 Jaeger 内部运行 SpanMetrics Connector,在 Prometheus endpoint 上公开生成的 RED 指标,并添加兼容 PromQL 的指标后端,以便 Jaeger 将这些指标提供给 Monitor 标签页:

    jaeger-spm-patch.yaml
    spec:
      volumes:
        - name: es-credentials
          secret:
            secretName: es-credentials
            items:
              - key: ES_PASS
                path: pass
        - name: oauth2-proxy-secrets
          secret:
            secretName: ${JAEGER_INSTANCE_NAME}-oauth2-proxy
            items:
              - key: OAUTH2_PROXY_CLIENT_SECRET
                path: client-secret
              - key: OAUTH2_PROXY_COOKIE_SECRET
                path: cookie-secret
        - name: monitoring-credentials
          secret:
            secretName: monitoring-credentials
            items:
              - key: username
                path: user
              - key: password
                path: pass
      volumeMounts:
        - name: es-credentials
          mountPath: /etc/jaeger/es-credentials
          readOnly: true
        - name: monitoring-credentials
          mountPath: /etc/jaeger/monitoring-credentials
          readOnly: true
      config:
        connectors:
          spanmetrics: {} 
        exporters:
          prometheus:
            add_metric_suffixes: false # Jaeger expects standard OTel metric names without _total suffixes
            endpoint: "0.0.0.0:8889"
            resource_to_telemetry_conversion:
              enabled: true
        extensions:
          basicauth/monitoring:
            client_auth:
              username_file: /etc/jaeger/monitoring-credentials/user
              password_file: /etc/jaeger/monitoring-credentials/pass
          jaeger_storage:
            metric_backends:
              monitoring_metrics_storage:
                prometheus:
                  endpoint: ${MONITORING_URL}
                  tls:
                    insecure_skip_verify: true
                  auth:
                    authenticator: basicauth/monitoring
          jaeger_query:
            storage:
              metrics: monitoring_metrics_storage
        service:
          extensions: [basicauth/monitoring, healthcheckv2, jaeger_storage, jaeger_query] 
          pipelines:
            traces:
              exporters: [debug, jaeger_storage_exporter, spanmetrics]
            metrics/spanmetrics:
              receivers: [spanmetrics]
              exporters: [prometheus]
    1. monitoring-credentials volume 会将监控 basic-auth 凭据挂载到 Jaeger 容器中。
    2. monitoring-credentials volumeMount 会使凭据可在 /etc/jaeger/monitoring-credentials/ 中使用。
    3. spanmetrics connector 会根据 Jaeger 接收的 span 生成 RED 指标。由于 Collector 会将每个服务路由到单个 Jaeger 副本,因此每个副本都会正确聚合一组互不重叠的服务。
    4. prometheus exporter 会在端口 8889 上公开生成的指标。Jaeger 实例已带有 prometheus: kube-prometheus 标签和 observability.metrics.enableMetrics: true(在 jaeger.yaml 中设置),因此 Operator 会自动创建 ServiceMonitor,以便 ACP Prometheus 抓取此 endpoint。
    5. basicauth/monitoring extension 为监控指标 endpoint 提供 basic authentication。
    6. metric_backends 部分配置 Jaeger 用于查询 SPM 数据的兼容 PromQL 的指标存储。
    7. 在 jaeger_query extension 中引用指标存储。
    8. 必须将 basicauth/monitoring extension 添加到 service.extensions 列表中才能使其生效。
    9. traces pipeline 现在也会导出到 spanmetrics(存储并生成指标),而专用的 metrics/spanmetrics pipeline 会将生成的指标转发到 prometheus exporter。
    WARNING

    volumes、volumeMounts、service.extensions 以及 pipeline exporters 字段都是数组。合并补丁会完全替换数组,而不是向其中追加内容。上述补丁文件包含所有现有条目以及新增条目,以防止数据丢失。

  5. 应用补丁:

    kubectl patch opentelemetrycollector ${JAEGER_INSTANCE_NAME} -n ${JAEGER_NS} \
      --type=merge -p "$(envsubst < jaeger-spm-patch.yaml)"

    等待 Jaeger 重启:

    kubectl rollout status deployment/${JAEGER_INSTANCE_NAME}-collector \
      -n ${JAEGER_NS} --timeout=180s
扩展到多个副本

要以高可用模式运行 SPM,请增加 otel 和 ${JAEGER_INSTANCE_NAME} collectors 上的 spec.replicas;无需进行其他更改。无状态 Collector 层可以自由扩展,并且由于 span 由 service 进行负载均衡,每个 Jaeger 副本都会负责一组互不重叠的服务,并在无冲突的情况下聚合这些服务。

验证

启用 SPM 后,可以按照上方验证部分中的说明部署 telemetrygen 测试客户端来进行验证。

生成跟踪数据后,在 Jaeger UI 中导航到 Monitor 标签页,查看 telemetrygen 服务聚合后的 RED 指标。

卸载

要从集群中移除 Alauda Distributed Tracing 组件,请按照卸载 Alauda Distributed Tracing中的说明操作。