使用 Istio 分布式跟踪应用程序

点击查看目录

本文为翻译文章,点击查看原文

在微服务领域,分布式跟踪正逐渐成为调试和跟踪应用程序最重要的依赖工具。

最近的聚会和会议上,我发现很多人对分布式跟踪的工作原理很感兴趣,但同时对于分布式跟踪如何与 Istio 和Aspen Mesh等服务网格进行配合使用存在较大的困惑。特别地,我经常被问及以下问题:

  • Tracing 如何与 Istio 一起使用?在 Span 中收集和报告哪些信息?
  • 是否必须更改应用程序才能从 Istio 的分布式跟踪中受益?
  • 如果目前在应用程序中报告 Span,它将如何与 Istio 中的 Span 进行交互?

在这篇博客中,我将尝试回答这些问题。

在我们深入研究这些问题之前,建议先快速了解为什么我要写与分布式跟踪相关博客。如果您关注Aspen Mesh的博客,您会注意到我写了两篇与 tracing 相关的博客,一篇关于 ”使用 Istio 跟踪 AWS 中的服务请求“,另一篇关于”使用 Istio 跟踪 gRPC 应用程序"

我们在 Aspen Mesh 有一个非常小的工程团队,如果经常在子系统或组件上工作,您很快就会成为(或标记或分配)常驻专家。我在微服务中添加了分布式跟踪,并在 AWS 环境中将其与 Istio 集成,在此过程中发现了值得分享的各种有趣的经验。在过去的几个月里,我们一直在大量使用跟踪来了解我们的微服务,现在这种方法已经成为我们排查问题首先采用的手段。后续,我们继续回答上面提到的问题。

Tracing 如何与 Istio 一起使用?

Istio 在应用程序运行的 Pod 容器中注入 sidecar 代理(Envoy)。sidecar 代理透明地拦截(防火墙魔法)进出应用程序的所有网络流量。拦截模式下,sidecar 代理处于一个独特的位置,可以自动跟踪所有网络请求(包括 HTTP/1.1、HTTP/2.0 和 gRPC)。

让我们看看 sidecar 代理对来自客户端(外部或其他微服务)的传入 Pod 请求所做的更改。从现在开始,为了简单起见,我将假设跟踪标头采用Zipkin格式。

  • 如果传入请求没有任何跟踪头,则在请求传递到与 sidecar 同一 Pod 中的应用程序容器前,sidecar 代理将创建根 Span(其中 trace、parent 和 Span ID 具有完全相同的 Span)。
  • 如果传入的请求有跟踪信息(如正在使用 Istio Ingress 或者微服务是从另一个注入了 sidecar 代理的微服务中调用),那么 sidecar 代理将从跟踪头中提取 Span 上下文,在将请求传递到同一 Pod 中的应用程序容器之前,创建一个新的兄弟(sibling)Span(与传入头相同的 trace、parent 和 Span ID)。

在应用程序容器发出相反方向上的出站请求(外部服务或集群中的服务)时,Pod 中的 sidecar 代理在向上游服务发出请求之前执行以下操作:

  • 如果不存在跟踪头,则 sidecar 代理会创建根 Span 并将 Span 上下文作为头部注入新请求。
  • 如果存在跟踪头,则 sidecar 代理从头部中提取 Span 上下文,并基于此上下文创建子 Span。新上下文作为请求中的跟踪头传播到上游服务。

根据上面的解释,您应该注意到对于微服务调用链中的每一跳,将获得 Istio 报告的两个 Span,一个来自客户端 sidecar(span.kind设置为 client)和一个来自服务器 sidecar(span.kind设置为 server)。sidecar 创建的所有 Span 都由 sidecar 自动报告给配置的后端跟踪系统,比如 Jaeger 或 Zipkin 等。

接下来,让我们看一下 Span 中报告的信息。Span 包含以下信息:

  • x-request-id:报告为 guid:x-request-id,这对于将访问日志与 Span 相关联非常有用。
  • upstream cluster:发出请求的上游服务。如果 Span 跟踪对 Pod 的传入请求,则通常将其设置为 in.<name>。如果 Span 跟踪出站请求,则将其设置为 out.<name>
  • HTTP headers:在可用时报告以下 HTTP 头部信息:
    • +URL
    • +Method
    • +User 代理
    • +Protocol
    • +Request 大小
    • +Response 大小
    • +Response 标记
  • 每个 Span 的开始和结束时间。
  • 跟踪的元数据:这包括 trace ID、Span ID 和 Span 类型(client 或 server)。除此之外,还会报告每个 Span 的操作名称。操作名称设置为影响路由配置的虚拟服务(或 v1alpha1 中的路由规则),如果选择了默认路由,则设置为“default-route”。这对于了解哪个 Istio 路由配置对 Span 生效非常有用。

接下来让我们继续讨论第二个问题。

是否必须修改应用程序才能利用 Istio 追踪?

是的,您需要在应用程序中添加逻辑,以便将传入跟踪头部信息从传入请求传播到传出请求,这样才能从 Istio 的分布式跟踪中获得更多有价值的信息。

如果应用程序容器在传入请求的上下文中发出新的出站请求,且传入请求中未包括跟踪头,则 sidecar 代理会为出站请求创建根 Span。这意味着您将始终只看到两个微服务的路径。另一方面,如果应用程序容器确实将跟踪头部信息从传入请求传播到传出请求,则 sidecar 代理将创建如上所述的子 Span。通过创建子 Span,您可以了解跨多个微服务的依赖关系。

在应用程序中传播跟踪头有两种选择。

  1. 查找Istio 文档中提到的跟踪头,并将其从传入请求传输到传出请求。这种方法很简单,几乎适用于所有情况。但是,它有一个主要缺点,无法向 Span 添加自定义标记信息例如用户信息等。您无法创建应用程序中的事件相关的子 Span。由于是在不了解 Span 格式或上下文的情况下传播跟踪信息,因此添加特定于应用程序的信息的能力有限。
  2. 第二种方法是在应用程序中设置跟踪客户端,并使用Opentracing API将跟踪头部信息从传入请求传播到传出请求。我创建了一个跟踪示例包,它提供了一种在您的应用程序中设置jaeger-client-go的简单方法,该方法与 Istio 兼容。以下代码段可用于应用程序的主功能中:
import (
	"log"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"github.com/aspenmesh/tracing-go"
)

func setupTracing() {
	// Configure Tracing
	tOpts := &tracing.Options{
		ZipkinURL: viper.GetString("trace_zipkin_url"),
		JaegerURL: viper.GetString("trace_jaeger_url"),
		LogTraceSpans: viper.GetBool("trace_log_spans"),
	}

	if err := tOpts.Validate(); err != nil {
		log.Fatal("Invalid options for tracing: ", err)
	}

	var tracer io.Closer
	if tOpts.TracingEnabled() {
		tracer, err = tracing.Configure("myapp", tOpts)
		if err != nil {
			tracer.Close()
			log.Fatal("Failed to configure tracing: ", err)
		} else {
			defer tracer.Close()
		}
	}
}

需要注意的关键点是在tracing-go包中我将 Opentracing 全局跟踪器设置 Jaeger。这使我能够使用 Opentracing API 将跟踪头从传入请求传播到传出请求,如下所示:

import (
	"net/http"
	"golang.org/x/net/context"
	"golang.org/x/net/context/ctxhttp"
	 ot "github.com/opentracing/opentracing-go"
)

func injectTracingHeaders(incomingReq *http.Request, addr string) {
	ifSpan:= ot.SpanFromContext(incomingReq.Context());Span!= nil {
		outgoingReq, _ := http.NewRequest("GET", addr, nil)
		ot.GlobalTracer().Inject(
			span.Context(),
			ot.HTTPHeaders,
			ot.HTTPHeadersCarrier(outgoingReq.Header))
		resp, err := ctxhttp.Do(ctx, nil, outgoingReq)
		// Do something with resp
	}
}

您还可以使用 Opentracing API 来设置 Span 标记或从 Istio 添加的跟踪上下文创建子 Span,如下所示:

func SetSpanTag(incomingReq *http.Request, key string, value interface{}) {
	ifSpan:= ot.SpanFromContext(incomingReq.Context());Span!= nil {
		span.SetTag(key, value)
	}
}

除了上述好处之外,您不必直接处理跟踪信息,但跟踪器(在本例中为 Jaeger)会为您处理它。我强烈建议使用此方法,因为它在应用程序中提供了跟踪的基础,增强了跟踪功能而不会产生太多开销。

现在让我们继续讨论第三个问题。

Istio 报告的 Span 如何与应用程序创建的 Span 交互?

如果您希望应用程序报告的 Span 是 Istio 添加的跟踪上下文的子 Span,则应使用 OpenTracing API StartSpanFromContext而不是使用StartSpan。如果存在跟踪头部信息,则StartSpanFromContext从父级上下文创建子 Span,否则创建根 Span。

请注意,在上面的所有示例中,我都使用了 OpenTracing Go API,但您应该能够使用与应用程序相同语言编写的任何跟踪客户端库,只要它与 OpenTracing API 兼容即可。

编辑本页