微服务应用中的分布式跟踪

更新日期:2023 年 10 月 24 日

本文档是关于设计、构建和部署微服务的四篇系列文章中的第四篇。本系列文章介绍微服务架构的各种元素。该系列介绍了微服务架构模式的优缺点及其应用方式。

  1. 微服务简介
  2. 将单体式应用重构为微服务
  3. 微服务设置中的服务间通信
  4. 微服务应用中的分布式跟踪(本文档)

本系列文章面向设计和实施迁移以将单体式应用重构为微服务应用的应用开发者和架构师。

在分布式系统中,请务必了解请求如何从一项服务流向另一项服务,以及每项服务中执行任务所需的时间。考虑您在上一个文档中部署的基于微服务的 Online Boutique 应用,将单体式应用重构为微服务。应用由多项服务组成。例如,下面的屏幕截图显示了商品详情页面,该页面从前端、推荐和广告服务中提取信息。

商品详情页。

为了呈现商品详情页面,前端服务会与推荐服务和广告服务进行通信,如下图所示:

前端服务与推荐服务、产品目录和广告服务进行通信。

图 1. 使用不同语言编写的服务。

在图 1 中,前端服务是使用 Go 编写的。以 Python 编写的推荐服务使用 gRPC 与前端服务进行通信。以 Java 编写的广告服务也使用 gRPC 与前端服务进行通信。除了 gRPC 之外,服务间通信方法也可以使用 REST HTTP。

在构建此类分布式系统时,您希望可观测性工具提供以下数据分析:

  • 请求完成的服务。
  • 请求缓慢时发生的情况。
  • 请求失败时发生错误的位置。
  • 请求的执行方式与系统的正常行为有何不同。
  • 请求执行情况与性能是否相关(某些服务调用花费的时间是否长或短)。

目标

  • 使用 kustomize 清单文件设置基础架构。
  • 将 Online Boutique 示例应用部署到 Google Kubernetes Engine (GKE)。
  • 使用 Cloud Trace 查看示例应用中的用户历程。

费用

在本文档中,您将使用 Google Cloud 的以下收费组件:

您可使用价格计算器根据您的预计使用情况来估算费用。 Google Cloud 新用户可能有资格申请免费试用

完成本文档后,您可以删除所创建的资源以避免继续计费。如需了解详情,请参阅清理

准备工作

如果您已经通过完成本系列中的前一个文档来设置项目,则微服务设置中的服务间通信可以重复使用该项目。完成以下步骤以启用其他 API 并设置环境变量。

  1. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  2. 确保您的 Google Cloud 项目已启用结算功能

  3. 在 Google Cloud 控制台中,激活 Cloud Shell。

    激活 Cloud Shell

  4. 启用适用于 Compute Engine、GKE、Cloud SQL、Artifact Analysis、Trace 和 Container Registry 的 API:

     gcloud services enable \
       compute.googleapis.com \
       sql-component.googleapis.com \
       servicenetworking.googleapis.com\
       container.googleapis.com \
       containeranalysis.googleapis.com \
       containerregistry.googleapis.com \
       sqladmin.googleapis.com
    

分布式跟踪

分布式跟踪记录会将上下文元数据附加到每个请求,并确保元数据在请求之间共享。您可以使用跟踪点来检测分布式跟踪记录。例如,您可以使用两个跟踪记录点对服务(前端、推荐和广告)进行插桩,以处理查看请求详情的客户端请求:一个用于发送请求的跟踪记录点,另一个跟踪记录接收响应。下图展示了此跟踪点插桩的工作原理:

具有两个跟踪记录点的跟踪记录点插桩。

图 2:每个服务间调用都有两个跟踪点,由一个请求-响应对组成。

为了跟踪跟踪记录点,了解在调用服务时要执行哪个请求,原始服务会在执行流中传递一个跟踪记录 ID。传递跟踪 ID 的过程称为“元数据传播”或“分布式上下文传播”。 当分布式应用的服务在执行给定请求期间相互通信时,上下文传播通过网络调用传输元数据。下图显示了元数据传播:

元数据传播会传递跟踪记录 ID。

图 3.跟踪记录元数据在服务之间传递。元数据包括一些服务的信息,例如调用哪项服务及其时间戳。

在 Online Boutique 示例中,当用户发送初始请求以提取产品详情时,跟踪记录即会开始。系统会生成一个新的跟踪记录 ID,每个后续请求都会使用包含原始元数据相关上下文的标头进行修饰。

为完成最终用户请求而调用的每个单独操作称为 span。发起服务标记每个 span 都有自己的唯一 span 和父级 span 的跟踪记录 ID。下图展示了跟踪记录的甘特图可视化内容:

个别操作被标记为 span。

图 4.父 span 包含子 span 的响应时间。

图 4 显示了跟踪树,其中前端服务调用推荐服务和广告服务。前端服务是父级 span,用于描述最终用户观察到的响应时间。子级 span 介绍了推荐服务和广告服务的调用和响应方式,包括响应时间信息。

Istio 这样的服务网格支持对服务到服务流量进行分布式跟踪,而无需任何专用的插桩。但在某些情况下,您可能需要更好地控制跟踪记录,或者可能需要跟踪未在服务网格中运行的代码。

本文档使用 OpenTelemetry 对分布式微服务应用进行插桩,以收集跟踪记录和指标。OpenTelemetry 可让您收集指标和跟踪记录,然后将其导出到后端,如 Prometheus、Cloud Monitoring、Datadog、Graphite、Zipkin 和 Jaager。

使用 OpenTelemetry 进行插桩

以下部分介绍了如何使用上下文传播来允许将多个请求的 span 附加到单个父级跟踪记录。

本示例使用 OpenTelemetry JavaScriptPythonGo 库对付款、推荐和前端服务的跟踪记录实现进行插桩。根据插桩的详细程度,跟踪数据可能会影响项目的费用(Cloud Trace 结算)。为了缓解费用问题,大多数跟踪系统都采用各种形式的采样,仅捕获特定百分比的观察到跟踪记录。在生产环境中,您的组织可能因为各种原因而确定想要采样的内容和理由。您可能希望根据管理费用、关注感兴趣的跟踪记录或过滤掉噪声来自定义采样策略。如需详细了解采样,请参阅 OpenTelemetry 采样

本文档使用 Trace 直观呈现分布式跟踪记录。您可使用 OpenTelemetry 导出器将跟踪记录发送到 Trace。

注册跟踪记录导出器

本部分介绍如何通过向微服务代码添加行来在每个服务中注册跟踪记录导出器。

对于前端服务(用 Go 编写),以下代码示例会注册导出器:

[...]
exporter, err := otlptracegrpc.New(
        ctx,
        otlptracegrpc.WithGRPCConn(svc.collectorConn))
    if err != nil {
        log.Warnf("warn: Failed to create trace exporter: %v", err)
    }
tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithSampler(sdktrace.AlwaysSample()))
    otel.SetTracerProvider(tp)

对于推荐服务(用 Python 编写),以下代码示例会注册导出器:

if os.environ["ENABLE_TRACING"] == "1":
    trace.set_tracer_provider(TracerProvider())
    otel_endpoint = os.getenv("COLLECTOR_SERVICE_ADDR", "localhost:4317")
    trace.get_tracer_provider().add_span_processor(
        BatchSpanProcessor(
            OTLPSpanExporter(
            endpoint = otel_endpoint,
            insecure = True
            )
        )
    )

对于付款服务(用 JavaScript 编写),以下代码示例会注册导出器:

provider.addSpanProcessor(new SimpleSpanProcessor(new OTLPTraceExporter({url: collectorUrl})));
provider.register();

设置上下文传播

跟踪系统需要遵循跟踪上下文规范,该规范定义了在服务之间传播跟踪上下文的格式。传播格式示例包括 Zipkin B3 格式X-Google-Cloud-Trace

OpenTelemetry 使用全局 TextMapPropagator 传播上下文。本示例使用跟踪记录上下文传播器,该传播器使用 W3C traceparent 格式。插桩库(例如 OpenTelemetry 的 HTTP 和 gRPC 库)使用全局传播器将跟踪记录上下文作为元数据添加到 HTTP 或 gRPC 请求。如需使上下文传播成功,客户端和服务器必须使用相同的传播格式。

通过 HTTP 传播上下文

前端服务会将跟踪记录上下文注入到 HTTP 请求标头中。后端服务会提取跟踪记录上下文。以下代码示例展示了如何对前端服务进行插桩以配置跟踪记录上下文:

otel.SetTextMapPropagator(
    propagation.NewCompositeTextMapPropagator(
        propagation.TraceContext{}, propagation.Baggage{}))

if os.Getenv("ENABLE_TRACING") == "1" {
    log.Info("Tracing enabled.")
    initTracing(log, ctx, svc)
} else {
    log.Info("Tracing disabled.")
}

...

var handler http.Handler = r
handler = &logHandler{log: log, next: handler}     // add logging
handler = ensureSessionID(handler)                 // add session ID
handler = otelhttp.NewHandler(handler, "frontend") // add OpenTelemetry tracing

通过 gRPC 进行上下文传播

考虑结账服务根据用户选择的商品下订单的流程。这些服务通过 gRPC 进行通信。

以下代码示例使用 gRPC 调用拦截器,该拦截器会拦截去电并注入跟踪记录上下文:

var srv *grpc.Server

// Propagate trace context always
otel.SetTextMapPropagator(
    propagation.NewCompositeTextMapPropagator(
        propagation.TraceContext{}, propagation.Baggage{}))
srv = grpc.NewServer(
    grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor()),
    grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor()),
)

收到请求后,付款或产品目录服务 (ListProducts) 会从请求标头中提取上下文,并使用父跟踪记录元数据来生成子 span。

以下部分详细介绍了如何为示例 Online Boutique 应用设置设置和审核分布式跟踪。

部署应用

如果您已有运行完本系列文档(微服务设置中的服务间通信)的正在运行的应用,则可以跳到下一部分查看跟踪记录。否则,请完成以下步骤以部署示例 Online Boutique 示例:

  1. 如需设置基础架构,请在 Cloud Shell 中克隆 GitHub 代码库:

    git clone https://github.com/GoogleCloudPlatform/microservices-demo.git
    
  2. 对于新部署,请重置环境变量:

    PROJECT_ID=PROJECT_ID
    REGION=us-central1
    GSA_NAME=microservices-sa
    GSA_EMAIL=$GSA_NAME@$PROJECT_ID.iam.gserviceaccount.com
    

    替换以下内容:

    • PROJECT_ID:项目 ID 的标识符。
  3. 可选:创建新集群或重复使用现有集群(如果存在):

    gcloud container clusters create-auto online-boutique --project=${PROJECT_ID}
      --region=${REGION}
    
  4. 创建 Google 服务账号:

    gcloud iam service-accounts create $GSA_NAME \
      --project=$PROJECT_ID
    
  5. 启用 API:

    gcloud services enable \
    monitoring.googleapis.com \
    cloudtrace.googleapis.com \
    cloudprofiler.googleapis.com \
      --project ${PROJECT_ID}
    
  6. 向 GSA 授予 Cloud Tracing 所需的角色:

    gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role roles/cloudtrace.agent
    
    gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role roles/monitoring.metricWriter
    
    gcloud projects add-iam-policy-binding ${PROJECT_ID} \
    --member "serviceAccount:${GSA_NAME}@${PROJECT_ID}.iam.gserviceaccount.com" \
    --role roles/cloudprofiler.agent
    
    gcloud iam service-accounts add-iam-policy-binding ${GSA_EMAIL} \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:${PROJECT_ID}.svc.id.goog[default/default]"
    
  7. 为您的 Kubernetes 服务账号添加注释(对于默认命名空间为 default/default),以使用 Google IAM 服务账号:

    kubectl annotate serviceaccount default \
        iam.gke.io/gcp-service-account=${GSA_EMAIL}
    
  8. 启用 Cloud Operations for GKE 配置;这会启用跟踪:

    cd ~/microservices-demo/kustomize && \
    kustomize edit add component components/google-cloud-operations
    
  9. 这会更新 kustomize/kustomization.yaml 文件,该文件可能类似于以下内容:

    apiVersion: kustomize.config.k8s.io/v1beta1
    kind: Kustomization
    resources:
    - base
    components:
    - components/google-cloud-operations
    [...]
    
  10. 部署微服务:

    kubectl apply -k .
    
  11. 检查部署的状态:

    kubectl rollout status deployment/frontend
    kubectl rollout status deployment/paymentservice
    kubectl rollout status deployment/recommendationservice
    kubectl rollout status deployment/adservice
    

    每个命令的输出如下所示:

    Waiting for deployment "" rollout to finish: 0 of 1 updated replicas are available...
    deployment "" successfully rolled out
    
  12. 获取已部署应用的 IP 地址:

    kubectl get service frontend-external | awk '{print $4}'
    

    等待负载均衡器 IP 地址发布。如需退出命令,请按 Ctrl+C。请记下负载均衡器 IP 地址,然后通过网址 http://IP_ADDRESS 访问应用。负载均衡器可能需要一段时间才能恢复正常并开始传输流量。

使用 Cloud Trace 查看跟踪记录

Online Boutique 应用中的用户购买流程具有以下流程:

  • 用户在着陆页上看到商品清单。
  • 用户只需点击购买即可进行购买。
  • 用户会被重定向到产品详情页面,并将商品添加到购物车中。
  • 用户会被重定向到结算页面,他们可以在那里付款以完成订单。

考虑这样一个场景,您需要排查在加载商品详情页面时响应时间较长的问题。如前所述,商品详情页面由多个微服务组成。如需确定出现长时间延迟的位置和原因,您可以查看分布式跟踪图表,以了解整个请求在不同服务中的性能。

如需查看分布式跟踪图表,请执行以下操作:

  1. 访问该应用,然后点击任意产品。系统会显示产品详情页面。
  2. 在 Google Cloud 控制台中,转到跟踪记录列表页面并查看时间轴。
  3. 如需查看分布式跟踪记录结果,请在 URI 列中点击前端
  4. 跟踪记录瀑布视图显示与 URI 关联的 span:

    跟踪记录瀑布视图显示 span。

    在前面的屏幕截图中,商品的跟踪记录包含以下 span:

    • 前端 span 可捕获客户端在加载商品详情页面时观察到的端到端延迟时间(150.349 毫秒)。
    • 推荐服务 span 可捕获后端调用在提取与商品相关的推荐时的延迟时间(4.246 毫秒)。
    • 广告服务 span 可捕获后端调用在提取与商品页面相关的广告时的延迟时间(4.511 毫秒)。

为了解决高响应时间问题,您可以查看分析洞见,其中包括当服务的依赖项未满足其服务等级目标 (SLO) 时任何异常请求的延迟时间分布图。您还可以使用 Cloud Trace 从采样数据中获取性能数据分析和创建分析报告

问题排查

如果应用性能管理中的跟踪记录未显示,请在 Logs Explorer 中检查是否存在权限遭拒错误。当服务账号没有导出跟踪记录的访问权限时,会发生权限遭拒。查看授予 Cloud Trace 所需的角色的步骤,并确保使用正确的命名空间为服务账号添加注解。之后,重启 opentelemetrycollector

```
kubectl rollout restart deployment opentelemetrycollector
```

清理

为避免因本教程中使用的资源导致您的 Google Cloud 账号产生费用,请删除包含这些资源的项目,或者保留项目但删除各个资源。

删除项目

  1. 在 Google Cloud 控制台中,进入管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

删除资源

如果您希望保留本文档中使用的 Google Cloud 项目,请删除各个资源:

  • 在 Cloud Shell 中,删除资源:

    gcloud container clusters delete online-boutique --project=${PROJECT_ID} --region=${REGION}
    

后续步骤