Istio 支持通过 Envoy 代理进行分布式追踪,代理自动为其应用程序生成追踪 span,只需要应用程序转发适当的请求上下文即可。Istio 支持很多追踪系统,包括 Zipkin, Jaeger,Lightstep 和 Datadog,其中 Jaeger 目前已经成为 Istio 默认的分布式追踪工具。在我们的容器云平台中,我们采用了Istio + Jaeger的组合,为应用程序提供了低侵入性的链路追踪功能。本文将重点介绍如何通过Envoy代理和Jaeger实现应用程序的分布式追踪。

1、Envoy 分布式追踪 Jaeger 的实现原理

  Envoy 原生支持 Jaeger,追踪所需 x-b3 开头的 Header 和 x-request-id 在不同的服务之间由业务逻辑进行传递,并由 Envoy 上报给 Jaeger,最终 Jaeger 生成完整的追踪信息。
  为了将各种追踪 span 整合在一起以获得完整的追踪图,应用程序必须在传入和传出请求之间传播追踪上下文信息。特别是,Istio 依赖于应用程序传播 b3 追踪 Header 以及由 Envoy 生成的请求 ID,即应用程序服务请求时需携带这些 Header。这些 Header 包括:

  • x-request-id: 这是一个唯一的请求标识符,用于标识单个请求。它有助于追踪请求,但不涉及分布式跟踪。在Istio中,这通常是由Envoy代理生成的,以便在服务之间传递请求标识。
  • x-b3-traceid: 追踪ID,是一个在整个请求链中唯一的标识符。它用于标识一个请求在分布式系统中的路径。
  • x-b3-spanId: 跨度ID,用于标识单个操作或跨度。每个服务处理请求时都会创建一个新的SpanId。它有助于将请求的不同部分关联起来。
  • x-b3-parentspanid: 父跨度ID,指示当前操作的父操作的SpanId。这有助于构建操作之间的层次关系,以便更好地理解请求的调用链。
  • x-b3-sampled:采样标志,用于指示是否对请求进行采样,以决定是否在分布式跟踪系统中记录该请求的信息。如果采样标志为 "1",则对该请求进行采样,如果为 "0",则不采样。
  • x-b3-flags:标志位,用于标识请求的特定属性。在一些系统中,可能会使用这个标志位来传递额外的信息。

  如果请求中没有 B3 HTTP Header,Istio Sidecar 代理(Envoy) 会自动生成初始化的 Headers。

  在 Istio 中,Envoy 和 Jaeger 的关系如下:

  上图中 Front Envoy 指的是第一个接收到请求的 Envoy Sidecar,它会负责创建 Root Span 并追加到请求 Header 内,请求到达不同的服务时,Envoy Sidecar 会将追踪信息进行上报。

2、应用程序通过 Envoy 代理和 Jaeger 进行分布式追踪

2.1 查看通过 Envoy 代理的应用程序会被自动生成初始化哪些 Headers

  在第一章介绍 Envoy 分布式追踪 Jaeger 的实现原理时,我们知道 Envoy 原生支持 Jaeger,追踪所需 x-b3 开头的 Header 和 x-request-id 在不同的服务之间由业务逻辑进行传递,并由 Envoy 上报给 Jaeger,最终 Jaeger 生成完整的追踪信息。如果请求中没有 B3 HTTP Header,Istio Sidecar 代理(Envoy) 会自动生成初始化的 Headers。在这一小节,我们将看一下通过 Envoy 代理的应用程序会被自动生成初始化哪些 Headers。

(1) 构建个简单镜像能够打印 HTTP 请求信息

以下是一个示例,使用 Go 语言来创建一个简单的 HTTP 服务器,并在其中打印请求信息:

1)创建一个名为 main.go 的 Go 源代码文件,内容如下:

  1. package main
  2.  
  3. import (
  4. "fmt"
  5. "log"
  6. "net/http"
  7. )
  8.  
  9. func main() {
  10. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  11. // 打印请求信息
  12. fmt.Println("Request Method:", r.Method)
  13. fmt.Println("Request URL:", r.URL.String())
  14. fmt.Println("Request Headers:")
  15. for header, value := range r.Header {
  16. fmt.Printf(" %s: %v\n", header, value)
  17. }
  18. fmt.Println("Request Body:", r.Body)
  19.  
  20. // 返回响应
  21. w.WriteHeader(http.StatusOK)
  22. w.Write([]byte("Hello, World!"))
  23. })
  24.  
  25. log.Fatal(http.ListenAndServe(":80", nil))
  26. }

2)创建一个名为 Dockerfile 的文件,内容如下:

  1. # 使用 Golang 官方的 Golang 镜像作为基础镜像
  2. FROM golang:1.17
  3.  
  4. # 设置工作目录
  5. WORKDIR /app
  6.  
  7. # 将 main.go 文件复制到容器中的 /app 目录下
  8. COPY ./main.go .
  9.  
  10. # 编译 Go 代码
  11. RUN go build -o http_request_printer main.go
  12.  
  13. # 在容器启动时运行 Go 应用
  14. CMD ["./http_request_printer"]

3)使用以下命令构建 Docker 镜像:

  1. docker build -t http_request_printer .

4)构建完成后,可以使用以下命令运行容器:

  1. docker run -p 8080:80 http_request_printer

注意 1:如果宿主机暴露 18080 端口,不要用谷歌浏览器或者谷歌浏览器里面的 Postman 插件进行测试,因为谷歌浏览器默认会禁用 18080 端口。

5)修改镜像 tag ,将镜像推送到镜像仓库,此步骤比较简单,忽略命令。

6)通过 Postman 测试访问此容器:

7)通过容器日志查看请求信息:

可以看到除了自定义 Header 外,谷歌浏览器里面的 Postman 插件帮我们加了一些 Header 信息,但是可以很明确的看到这些 Header 都和链路追踪没有关系。

(2) 验证通过 Envoy 代理的应用程序会被自动生成初始化哪些 Headers

1)定义 http_request_printer 服务声明式配置文件

  1. # http_request_printer_deploy_svc.yaml
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5. namespace: tracing
  6. labels:
  7. version: v1
  8. app: http-request-printer
  9. name: http-request-printer-v1
  10. spec:
  11. replicas: 1
  12. selector:
  13. matchLabels:
  14. version: v1
  15. app: http-request-printer
  16. template:
  17. metadata:
  18. labels:
  19. version: v1
  20. app: http-request-printer
  21. annotations:
  22. cloudbases.io/containerSecrets: '{"container-rr19ea":"harbor"}'
  23. spec:
  24. containers:
  25. - name: container-rr19ea
  26. imagePullPolicy: IfNotPresent
  27. image: '10.20.32.201:80/library/http_request_printer'
  28. ports:
  29. - name: http-80
  30. protocol: TCP
  31. containerPort: 80
  32. servicePort: 80
  33. serviceAccount: default
  34. affinity: {}
  35. initContainers: []
  36. volumes: []
  37. imagePullSecrets:
  38. - name: harbor
  39. strategy:
  40. type: RollingUpdate
  41. rollingUpdate:
  42. maxUnavailable: 25%
  43. maxSurge: 25%
  44. ---
  45. apiVersion: v1
  46. kind: Service
  47. metadata:
  48. namespace: tracing
  49. labels:
  50. version: v1
  51. app: http-request-printer
  52. annotations:
  53. cloudbases.io/serviceType: statelessservice
  54. name: http-request-printer
  55. spec:
  56. sessionAffinity: None
  57. selector:
  58. app: http-request-printer
  59. template:
  60. metadata:
  61. labels:
  62. version: v1
  63. app: http-request-printer
  64. ports:
  65. - name: http-80
  66. protocol: TCP
  67. port: 80
  68. targetPort: 80
  69. type: NodePort

2)在 Kubernetes 集群中部署 http_request_printer 服务

  1. kubectl apply -f http_request_printer_deploy_svc.yaml

3)查看服务访问信息,并通过 Postman 访问此服务

  1. [root@master1 ~]# kubectl get svc -n=tracing
  2. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  3. http-request-printer NodePort 10.234.131.36 <none> 80:32513/TCP 74s

4)通过容器日志查看请求信息:

可以看到除了自定义 Header 外,谷歌浏览器里面的 Postman 插件帮我们加了一些 Header 信息,但是可以很明确的看到这些 Header 都和链路追踪没有关系。

5)修改应用deployment配置让注入边车

  1.    #修改Pod模板标签,添加边车注入标签 sidecar.istio.io/inject: 'true'
  2.    labels:
  3. app: http-request-printer
  4. sidecar.istio.io/inject: 'true'
  5. version: v1

6)Pod重启后,确定容器已注入边车

7)注入边车后,再通过 Postman 访问此服务

查看容器日志可以看到多了5个和链路追踪相关的 Header 数据。

由此证明,如果请求中没有 B3 HTTP Header,Istio Sidecar 代理(Envoy) 会自动生成初始化的 Headers。

8)可以看到 x-b3-sampled 值为1,代表 Jaeger 会记录该请求的信息

3、总结

  Envoy 原生支持 Jaeger,追踪所需 x-b3 开头的 Header 和 x-request-id 在不同的服务之间由业务逻辑进行传递,并由 Envoy 上报给 Jaeger,最终 Jaeger 生成完整的追踪信息。

 为了将各种追踪 span 整合在一起以获得完整的追踪图,应用程序必须在传入和传出请求之间传播追踪上下文信息。特别是,Istio 依赖于应用程序传播 b3 追踪 Header 以及由 Envoy 生成的请求 ID,即应用程序服务请求时需携带这些 Header。

  如果请求中没有 B3 HTTP Header,Istio Sidecar 代理(Envoy) 会自动生成初始化的 Headers。

参考:分布式追踪

应用程序通过 Envoy 代理和 Jaeger 进行分布式追踪(一)的更多相关文章

  1. ASP.NET Core使用Jaeger实现分布式追踪

    前言 最近我们公司的部分.NET Core的项目接入了Jaeger,也算是稍微完善了一下.NET团队的技术栈. 至于为什么选择Jaeger而不是Skywalking,这个问题我只能回答,大佬们说了算. ...

  2. 总结两种动态代理jdk代理和cglib代理

    动态代理 上篇文章讲了什么是代理模式,为什么用代理模式,从静态代理过渡到动态代理. 这里再简单总结一下 什么是代理模式,给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原 ...

  3. 通过一个工具类更深入理解动态代理和Threadlocal

    动态代理和Threadlocal 一个代理类返回指定的接口,将方法调用指定的调用处理程序的代理类的实例.返回的是一个代理类,由指定的类装载器的定义和实现指定接口指定代理实例调用处理程序最近用到一个工具 ...

  4. 爬虫--requests模块高级(代理和cookie操作)

    代理和cookie操作 一.基于requests模块的cookie操作 引言:有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时,如果使用之前requests ...

  5. java的静态代理、jdk动态代理和cglib动态代理

    Java的代理就是客户端不再直接和委托类打交道,而是通过一个中间层来访问,这个中间层就是代理.使用代理有两个好处,一是可以隐藏委托类的实现:二是可以实现客户与委托类之间的解耦,在不修改委托类代码的情况 ...

  6. JAVA高级架构师基础功:Spring中AOP的两种代理方式:动态代理和CGLIB详解

    在spring框架中使用了两种代理方式: 1.JDK自带的动态代理. 2.Spring框架自己提供的CGLIB的方式. 这两种也是Spring框架核心AOP的基础. 在详细讲解上述提到的动态代理和CG ...

  7. AOP的底层实现-CGLIB动态代理和JDK动态代理

    AOP是目前Spring框架中的核心之一,在应用中具有非常重要的作用,也是Spring其他组件的基础.它是一种面向切面编程的思想.关于AOP的基础知识,相信多数童鞋都已经了如指掌,我们就略过这部分,来 ...

  8. JDK动态代理和CGLIB的区别

    Aspect默认情况下不用实现接口,但对于目标对象,在默认情况下必须实现接口 如果没有实现接口必须引入CGLIB库 我们可以通过Advice中添加一个JoinPoint参数,这个值会由spring自动 ...

  9. JDK动态代理和CGLib动态代理简单演示

    JDK1.3之后,Java提供了动态代理的技术,允许开发者在运行期间创建接口的代理实例. 一.首先我们进行JDK动态代理的演示. 现在我们有一个简单的业务接口Saying,如下: package te ...

  10. SpringAOP-JDK 动态代理和 CGLIB 代理

    在 Spring 中 AOP 代理使用 JDK 动态代理和 CGLIB 代理来实现,默认如果目标对象是接口,则使用 JDK 动态代理,否则使用 CGLIB 来生成代理类. 1.JDK 动态代理 那么接 ...

随机推荐

  1. php获取未解码之前的原始接口请求参数

    前言 目前的几个项目,业务方基本都使用POST方式请求接口,我们本机磁盘会保留一份请求的原始参数用于请求分析和问题排查使用,一般有问题,也会基于seqid(请求唯一id)捞到日志,copy参数模拟请求 ...

  2. 2022-02-26:k8s安装swagger,yaml如何写?

    2022-02-26:k8s安装swagger,yaml如何写? 答案2022-02-26: yaml如下: apiVersion: apps/v1 kind: Deployment metadata ...

  3. 2021-12-28:给定一个二维数组matrix,matrix[i][j] = k代表: 从(i,j)位置可以随意往右跳<=k步,或者从(i,j)位置可以随意往下跳<=k步, 如果matrix[i]

    2021-12-28:给定一个二维数组matrix,matrix[i][j] = k代表: 从(i,j)位置可以随意往右跳<=k步,或者从(i,j)位置可以随意往下跳<=k步, 如果mat ...

  4. docker安装go-fastdfs

    1.docker命令安装 docker run -d --name fastdfs -p 8180:8080 sjqzhang/go-fastdfs 2.浏览器访问 http://192.168.20 ...

  5. 时间函数strftime和strptime的差别

    strftime是转换为特定格式输出, strptime是将一个时间字符串解析为时间类型对象. strftime是按照想要的格式,去转换.重点是格式! strptime不管什么格式,只要把特定的时间字 ...

  6. Redis数据结构三之压缩列表

    本文首发于公众号:Hunter后端 原文链接:Redis数据结构三之压缩列表 本篇笔记介绍压缩列表. 在 Redis 3.2 版本之前,压缩列表是列表对象.哈希对象.有序集合对象的的底层实现之一. 因 ...

  7. Redis - 二进制位数组

    简介 Redis 使用字符串对象来表示位数组,因为字符串对象使用的 SDS 数据结构是二进制安全的,所以程序可以直接使用 SDS 结构来保存位数组,并使用 SDS 结构的操作函数来处理位数组. 在 S ...

  8. # 代码随想录算法训练营Day10 栈与队列| 理论基础  232.用栈实现队列  225. 用队列实现栈

    栈与队列理论基础 队列是先进先出,栈是先进后出 关于栈的四个问题 C++中stack 是容器么? 我们使用的stack是属于哪个版本的STL? 我们使用的STL中stack是如何实现的? stack ...

  9. OWASP移动应用安全测试指南中文版

    OWASP移动应用安全测试指南(MASTG)是OWASP移动应用安全(MAS)旗舰项目的一部分,是一本涵盖移动应用安全分析过程.技术和工具的综合手册,也是一套详尽的测试案例,用于验证OWASP移动应用 ...

  10. 一篇文章带你详细了解axios的封装

    axios 封装 对请求的封装在实际项目中是十分必要的,它可以让我们统一处理 http 请求.比如做一些拦截,处理一些错误等.本篇文章将详细介绍如何封装 axios 请求,具体实现的功能如下 基本配置 ...