Z.S.K.'s Records

Istio学习(使用jaeger实现grpc-gateway全链路追踪)

一直想把istio引入到业务中, 前期调研实操中发现istio完美解决了grpc在kubernetes中的负载均衡问题, 这次学习一下opentracing在istio中的应用, 这里主要是想看grpc-gateway的调用,目前业务中用的最多, 当然tcp就更没问题.

fork了一个大神的demo, 精简了部分流程, 把在google cloud上环境的搭建去除了,同时将grpc-gateway部分的代码改了,fork的源码中把grpc-gateway单独写成一个模块提供服务, 这里改成了跟真正的服务结合在一起,少一层中转, github在这里

流程

这个demo里涉及三个服务:

​ —————————————————————-

curl == |(grpc-gateway) http ==> service-a == grpc| ==> service-c == tcp ==> mongodb

​ —————————————————————-

service-a服务中整合了grpc-gateway服务, 暴露出8088 http接口, 通过8088端口转到 grpc 50051端口调用 service-c

service-c 通过tcp调用mongodb写入数据

这里直接使用命令行curl service-a的8088端口

grpc-gateway的实现过程可参考这里

前提

需要一个kubernetes集群,部署了istio及开启了jaeger和kiali, 这部分不在这里展开. 可参考这里

部署

编译镜像

1
2
cd grpc-gateway-with-opentracing/services
bash build_images.sh

发布

1
2
3
4
5
cd grpc-gateway-with-opentracing/deployments
kubectl apply -f namespaces/middle.yaml
kubectl apply -f secrets/go-srv-demo.yaml
kubectl apply -f yaml/*
#其中, mongodb使用到的用户名及密码为forguest:VnximZWwED, 端口27017

这里为了方便, mongodb数据库没有使用持久卷, 数据库启动之后连接mongodb进行用户授权,

安装mongo shell 命令行工具, ubuntu下进行以下操作

注意: 使用的mongodb server为4.2的版本, 请安装对应版本的客户端,不然问题一大堆

1
2
3
4
5
wget -qO - https://www.mongodb.org/static/pgp/server-4.2.asc | sudo apt-key add -
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/4.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.2.list

sudo apt-get update
apt-get install -y mongodb-org-shell
1
2
use admin;
db.createUser({user: "forguest" , pwd: "VnximZWwED", roles: [ "userAdminAnyDatabase","readWriteAnyDatabase" ]})

测试

使用以下命令进行测试, 请对应替换service-a的集群ip.

1
for x in $(seq 1 100);do sleep 5;curl -H'Content-Type: application/json' 'your-service-a-svc-clusterIP:8088/api/v1/greeting';done

Jaeger效果如下:

kialiu效果如下:

Opentracing分析

在grpc-agteway-with-opentracing.go代码中, 定义了如下对象,

1
2
3
4
5
6
7
8
9
10
var (
otHeaders = []string{
"x-request-id",
"x-b3-traceid",
"x-b3-spanid",
"x-b3-parentspanid",
"x-b3-sampled",
"x-b3-flags",
"x-ot-span-context"}
)

在grpc调用时, istio会做如下的操作:

  • Inbound 流量:对于经过 Sidecar 流入应用程序的流量,如果经过 Sidecar 时 Header 中没有任何跟踪相关的信息,则会在创建一个根 Span,TraceId 就是这个 SpanId,然后再将请求传递给业务容器的服务;如果请求中包含 Trace 相关的信息,则 Sidecar 从中提取 Trace 的上下文信息并发给应用程序。
  • Outbound 流量:对于经过 Sidecar 流出的流量,如果经过 Sidecar 时 Header 中没有任何跟踪相关的信息,则会创建根 Span,并将该跟 Span 相关上下文信息放在请求头中传递给下一个调用的服务;当存在 Trace 信息时,Sidecar 从 Header 中提取 Span 相关信息,并基于这个 Span 创建子 Span,并将新的 Span 信息加在请求头中传递

总结就是: 每一次的调用, istio都会将请求中的header进行解析, 看header中是否包含上面声明的变量,如果不存在, 则创建一个root span, 如果存在, 则将spanid做为自己的parentid,如果还有调用, 则依次这样传递下去

可以来看一下jaeger上的header信息:

首先,每一次请求istio都会生成一个唯一的traceID(x-b3-traceid)及requestID(x-request-id), 这两个值在这次请求中不管经过几个服务都不会改变, 这样就能够通过这者查询出这次请求所经过的服务有哪一些,但是这两者无法确定经过的服务间的顺序是怎样的, 所以必须看x-b3-spanid及x-b3-parentspanid

从上图可以看出service-a(根请求)的spanID为traceID的后16位, 再通过这个spanID,可以看到

8932e8b49bbddf3ac78400f5fce93775child_of即孩子span

通过这种父子关系就可以很容易确定谁调用谁,再结合traceID或者RequestID(通常这两者一样)的唯一性就能够定位所有调用过的服务了.

从上面也可以看出, 要使用jaeger实现全链路监控,还是需要业务代码做集成,, 尽管istio帮我们在业务上做了很多的事情, 但目前像上面的例子需要加入header的部分istio无法实现,因为它不知道service-a会去调用哪个服务. 当然 ,以后这部分的coding会越来越少

参考文章:

转载请注明原作者: 周淑科(https://izsk.me)

 wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?