使用 OpenSearch 安装 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 Build of OpenTelemetry v2 operator。
  • 已提供一个可用的 OpenSearch 3.x 实例,并且你拥有其 endpoint URL、用户名和密码。
  • 由具有 cluster-admin 角色的集群管理员建立的有效 ACP CLI(kubectl)会话。
  • 已安装 jq 命令行工具。

操作步骤

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

    export OPENSEARCH_ENDPOINT='<OpenSearch endpoint URL>'
    export OPENSEARCH_USER='<OpenSearch username>'
    export OPENSEARCH_PASS='<OpenSearch password>'

    将占位符值替换为你的实际 OpenSearch 凭据。

  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_INDEX_CLEANER_IMAGE=$(echo "$JAEGER_RELATED_IMAGES" | jq -r '.[] | select(.name=="component.jaeger-es-index-cleaner") | .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"
    # Index prefix for Jaeger data
    export JAEGER_ES_INDEX_PREFIX="acp-${CLUSTER_NAME}"
    # Maximum age of spans the Jaeger query service will search. Keep aligned with the index-cleaner retention.
    export JAEGER_MAX_SPAN_AGE="168h"
    # Base path for the Jaeger UI
    export JAEGER_BASEPATH="/clusters/${CLUSTER_NAME}/jaeger"
  4. 创建 Jaeger 命名空间和 OpenSearch 凭据 Secret:

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

    验证 Secret 是否已创建:

    kubectl get secret opensearch-credentials -n ${JAEGER_NS}
  5. 为 OAuth2 Proxy 创建一个 Secret,用于将 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 -
  6. 创建一个名为 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: opensearch-credentials
          secret:
            secretName: opensearch-credentials
            items:
              - key: OPENSEARCH_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: opensearch-credentials
          mountPath: /etc/jaeger/opensearch-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: opensearch_storage
    
        extensions:
          healthcheckv2:
            use_v2: true
            http:
              endpoint: "0.0.0.0:13133"
    
          jaeger_storage:
            backends:
              opensearch_storage:
                opensearch:
                  server_urls:
                    - "${OPENSEARCH_ENDPOINT}"
                  auth:
                    basic:
                      username: "${OPENSEARCH_USER}"
                      password_file: /etc/jaeger/opensearch-credentials/pass
                  tls:
                    insecure_skip_verify: true
                  service_cache_ttl: 12h
                  max_span_age: "${JAEGER_MAX_SPAN_AGE}"
                  indices:
                    index_prefix: "${JAEGER_ES_INDEX_PREFIX}"
                    spans:
                      date_layout: "2006-01-02"
                      rollover_frequency: "day"
                      shards: 5
                      replicas: 1
                    services:
                      date_layout: "2006-01-02"
                      rollover_frequency: "day"
                      shards: 5
                      replicas: 1
                    dependencies:
                      date_layout: "2006-01-02"
                      rollover_frequency: "day"
                      shards: 5
                      replicas: 1
                    sampling:
                      date_layout: "2006-01-02"
                      rollover_frequency: "day"
                      shards: 5
                      replicas: 1
    
          jaeger_query:
            storage:
              traces: opensearch_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 二进制文件。
    3. Jaeger 容器的资源请求和限制。请根据预期的 trace 量进行调整;更高吞吐量的环境可能需要更多 CPU 和内存。
    4. 为 Jaeger 实例启用 Prometheus metrics endpoint。
    5. jaeger_storage extension 配置用于存储 trace 数据的 OpenSearch backend
    6. opensearch driver 选择 OpenSearch 存储实现。Jaeger 对 OpenSearch 采用与 Elasticsearch 相同的实现。
    7. service_cache_ttl 控制 service name cache 的保留时长。默认值为 12h。如果索引保留时间较短,请降低此值,以确保 Jaeger UI 能够及时发现 service。
    8. max_span_age 是 query service 将搜索的 span 最大年龄,此处设置为 JAEGER_MAX_SPAN_AGE。请将其与 jaeger-es-index-cleaner 的保留策略保持一致,以免 UI 查询到已删除的索引。
    9. index_prefix 必须与 jaeger-es-index-cleaner CronJob 使用的 INDEX_PREFIX 匹配,否则 cleaner 无法找到要删除的索引。有关分片和副本调优的更多详细信息,请参见 分片和副本
    10. rollover_frequency: daydate_layout 结合后会生成每日索引,例如 ${JAEGER_ES_INDEX_PREFIX}-jaeger-span-YYYY-MM-DD。这些普通的每日索引(不含 rollover alias)正是 jaeger-es-index-cleaner 通过日期后缀进行匹配的对象。
    11. jaeger_query extension 提供 Jaeger Query API 和 Jaeger UI。
    12. additionalContainers 部分定义了 OAuth2 Proxy sidecar,它通过与 Alauda Container Platform Dex Identity Provider 集成来处理 Jaeger UI 的认证。
    13. OAuth2 Proxy sidecar 的资源请求和限制。由于该容器仅代理认证请求,因此资源需求较低。
  7. 使用 envsubst 渲染 manifest 并应用配置:

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

    kubectl rollout status deployment/${JAEGER_INSTANCE_NAME}-collector \
      -n ${JAEGER_NS} --timeout=180s
  9. 部署 jaeger-es-index-cleaner,以定期删除已过期索引。

    为什么不是

    jaeger-es-rollover initjaeger-es-rollover 工具的 init 子命令会调用 Elasticsearch _ilm/policy/... endpoint 来验证是否存在 ILM policy,但 OpenSearch 使用的是 _plugins/_ism/policies/...。两者不兼容,因此 init 在 OpenSearch 上会失败(参见 jaegertracing/jaeger#7121)。

    本指南使用每日索引加 jaeger-es-index-cleaner 的方式,这种方式依赖 rollover aliases,因此不需要 jaeger-es-rollover init

    工作原理

    OpenSearch backend 会写入每日索引,例如 acp-<cluster>-jaeger-span-2026-04-22jaeger-es-index-cleaner 会匹配名为 <prefix>-jaeger-(span|service|dependencies|sampling)-YYYY-MM-DD 的索引,将日期后缀与“今天减去 N 天”进行比较,并删除早于保留期的索引。

    设置 index-cleaner 环境变量:

    # Index retention in days: indices older than this are deleted
    export JAEGER_INDEX_RETENTION_DAYS="7"
    # Daily execution time (UTC). For example, "30 2 * * *" means 02:30 UTC.
    export JAEGER_CLEANER_SCHEDULE="30 2 * * *"
    INFO

    保留参数与 Jaeger 之间的关系

    为防止 Jaeger UI 查询到清理器刚刚删除的索引,请保持 JAEGER_INDEX_RETENTION_DAYS(清理器保留期)与 Jaeger 的 max_span_ageJAEGER_MAX_SPAN_AGE)一致。例如,将两者都设置为 7 天(168h)。

  10. 创建 jaeger-index-cleaner.yaml,将 index-cleaner 以 CronJob 方式部署:

    jaeger-index-cleaner.yaml
    apiVersion: batch/v1
    kind: CronJob
    metadata:
      name: ${JAEGER_INSTANCE_NAME}-es-index-cleaner
      namespace: ${JAEGER_NS}
    spec:
      schedule: "${JAEGER_CLEANER_SCHEDULE}"
      concurrencyPolicy: Forbid
      successfulJobsHistoryLimit: 3
      failedJobsHistoryLimit: 3
      jobTemplate:
        spec:
          backoffLimit: 3
          template:
            spec:
              restartPolicy: Never
              containers:
                - name: jaeger-es-index-cleaner
                  image: "${JAEGER_ES_INDEX_CLEANER_IMAGE}"
                  args:
                    - "${JAEGER_INDEX_RETENTION_DAYS}"
                    - "${OPENSEARCH_ENDPOINT}"
                  env:
                    - name: INDEX_PREFIX
                      value: "${JAEGER_ES_INDEX_PREFIX}"
                    - name: INDEX_DATE_SEPARATOR
                      value: "-"
                    - name: ES_TLS_ENABLED
                      value: "true"
                    - name: ES_TLS_SKIP_HOST_VERIFY
                      value: "true"
                    - name: ES_USERNAME
                      valueFrom:
                        secretKeyRef:
                          name: opensearch-credentials
                          key: OPENSEARCH_USER
                    - name: ES_PASSWORD
                      valueFrom:
                        secretKeyRef:
                          name: opensearch-credentials
                          key: OPENSEARCH_PASS
                  resources:
                    requests:
                      cpu: 50m
                      memory: 64Mi
                    limits:
                      cpu: 500m
                      memory: 256Mi
    1. schedule 使用标准 Cron 表达式(UTC)。请安排在非高峰时段运行;通常每天一次即可。
    2. concurrencyPolicy: Forbid 可确保在前一个清理任务仍在运行时不会启动新的 Job,从而避免并发删除对 OpenSearch 造成短暂压力。
    3. argsjaeger-es-index-cleaner 的位置参数:第一个是保留天数,第二个是 OpenSearch endpoint。这里故意未设置 ROLLOVER=true,因此会使用默认的每日索引匹配模式。
    4. INDEX_PREFIX 必须与 Jaeger 的 indices.index_prefix 完全一致(此处为 acp-<cluster>),否则 cleaner 无法找到对应索引。
    5. ES_TLS_SKIP_HOST_VERIFY=true 对应 Jaeger 侧的 insecure_skip_verify: true TLS 策略。如果你改为使用自签名 CA 加强 TLS,请将 CA 挂载到容器中,并将 ES_TLS_CA_FILE 设置为其路径。

    渲染并部署该 CronJob:

    envsubst < jaeger-index-cleaner.yaml | kubectl apply -f -
  11. 为命名空间添加标签并创建 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 实例,用于接收已进行埋点的应用发送的 trace 数据,并将其转发到 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 容器的资源请求和限制。请根据预期的 trace 吞吐量进行调整。
    3. 使 operator 能够自动为 Collector 的 metrics endpoint 创建 ServiceMonitor 资源。
    4. OTLP receiver 接受来自已进行埋点的应用通过 gRPC(端口 4317)和 HTTP(端口 4318)发送的 trace 数据。
    5. otlp/traces exporter 会将接收到的 trace 转发到 Jaeger collector service。endpoint 会根据 Jaeger 实例名称和命名空间推导得出。
    6. trace pipeline 通过 OTLP 和 Zipkin 接收数据,经由 memory_limiterbatch 处理后,同时输出到 debug exporter(用于日志)和 otlp/traces(用于转发到 Jaeger)。
  2. 使用 envsubst 渲染 manifest 并应用配置:

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

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

验证

在安装完所有组件后,通过生成示例 trace 数据来验证端到端 tracing pipeline。

  1. 以测试客户端方式部署 telemetrygen,用于生成示例 trace:

    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 service。

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

    Service 下拉列表中选择 telemetrygen service,然后单击 Find Traces,以验证生成的 trace 是否可见。

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

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

(可选)启用 Service Performance Monitoring (SPM)

Service Performance Monitoring (SPM) 会在 Jaeger UI 中显示为 Monitor 选项卡,并聚合 span 数据以生成 RED(Request、Error、Duration)指标。这样无需预先了解 service 或 operation 名称,即可识别性能问题。更多详细信息,请参见 Service Performance Monitoring (SPM)

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

前提条件

  • 已部署 Jaeger v2 和 OpenTelemetry Collector(即已完成前面的所有步骤)。
  • 集群中已可用 ACP monitoring。

操作步骤

  1. 从集群中获取 monitoring 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. 为 monitoring 凭据创建一个 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. Patch OpenTelemetry Collector,使其通过 loadbalancing exporter 将 span 转发到 Jaeger,而不是直接使用 otlp/traces exporter。通过 routing_key: service,同一 service 的所有 span 会一致地发送到同一个 Jaeger 副本,因此下一步在 Jaeger 内部运行的 SpanMetrics Connector 会对每个 service 只聚合一次。这就是当 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 自动提供的 headless 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 的文件,内容如下。此 patch 会在 Jaeger 内部运行 SpanMetrics Connector,在 Prometheus endpoint 上公开生成的 RED 指标,并添加一个兼容 PromQL 的 metrics backend,以便 Jaeger 能将这些指标提供给 Monitor 选项卡:

    jaeger-spm-patch.yaml
    spec:
      volumes:
        - name: opensearch-credentials
          secret:
            secretName: opensearch-credentials
            items:
              - key: OPENSEARCH_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: opensearch-credentials
          mountPath: /etc/jaeger/opensearch-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 会将 monitoring basic-auth 凭据挂载到 Jaeger 容器中。
    2. monitoring-credentials volumeMount 会将凭据暴露在 /etc/jaeger/monitoring-credentials/
    3. spanmetrics connector 会基于 Jaeger 接收到的 span 生成 RED 指标。由于 Collector 会将每个 service 路由到单个 Jaeger 副本,因此每个副本都能正确地聚合一组互不重叠的 service。
    4. prometheus exporter 会在端口 8889 上公开生成的指标。Jaeger 实例已经包含 prometheus: kube-prometheus 标签以及 observability.metrics.enableMetrics: true(在 jaeger.yaml 中设置),因此 operator 会自动创建一个 ServiceMonitor,使 ACP Prometheus 能够抓取此 endpoint。
    5. basicauth/monitoring extension 为 monitoring metrics endpoint 提供 basic authentication。
    6. metric_backends 部分配置了 Jaeger 用于查询 SPM 数据的兼容 PromQL 的 metrics storage。
    7. jaeger_query extension 中引用该 metrics store。
    8. 必须将 basicauth/monitoring extension 添加到 service.extensions 列表中才会生效。
    9. 现在 traces pipeline 还会输出到 spanmetrics(用于存储和生成 metrics),并且一个专用的 metrics/spanmetrics pipeline 会将生成的 metrics 转发到 prometheus exporter。
    WARNING

    volumesvolumeMountsservice.extensions 和 pipeline 的 exporters 字段都是数组。merge patch 会整体替换数组,而不是向其中追加内容。上面的 patch 文件包含了所有现有条目以及新增条目,以防止数据丢失。

  5. 应用该 patch:

    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 副本都会拥有一组互不重叠的 service,并且不会产生冲突。请牢记以下注意事项:

验证

启用 SPM 后,你可以按照上文 验证 部分所述,部署 telemetrygen 测试客户端进行验证。

生成 trace 后,前往 Jaeger UI 中的 Monitor 选项卡,查看 telemetrygen service 的聚合 RED 指标。

卸载

要从集群中移除 Alauda Distributed Tracing 组件,请参阅 卸载 Alauda Distributed Tracing