go 链路追踪_调用链 OpenTracing + Jaeger + 阿里云链路追踪

63177aea7dfec332d60e5ab45997c270.png

OpenTracing

从分布式系统架构普及之后,各个公司+社区出了很多监控调用链的项目,因为标准都不相同,最后 OpenTracing 这种规范一统天下。https://github.com/opentracing 安装opentracing的python包,pip install opentracing, 可以看到源码,里面统一定义了概念和流程,并且有一些接口需要被具体实现才能用。

核心原理是定义了 Span 和 Trace。Span容纳单次请求的信息并上报,Trace可以把多个Span连成一个链路。如果要追踪A服务调用B服务的调用链,要把A服务的信息放在请求B服务的header里

1325b5f8b0dd64fac221a1ae88f0645a.png

Jaeger

Jaeger【读作 耶格儿】是Uber出的实现了OpenTracing规范的框架。同时同样流行的还有Zipkin。我目前主要只在看Jaeger。

Jaeger是一套含有信息上报,信息展示的框架,一般需要配合存储比如ES。如果想本地起一下试一下全套Jaeger功能,可以使用docker pull jaeger的all-in-one镜像 - https://www.jaegertracing.io/docs/1.18/getting-started/

docker run -d --name jaeger 
  -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 
  -p 5775:5775/udp 
  -p 6831:6831/udp 
  -p 6832:6832/udp 
  -p 5778:5778 
  -p 16686:16686 
  -p 14268:14268 
  -p 14250:14250 
  -p 9411:9411 
  jaegertracing/all-in-one:1.18

核心的端口:6831是UDP上报jaeger-agent端口, 16686是Jaeger UI 和 API 端口

2b499503a38b16f4d2933710bf143e8a.png

Jaeger的架构

https://www.jaegertracing.io/docs/1.18/architecture/

9df9053d887f45f03746800f92570778.png
  1. Jaeger Client 和用户应用集成在一起,主要功能就是收集网络请求并生成span,trace等符合OpenTracing要求的对象
  2. Jaeger Client 通过 UDP 把span信息上报给 Jaeger Agent 。 这一步传说也可以跳过,传说 Jaeger Client可以直接上报 Jaeger Collector
  3. Jaeger Agent 要和用户应用部署在一起。 比如Jaeger-agent可以起在应用所在服务器的localhost:6831,并配置好要上报的Collector的信息
docker run jaegertracing/jaeger-agent /go/bin/agent-linux --reporter.grpc.host-port=xxx
  1. Jaeger Collector 接收agent上报的信息,并存入Kafka,ES等
  2. Jaeger UI 可视化调用链

Jaeger-Client Python 支持

Jaeger官方提供了各个语言的支持包 https://github.com/jaegertracing, python的代码集成jaeger使用 pip install jaeger-client。查看源码发现是基于OpenTracing又实现了很多具体的类方法。

使用Jaeger的话,在request header里使用的key名默认是 "uber-trace-id"(也可以通过jaeger config里的trace_id_header修改),值为"trace_id: span_id: parent_id: flags",其中所有值16进制编码后传输。 举例 {'uber-trace-id': '6997ed0a6a74f050:bf49be2de63d86e7:e02975aab05fd358:1'}

5349784b96e7233d1ce9336dbc569ff3.png

8e994b398ee1f284acfebe644b77c75b.png

阿里云 链路追踪 Tracing Analysis

通过Jaeger上报Python应用数据 文档 https://help.aliyun.com/document_detail/90506.html?

,实际这个文档只写了非常简洁的核心,简洁到关于Span的部分也就算个示意吧 = =

阿里云的 Tracing Analysis 代替了 Jaeger Collector 的部分,有UI展示(代替Jaeger UI)并且提供了存储,省去了用户自己配置ES等存储的成本和部署 Jaeger Collector的成本。

本地起 jaeger-agent 服务,连接阿里云上报:

docker run jaegertracing/jaeger-agent /go/bin/agent-linux --reporter.grpc.host-port=xxx:xxxx --jaeger.tags=Authentication={xxx}

接入信息endpoint可以在阿里云里看到

ca22bf9efa1be2603bc61ca783366128.png

集成 Django 具体代码实现

在调研了一堆开源中间件之后,发现真的就用官方的就够用了,规范,好用 https://github.com/opentracing-contrib/python-django

不过这个中间件最大的问题,就是默认只能上报所有Django View(including function view)的request。而我们微服务间的调用,比如A服务调用B服务,往往使用了 requests 包 (我们封装了一层sparrowcloud,变request_client)。这种使用 requests 包直接发起的请求,是需要明白Jaeger传递信息调用的方式,自己写代码传递的。而接收方,比如B服务,因为已经是Django View了,中间件里提供了解析 parent span,所以B服务的代码不用多修改。

be26b176f4d70b8eeef0781f336f5545.png

使用中间件的方式

A B 服务都需要配置 settings.py

import django_opentracing
import opentracing
from jaeger_client import Config


MIDDLEWARE = (
    'django_opentracing.OpenTracingMiddleware',
    '...'
)


# config这里除了 service_name 要配置,其他的不用配置都是用的默认值,具体有什么默认值可以看源码
config = Config(
    config={ # usually read from some yaml config
        'sampler': {
            'type': 'const',
            'param': 1,
        },
        # 'local_agent': {
        #     'reporting_host': os.environ.get('JAEGER_REPORTING_HOST', 'localhost'),
        #     'reporting_port': os.environ.get('JAEGER_REPORTING_PORT', '6831'),
        # },
        'logging': True,
        # 'enabled': True,
    },
    service_name=os.environ.get('JAEGER_SERVICE_NAME', 'test-sparrow-promotion'),
    validate=True,
)


# generate a Jaeger tracer
tracer = config.initialize_tracer()


# trace all views
OPENTRACING_TRACE_ALL = True
# attributes need to be traced, e.g. ['path', 'method']
OPENTRACING_TRACED_ATTRIBUTES = ['META']
# will use Jaeger tracer
OPENTRACING_TRACING = django_opentracing.DjangoTracing(tracer)

requests包调用的方式

A 服务调用B的地方需要单独写代码,以某个测试接口为例 views.py

from django.conf import settings
from opentracing.propagation import Format
import django_opentracing
import opentracing
import requests


@api_view(('POST', ))
@permission_classes((AllowAny, ))
def test_jaeger_one_price(request, *args, **kwargs):
    '''测试服务间调用'''
    subject_id = 1
    page = 1
    api_path = '/api/sparrow_products_i/subject/subject_product/?subject_id={0}&page={1}&page_size=500'
    url = 'http://127.0.0.1:8002' + api_path.format(subject_id,page)
    # 拿到当前生效的span
    tracer = opentracing.global_tracer()
    span = tracer.active_span
    # inject span 进 requests header
    headers = {}
    tracer.inject(span, Format.HTTP_HEADERS, headers)
    # inject之后,实际 headers = {'uber-trace-id': '6997ed0a6a74f050:bf49be2de63d86e7:e02975aab05fd358:1'}
    res = requests.get(url, headers=headers)
    return Response(res.json())

代码生效后,A调用B并上报阿里云,实际效果:

3a87cb885687a8fe528c8e4ef5f11fca.png

09611a47653da5ae24be7b882f2ee134.png

d402a20a4755b5844fcf954b4ea00a97.png

NOTE: span的其他信息,比如tags,不需要传递,所以也不需要特别设置

afd3f7aab7e911c9570a1dd8a2c97768.png

进一步源码研究

到底在传递什么信息

在源码中加断点,查看A服务调用B服务,Jaeger传递了哪些信息。

e7d88bf46336f4f9644cb6c9d92d6648.png

a4f4e0c10102dd3e2bf3582f54fa6a2b.png

可以看到,A服务中原始的span_id和trace_id就是随机数字生成的,然后hex之后冒号分隔,放header里。 调用B服务接口后,B看到来的request header了有span_id和trace_id,就对应到自己的parent_id和trace_id,然后生成自己的span_id,继续传。以此AB共用一个trace_id,而A的span_id就是B的parent_id。之后Jaeger根据这些信息串成调用链。

核心函数

  1. inject 把span等信息inject到header(carrier)里

在 jaeger_client 包里 codecs.py 中找到 inject 方法,可以看到原理就是把 span中的 trace_id, span_id, parent_id, flags, baggage 放入carrier(header)中

2. extract 从carrier(header)里解析span等信息

在 jaeger_client 包里 codecs.py 找到 extract 方法,可以看到就是把carrier(header)里的信息解析出来并生成 SpanContext

client数据怎么上报agent

在第一次new tracer的时候,tracer = config.initialize_tracer(),会调用 config.py中的new_tracer(),一系列代码后创建了一个LocalAgentSender实例和tornado.ioloop

ad7b4e876b89bab5c058cfe6893b624e.png

c0fa83676cefad7340d07d7301b54d54.png

bccb749c9fee90a608cffbd8b9393254.png

传输给agent的原理是一直buffer在内存里,直到flush()被调用然后发送给agent(NOTE: LocalAgentSender derives from TBufferedTransport. This will buffer up all written data until flush() is called. Flush gets called at the end of the batch span submission call.)

agent怎么上报collector

client怎么直接上报collector

jaeger python 目前看还不支持通过http方式直接上报collector,参考 https://github.com/jaegertracing/jaeger-client-python/issues/98 相关issue显示要在 jaeger-client==5.0.0 以后才有可能上这个功能(尽管 go和java现在已经支持http了。。。)

Kubernetes 部署

Jaeger Agent

jaeger-agent 可以部署成sidecar的形式,也可以部署成daemonset的形式

https://medium.com/@masroor.hasan/tracing-infrastructure-with-jaeger-on-kubernetes-6800132a677(此文章写的非常清晰)

官方文档 https://www.jaegertracing.io/docs/1.16/operator/#installing-the-agent-as-daemonset

项目(已转移至Jaeger Operator)https://github.com/jaegertracing/jaeger-kubernetes中提到的样例:https://raw.githubusercontent.com/jaegertracing/jaeger-kubernetes/master/jaeger-production-template.yml

我们选择用daemonset的部署方式。编排文件如下

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: jaeger-agent
  namespace: default
  labels:
    app: jaeger
    jaeger-infra: agent-daemonset
spec:
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: jaeger
      jaeger-infra: agent-instance
  template:
    metadata:
      labels:
        app: jaeger
        jaeger-infra: agent-instance
    spec:
      containers:
        - name: agent-instance
          image: 'jaegertracing/jaeger-agent:1.16.0'
          imagePullPolicy: IfNotPresent
          args:
            - '--reporter.grpc.host-port=xxx:xxxx'
            - '--jaeger.tags=Authentication=xxx'
          ports:
            - containerPort: 5775
              hostPort: 5775
              protocol: UDP
            - containerPort: 6831
              hostPort: 6831
              protocol: UDP
            - containerPort: 6832
              hostPort: 6832
              protocol: UDP
            - containerPort: 5778
              hostPort: 5778
              protocol: TCP
          resources:
            limits:
              cpu: 200m
              memory: 200M
            requests:
              cpu: 200m
              memory: 200M
          terminationMessagePath: /dev/termination-log
          terminationMessagePolicy: File
      dnsPolicy: ClusterFirstWithHostNet
      hostNetwork: true
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
  updateStrategy:
    type: OnDelete

可以看到在阿里云K8S中守护进程集启动成功

dd5d445ae827293554a5b6355638922d.png

Deployments

在需要使用jaeger的应用部署中增加环境变量。其中 JAEGER_AGENT_HOST必填,需要配置成变量引用status.hostIP。JAEGER_AGENT_PORT 可以不配置,默认就是6831。JAEGER_SERVICE_NAME看代码可知,jaeger创建tracer传入config的时候必须有service_name,但是如果settings里已经写了,此处可以不覆盖。

- name: JAEGER_SERVICE_NAME
  value: test-sparrow-promotion
- name: JAEGER_AGENT_HOST
  valueFrom:
    fieldRef:
      apiVersion: v1
      fieldPath: status.hostIP
- name: JAEGER_AGENT_PORT
  value: '6831' 

01a70212f190ed493c0a0590b3f749ec.png

57a3a0756cacc0b0169acd3c46fb5001.png

集群部署完毕。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值