应用程序通过 Envoy 代理和 Jaeger 进行分布式追踪 —— Ingress Controller + Http服务 + Grpc服务(三)

1、概述

  在《应用程序通过 Envoy 代理和 Jaeger 进行分布式追踪(一)》这篇博文中,我们详细介绍了单个应用程序通过 Envoy 和 Jaeger 实现链路追踪的过程。通过这个示例我们知道,Istio 支持通过 Envoy 代理进行分布式追踪,代理会自动为其应用程序生成追踪 span,只需要应用程序转发适当的请求上下文即可。特别是,Istio 依赖于应用程序传播 b3 追踪 Header 以及由 Envoy 生成的请求 ID,即应用程序服务请求时需携带这些 Header。如果请求中没有 B3 HTTP Header,Istio Sidecar 代理(Envoy) 会自动生成初始化的 Headers。

  在《Nginx Ingress Contoller 通过 Envoy 代理和 Jaeger 进行分布式追踪(二)》这篇博文中,进一步扩展链路追踪范围,演示了如何将 Nginx Ingress Controller 与之前提到的应用程序一起使用,从而实现更为复杂的分布式链路追踪。

  在本文将继续扩展链路追踪范围,演示 Nginx Ingress Controller(http协议)——》前端服务(http协议)——》后端服务(http协议)——》告警客户端后端服务 (grpc协议)——》告警服务端后端服务的分布式链路追踪。

  其中 Nginx Ingress Controller 服务和前端服务默认会自动转发请求的上下文信息(Header),后端服务通过少量的代码侵入将原请求的上下文信息(Header)转发给告警客户端后端服务,告警客户端后端服务通过少量的代码侵入将原请求的上下文信息(Grpc Metadata)转发给告警服务端后端服务。

2、示例演示

2.1 环境准备

本文主要目的是讲解分布式链路追踪,因此不再讲解各组件的部署,这些组件部署时都需要注入 Envoy 边车,注入边车后,各组件通过 Envoy 代理和 Jaeger 进行分布式追踪。 

2.2 Nginx Ingress Controller(http协议)——》前端服务

Nginx Ingress Controller 的部署本文略,这里只展示下 Nginx Ingress Controller 是如何将服务代理到前端服务的。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: xxxx
    nginx.ingress.kubernetes.io/service-upstream: "true"
    nginx.ingress.kubernetes.io/upstream-vhost: dashboard.xxxx.svc.cluster.local
  name: dashboard-tracing
spec:
  rules:
  - host: dashboard-tracing.10.20.32.203.nip.io
    http:
      paths:
      - backend:
          service:
            name: dashboard
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific

2.3 前端服务(http协议)——》后端服务

前端服务默认会自动转发请求的上下文信息(Header)信息到后端服务,所以前端服务无需侵入,其他前端相关内容本文不再赘余,我们只需要知道前端服务会自动转发请求的上下文信息即可。 

2.4 后端服务(http协议)——》告警客户端后端服务 

后端服务需要通过少量的代码侵入将原请求的上下文信息(Header)信息转发给告警客户端后端服务,下面通过侵入源码来分析下是如何将原请求的上下文信息转发给告警客户端后端服务的。

主要是使用 k8s.io/apimachinery 库的 proxy 包,通过 proxy 包的 ServeHTTP 方法作为客户端调用告警客户端后端服务。调用 ServeHTTP 方法时后端服务会将 request 对象作为参数(request对象除了组织告警客户端 Host 信息外,还将前端服务传递过来的 Header 信息放到了request 对象中,此块代码因隐私问题不再粘贴),这样在 ServeHTTP 方法逻辑里面就能获取到前端服务传递过来的上下文信息(Header)了。

......
httpProxy := proxy.NewUpgradeAwareHandler(u, http.DefaultTransport, false, false, &errorResponder{})
httpProxy.ServeHTTP(response, request.Request)

下面看下 ServeHTTP 方法逻辑,可以看到通过调用 utilnet.CloneHeader 方法将前端服务传递过来的上下文信息(Header)传递给告警客户端后端服务。

// 方法包路径: k8s.io/apimachinery@v0.21.7/pkg/util/proxy/upgradeaware.go
// ServeHTTP handles the proxy request
func (h *UpgradeAwareHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	.......
	// WithContext creates a shallow clone of the request with the same context.
	newReq := req.WithContext(req.Context())
    // 主要看这里,新的请求会克隆老请求对象里面的 Header 头信息
	newReq.Header = utilnet.CloneHeader(req.Header)
	if !h.UseRequestLocation {
		newReq.URL = &loc
	}
	......
	proxy.ServeHTTP(w, newReq)
}

2.5 告警客户端后端服务(grpc协议)——》告警服务端后端服务

在2.2-2.4中,都是 http 之间的链路追踪,都是通过 header 传递链路追踪相关的上下文信息,2.5告警客户端后端服务是通过 grpc 协议访问告警服务端后端服务的,但是在 grpc 中怎么传递呢?
grpc 底层采用 http2 协议也是支持传递数据的,采用的是 Metadata,Metadata 对于 gRPC 本身来说透明, 它使得 client 和 server 能为对方提供本次调用的信息。
就像一次 http 请求的 RequestHeader 和 ResponseHeader,http header 的生命周期是一次 http 请求, Metadata 的生命周期则是一次 RPC 调用。

下面来看下告警客户端后端服务是如何将从后端服务转发的上下文信息通过 Metadata 传递给告警服务端后端服务的。

下面以修改查询告警消息列表方法逻辑为例,演示警客户端后端服务如何将从后端服务转发的上下文信息通过 Metadata 传递给告警服务端后端服务,其他方法修改方式和此方法一致。

......
// 传播追踪上下文信息,将后端服务传递过来的上下文信息放置到metadata对象中,通过context传递给告警服务端后端服务
// type MD map[string][]string
httpHeader := metadata.MD(utilnet.CloneHeader(request.Request.Header))
ctx = metadata.NewOutgoingContext(ctx, httpHeader)
......

2.6 客户端在前端服务访问查询告警消息列表功能,然后在 Jaeger UI 查看上报链路信息

客户端在前端服务访问查询告警消息列表功能时,告警客户端后端服务作为 grpc 客户端调用了两次告警服务端后端服务的接口,总共11个 span,envoy 上报链路信息如下:

(1)Nginx Ingress Controller 入站流量劫持 ——》nginx ——》(2)Nginx Ingress Controller 出站流量劫持 ——》(3)前端服务入站劫持 ——》前端服务 ——》(4)前端服务出站劫持 ——》(5)后端服务入站劫持 ——》 后端服务 ——》(6)后端服务出站劫持 ——》(7)告警客户端后端服务入站劫持 ——》告警客户端后端服务 ——》(8)告警客户端后端服务出站劫持 ——》(9)告警服务端后端服务入站劫持 ——》 告警服务端后端服务消息列表方法(回包)——》 告警客户端后端服务 ——》 (10)告警客户端后端服务出站劫持 ——》(11)告警服务端后端服务入站劫持 ——》 告警服务端后端服务消息历史方法(回包)

 注意 1:如果访问容器的入站流量不是inbound、或者出站流量不是outbound,请检查对应服务的服务协议是否正确,尤其出站流量是 PassthroughCluster。

这时候可以通过 istioctl pc listener <pod-name>.<pod-namespace> --port 端口命令排查是否没有进行路由匹配。例如以下示例能成功匹配下面路由的话出站流量就为 outbound。

3、总结

  通过《应用程序通过 Envoy 代理和 Jaeger 进行分布式追踪(一)》、《Nginx Ingress Contoller 通过 Envoy 代理和 Jaeger 进行分布式追踪(二)》加上本篇三篇博文,详细解决了应用程序如何通过 Envoy 代理和 Jaeger 进行分布式追踪,最主要的还是这一点:为了将各种追踪 span 整合在一起以获得完整的追踪图,应用程序(不管是http协议、grpc协议、或者是其他Istio支持的能上报链路追踪的服务协议)必须在传入和传出请求之间传播追踪上下文信息

posted @ 2023-09-01 15:53  人艰不拆_zmc  阅读(259)  评论(0编辑  收藏  举报