在Kubernetes上使用Jaeger的分布式追踪基础设施

[复制链接]
发表于 2023-3-3 20:40:38 | 显示全部楼层 |阅读模式
在Kubernetes上使用Jaeger的分布式追踪基础设施

转载请注明来源:https://janrs.com/2023/03/在kubernetes上使用jaeger的分布式追踪基础设施/
作为分布式系统(或任何系统)的一个组成部分,监测基础设施的重要性怎么强调都不过分。监控监控不仅要跟踪二进制的 "上升 "和 "下降 "模式,还要参与到复杂的系统行为中。监测基础设施的设置可以让人们深入了解性能、系统健康和长期的行为模式。
这篇文章介绍了监控监控基础设施的一个方面--分布式跟踪。
微服务架构中的可观察性

Kubernetes已经成为微服务基础设施和部署的事实上的协调器。这个生态系统非常丰富,是开源社区中发展最快的系统之一。带有Prometheus、ElasticSearch、Grafana、Envoy/Consul、Jaeger/Zipkin的监控监控基础设施构成了一个坚实的基础,以实现整个堆栈的指标、日志日志、仪表盘、服务发现和分布式跟踪。
分布式追踪

分布式跟踪能够捕获请求,并建立一个从用户请求到数百个服务之间互动的整个调用链的视图。它还能对应用程序的延迟(每个请求花了多长时间)进行检测,跟踪网络调用的生命周期(HTTP、RPC等),并通过获得瓶颈的可见性来确定性能问题。
下面的章节将介绍在Kubernetes设置中使用Jaeger对gRPC服务进行分布式跟踪。Jaeger Github Org有专门的Repo,用于Kubernetes中Jaeger的各种部署配置。这些都是很好的例子,我将尝试分解每个Jaeger组件和它的Kubernetes部署。
Jaeger组件

Jaeger是一个开源的分布式跟踪系统,实现了OpenTracing规范。Jaeger包括存储、可视化和过滤跟踪的组件。
架构图


Jaeger客户端

应用程序跟踪仪表从Jaeger客户端开始。下面的例子使用Jaeger Go库从环境变量初始化追踪器配置,并启用客户端指标。
  1. package tracer
  2. import (
  3.     "io"
  4.    
  5.     "github.com/uber/jaeger-client-go/config"
  6.     jprom "github.com/uber/jaeger-lib/metrics/prometheus"
  7. )
  8. func NewTracer() (opentracing.Tracer, io.Closer, error) {
  9.     // load config from environment variables
  10.     cfg, _ := jaegercfg.FromEnv()
  11.        
  12.         // 博客原来:janrs.com
  13.     // create tracer from config
  14.     return cfg.NewTracer(
  15.         config.Metrics(jprom.New()),
  16.     )
  17. }
复制代码
Go客户端使通过环境变量初始化Jaeger配置变得简单。一些需要设置的重要环境变量包括JAEGER_SERVICE_NAME、JAEGER_AGENT_HOST和JAEGER_AGENT_PORT。Jaeger Go客户端支持的环境变量的完整列表列在这里
为了给你的gRPC微服务添加追踪功能,我们将使用gRPC中间件来启用gRPC服务器和客户端的追踪功能。 grpc-ecosystem/go-grpc-middleware有一个很棒的拦截器集合,包括支持OpenTracing提供者的服务器端和客户端的拦截器。
grpc_opentracing包暴露了opentracing拦截器,可以用任何opentracing.Tracer实现来初始化。在这里,我们用连锁的单项和流拦截器初始化了一个gRPC服务器。启用它将创建一个根serverSpan,对于每个服务器端的gRPC请求,追踪器将为服务中定义的每个RPC调用附加一个Span。
  1. package grpc_server
  2. import (
  3.         "github.com/opentracing/opentracing-go"
  4.         "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
  5.         "github.com/grpc-ecosystem/go-grpc-middleware"
  6.         "google.golang.org/grpc"
  7.   
  8.           "github.com/masroorhasan/myapp/tracer"               
  9. )
  10. func NewServer() (*grpc.Server, error) {
  11.         // initialize tracer
  12.         tracer, closer, err := tracer.NewTracer()
  13.         defer closer.Close()
  14.         if err != nil {
  15.                 return &grpc.Server{}, err
  16.         }
  17.         opentracing.SetGlobalTracer(tracer)
  18.         // initialize grpc server with chained interceptors # janrs.com
  19.         s := grpc.NewServer(
  20.                 grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
  21.                         // add opentracing stream interceptor to chain
  22.                         grpc_opentracing.StreamServerInterceptor(grpc_opentracing.WithTracer(tracer)),
  23.                   )),
  24.                   grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
  25.                         // add opentracing unary interceptor to chain
  26.                         grpc_opentracing.UnaryServerInterceptor(grpc_opentracing.WithTracer(tracer)),
  27.                 )),
  28.         )
  29.         return s, nil
  30. }
复制代码
为了实现对gRPC服务的上游和下游请求的追踪,gRPC客户端也必须用客户端开放追踪拦截器进行初始化,如下例所示。
  1. package grpc_client
  2. import (
  3.     "github.com/opentracing/opentracing-go"
  4.     "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
  5.     "github.com/grpc-ecosystem/go-grpc-middleware"
  6.     "google.golang.org/grpc"
  7.    
  8.     "github.com/masroorhasan/myapp/tracer"
  9. )
  10. func NewClientConn(address string) (*grpc.ClientConn, error) {
  11.     // initialize tracer #博文来源:janrs.com
  12.     tracer, closer, err := tracer.NewTracer()
  13.     defer closer.Close()
  14.     if err != nil {
  15.         return &grpc.ClientConn{}, err
  16.     }
  17.     // initialize client with tracing interceptor [#博文来源:janrs.com#] using grpc client side chaining
  18.     return grpc.Dial(
  19.         address,
  20.         grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient(
  21.             grpc_opentracing.StreamClientInterceptor(grpc_opentracing.WithTracer(tracer)),
  22.         )),
  23.         grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(
  24.             grpc_opentracing.UnaryClientInterceptor(grpc_opentracing.WithTracer(tracer)),
  25.         )),
  26.      )
  27. }
复制代码
由gRPC中间件创建的父跨度被注入到go上下文中,从而实现强大的跟踪支持。opentracing go客户端可以用来将子跨度附加到父跨度上,以实现更精细的追踪,以及控制每个跨度的寿命,为追踪添加自定义标签等。
Jaeger代理

Jaeger代理是一个守护进程,它通过UDP接收来自Jaeger客户端的跨度,并将它们分批转发给收集器。该代理作为一个缓冲器,从客户那里抽象出批处理和路由。
尽管代理是作为一个守护程序建立的,但在Kubernetes设置中,代理可以被配置为在应用Pod中作为一个sidecar容器运行,或作为一个独立的DaemonSet。
下文讨论了每种部署策略的优点和缺点。
Jaeger SideCar 代理

Jaeger Sidecar 代理是一个容器,与你的应用容器放在同一个舱中。表示为Jaeger服务的应用程序myapp将通过localhost向代理发送Jaeger跨度到6381端口。[#博文来源:janrs.com#]如前所述,这些配置是通过客户端的环境变量JAEGER_SERVICE_NAME、JAEGER_AGENT_HOST和JAEGER_AGENT_PORT设置的。
  1. apiVersion: apps/v1
  2. kind: Deployment
  3. metadata:
  4.   name: myapp-deployment
  5.   namespace: default
  6.   labels:
  7.     app: myapp
  8. spec:
  9.   replicas: 3
  10.   selector:
  11.     matchLabels:
  12.       app: myapp
  13.   template:
  14.     metadata:
  15.       labels:
  16.         app: myapp
  17.     spec:
  18.       containers:
  19.       - name: myapp
  20.         image: masroorhasan/myapp
  21.         ports:
  22.         - containerPort: 80
  23.         env:
  24.         - name: JAEGER_SERVICE_NAME
  25.           value: myapp
  26.         - name: JAEGER_AGENT_HOST
  27.           value: localhost  # default
  28.         - name: JAEGER_AGENT_PORT
  29.           value: "6831"
  30.         resources:
  31.           limits:
  32.             memory: 500M
  33.             cpu: 250m
  34.           requests:
  35.             memory: 500M
  36.             cpu: 250m
  37.       # sidecar agent
  38.       - name: jaeger-agent
  39.         image: jaegertracing/jaeger-agent:1.6.0
  40.         ports:
  41.         - containerPort: 5775
  42.           protocol: UDP
  43.         - containerPort: 5778
  44.           protocol: TCP
  45.         - containerPort: 6831
  46.           protocol: UDP
  47.         - containerPort: 6832
  48.           protocol: UDP
  49.         command:
  50.           - "/go/bin/agent-linux"
  51.           - "--collector.host-port=jaeger-collector.monitoring:14267"
  52.         resources:
  53.           limits:
  54.             memory: 50M
  55.             cpu: 100m
  56.           requests:
  57.             memory: 50M
  58.             cpu: 100m
复制代码
通过这种方法,每个代理(也就是每个应用)都可以被配置为向不同的收集器(也就是不同的后端存储)发送痕迹。
然而,这种方法最大的缺点之一是将代理的生命周期和应用程序紧密结合在一起。追踪的目的是在应用程序的生命周期内提供对其的洞察力。更有可能的是,代理侧车容器在主应用容器之前被杀死,在应用服务关闭期间,任何/所有重要的追踪都会丢失。这些痕迹的丢失对于理解复杂服务交互的应用生命周期行为可能是非常重要的。这个GitHub问题验证了在关机期间正确处理SIGTERM的必要性。
Jaeger Daemonset 代理

另一种方法是通过Kubernetes中的DaemonSet工作负载,将代理作为集群中每个节点的守护程序运行。DaemonSet工作负载可以确保当节点被扩展时,DaemonSet Pod的副本也随之扩展。
在这种情况下,每个代理守护程序负责从其节点中安排的所有运行中的应用程序(配置了Jaeger客户端)中获取追踪信息。这是通过在客户端设置JAEGER_AGENT_HOST指向节点中代理的IP来配置的。代理DaemonSet被配置为hostNetwork: true和适当的DNS策略,以便Pod使用与主机相同的IP。由于代理的6831端口是通过UDP接受jaeger.thrift消息的,所以守护的Pod配置端口也与hostPort: 6831绑定。
  1. # Auth : janrs.com
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5.   name: myapp-deployment
  6.   namespace: default
  7.   labels:
  8.     app: myapp
  9. spec:
  10.   replicas: 3
  11.   selector:
  12.     matchLabels:
  13.       app: myapp
  14.   template:
  15.     metadata:
  16.       labels:
  17.         app: myapp
  18.     spec:
  19.       containers:
  20.       - name: myapp
  21.         image: masroorhasan/myapp
  22.         ports:
  23.         - containerPort: 80
  24.         env:
  25.         - name: JAEGER_SERVICE_NAME
  26.           value: myapp
  27.         - name: JAEGER_AGENT_HOST   # NOTE: Point to the Agent daemon on the Node
  28.           valueFrom:
  29.             fieldRef:
  30.               fieldPath: status.hostIP
  31.         - name: JAEGER_AGENT_PORT
  32.           value: "6831"
  33.         resources:
  34.           limits:
  35.             memory: 500M
  36.             cpu: 250m
  37.           requests:
  38.             memory: 500M
  39.             cpu: 250m
  40. ---
  41. apiVersion: extensions/v1beta1
  42. kind: DaemonSet
  43. metadata:
  44.   name: jaeger-agent
  45.   namespace: monitoring
  46.   labels:
  47.     app: jaeger
  48.     jaeger-infra: agent-daemonset
  49. spec:
  50.   template:
  51.     metadata:
  52.       labels:
  53.         app: jaeger
  54.         jaeger-infra: agent-instance
  55.     spec:
  56.       hostNetwork: true     # NOTE: Agent is configured to have same IP as the host/node
  57.       dnsPolicy: ClusterFirstWithHostNet
  58.       containers:
  59.       - name: agent-instance
  60.         image: jaegertracing/jaeger-agent:1.6.0
  61.         command:
  62.           - "/go/bin/agent-linux"
  63.           - "--collector.host-port=jaeger-collector.monitoring:14267"
  64.           - "--processor.jaeger-binary.server-queue-size=2000"
  65.           - "--discovery.conn-check-timeout=500ms"
  66.         ports:
  67.         - containerPort: 5775
  68.           protocol: UDP
  69.         - containerPort: 6831
  70.           protocol: UDP
  71.           hostPort: 6831
  72.         - containerPort: 6832
  73.           protocol: UDP
  74.         - containerPort: 5778
  75.           protocol: TCP
  76.         resources:
  77.           requests:
  78.             memory: 200M
  79.             cpu: 200m
  80.           limits:
  81.             memory: 200M
  82.             cpu: 200m
复制代码
人们可能会被诱惑(就像我一样),用Kubernetes服务来引导DaemonSet。这背后的想法是,不要把应用程序的痕迹绑定到当前节点的单一代理上。使用服务可以将工作负载(跨度)分散到集群中的所有代理。这在理论上减少了在受影响节点的单个代理荚发生故障的情况下,应用实例丢失跨度的机会。
然而,当你的应用程序扩展时,这将不起作用,高负载会在需要处理的痕迹数量上产生巨大的峰值。使用Kubernetes服务意味着通过网络从客户端向代理发送追踪信息。很快,我就开始注意到大量的掉线现象。客户端通过UDP thrift协议向代理发送跨度,大量的峰值导致超过UDP最大数据包大小,从而导致丢包。
解决办法是适当地分配资源,使Kubernetes在整个集群中更均匀地调度pod。[#博文来源:janrs.com#]我们可以增加客户端的队列大小(设置JAEGER_REPORTER_MAX_QUEUE_SIZE环境变量),以便在代理失效时有足够的缓冲空间。增加代理的内部队列大小也是有益的(设置处理器.jaeger-binary.server-queue-size值),这样他们就不太可能开始丢弃跨度。
Jaeger Collector 服务

Jaeger收集器负责从Jaeger代理那里接收成批的跨度,通过处理管道运行它们,并将它们存储在指定的存储后端。跨度以jaeger.thrift格式从Jaeger代理处通过TChannel(TCP)协议发送,端口为14267。
Jaeger收集器是无状态的,可以根据需要扩展到任何数量的实例。因此,收集器可以由Kubernetes内部服务(ClusterIP)前置,可以从代理到不同收集器实例的内部流量进行负载平衡。
  1. apiVersion: extensions/v1beta1
  2. kind: Deployment
  3. metadata:
  4.   name: jaeger-collector
  5.   namespace: monitoring
  6.   labels:
  7.     app: jaeger
  8.     jaeger-infra: collector-deployment
  9. spec:
  10.   replicas: 1
  11.   strategy:
  12.     type: Recreate
  13.   template:
  14.     metadata:
  15.       labels:
  16.         app: jaeger
  17.         jaeger-infra: collector-pod
  18.     spec:
  19.       containers:
  20.       - image: jaegertracing/jaeger-collector:1.6.0
  21.         name: jaeger-collector
  22.         args: ["--config-file=/conf/collector.yaml"]
  23.         ports:
  24.         - containerPort: 14267
  25.           protocol: TCP
  26.         - containerPort: 14268
  27.           protocol: TCP
  28.         - containerPort: 9411
  29.           protocol: TCP
  30.         readinessProbe:
  31.           httpGet:
  32.             path: "/"
  33.             port: 14269
  34.         volumeMounts:
  35.         - name: jaeger-configuration-volume
  36.           mountPath: /conf
  37.         env:
  38.         - name: SPAN_STORAGE_TYPE
  39.           valueFrom:
  40.             configMapKeyRef:
  41.               name: jaeger-configuration
  42.               key: span-storage-type
  43.       volumes:
  44.         - configMap:
  45.             name: jaeger-configuration
  46.             items:
  47.               - key: collector
  48.                 path: collector.yaml
  49.           name: jaeger-configuration-volume
  50.       resources:
  51.         requests:
  52.           memory: 300M
  53.           cpu: 250m
  54.         limits:
  55.           memory: 300M
  56.           cpu: 250m
  57. ---
  58. apiVersion: v1
  59. kind: Service
  60. metadata:
  61.   name: jaeger-collector
  62.   namespace: monitoring
  63.   labels:
  64.     app: jaeger
  65.     jaeger-infra: collector-service
  66. spec:
  67.   ports:
  68.   - name: jaeger-collector-tchannel
  69.     port: 14267
  70.     protocol: TCP
  71.     targetPort: 14267
  72.   selector:
  73.     jaeger-infra: collector-pod
  74.   type: ClusterIP
  75. view raw
复制代码
Jaeger Query 查询服务

查询服务是支持用户界面的Jaeger服务器。它负责从存储器中检索痕迹,并将其格式化以显示在用户界面上。根据查询服务的使用情况,它的资源占用率非常小。
设置一个内部Jaeger用户界面的入口,指向后端查询服务。
  1. apiVersion: extensions/v1beta1
  2. kind: Deployment
  3. metadata:
  4.   name: jaeger-query
  5.   namespace: monitoring
  6.   labels:
  7.     app: jaeger
  8.     jaeger-infra: query-deployment
  9. spec:
  10.   replicas: 1
  11.   strategy:
  12.     type: Recreate
  13.   template:
  14.     metadata:
  15.       labels:
  16.         app: jaeger
  17.         jaeger-infra: query-pod
  18.     spec:
  19.       containers:
  20.       - image: jaegertracing/jaeger-query:1.6.0
  21.         name: jaeger-query
  22.         args: ["--config-file=/conf/query.yaml"]
  23.         ports:
  24.         - containerPort: 16686
  25.           protocol: TCP
  26.         readinessProbe:
  27.           httpGet:
  28.             path: "/"
  29.             port: 16687
  30.         volumeMounts:
  31.         - name: jaeger-configuration-volume
  32.           mountPath: /conf
  33.         env:
  34.         - name: SPAN_STORAGE_TYPE
  35.           valueFrom:
  36.             configMapKeyRef:
  37.               name: jaeger-configuration
  38.               key: span-storage-type
  39.         resources:
  40.           requests:
  41.             memory: 100M
  42.             cpu: 100m
  43.           limits:
  44.             memory: 100M
  45.             cpu: 100m
  46.       volumes:
  47.         - configMap:
  48.             name: jaeger-configuration
  49.             items:
  50.               - key: query
  51.                 path: query.yaml
  52.           name: jaeger-configuration-volume
  53. ---
  54. apiVersion: v1
  55. kind: Service
  56. metadata:
  57.   name: jaeger-query
  58.   namespace: monitoring
  59.   labels:
  60.     app: jaeger
  61.     jaeger-infra: query-service
  62. spec:
  63.   ports:
  64.   - name: jaeger-query
  65.     port: 16686
  66.     targetPort: 16686
  67.   selector:
  68.     jaeger-infra: query-pod
  69.   type: ClusterIP
  70. ---
  71. apiVersion: extensions/v1beta1
  72. kind: Ingress
  73. metadata:
  74. name: jaeger-ui
  75. namespace: monitoring
  76. annotations:
  77.    kubernetes.io/ingress.class: traefik # or nginx or whatever ingress controller
  78. spec:
  79. rules:
  80. - host: jaeger.internal-host # your jaeger internal endpoint
  81.    http:
  82.      paths:
  83.      - backend:
  84.          serviceName: jaeger-query
  85.          servicePort: 16686
复制代码
Storage Configuration 存储配置

Jaeger同时支持ElasticSearch和Cassandra作为存储后端。使用ElasticSearch作为存储,可以拥有一个强大的监控基础设施,将跟踪和日志日志记录联系在一起。采集器处理管道的一部分是为其存储后端索引跟踪--这将使跟踪显示在你的日志日志UI(例如Kibana)中,也将跟踪ID与你的结构化日志标签绑定。你可以通过SPAN_STORAGE_TYPE的环境变量将存储类型设置为ElasticSearch,并通过配置配置存储端点。
Kubernetes ConfigMap用于设置一些Jaeger组件的存储配置。例如,Jaeger收集器和查询服务的存储后端类型和端点。
  1. apiVersion: v1
  2. kind: ConfigMap
  3. metadata:
  4.   name: jaeger-configuration
  5.   namespace: monitoring
  6.   labels:
  7.     app: jaeger
  8.     jaeger-infra: configuration
  9. data:
  10.   span-storage-type: elasticsearch
  11.   collector: |
  12.     es:
  13.       server-urls: http://elasticsearch:9200
  14.     collector:
  15.       zipkin:
  16.         http-port: 9411
  17.   query: |
  18.     es:
  19.       server-urls: http://elasticsearch:9200
复制代码
监控

如前所述,追踪是监控基础设施的一个重要组成部分。这意味着,甚至你的追踪基础设施的组件也需要被监控。
Jaeger在每个组件的特定端口上以Prometheus格式暴露指标。如果有正在运行的Prometheus节点导出器(它绝对应该是)在特定的端口上刮取指标 - 然后将你的Jaeger组件的指标端口映射到节点导出器正在刮取指标的端口。
这可以通过更新Jaeger服务(代理、收集器、查询)来完成,将它们的指标端口(5778、14628或16686)映射到节点出口商期望搜刮指标的端口(例如8888/8080)。
一些需要跟踪的重要指标。

  • Health of each component — memory usage:
    sum(rate(container_memory_usage_bytes{container_name=~”^jaeger-.+”}[1m])) by (pod_name)
  • Health of each component — CPU usage:
    sum(rate(container_cpu_usage_seconds_total{container_name=~"^jaeger-.+"}[1m])) by (pod_name)
  • Batch failures by Jaeger Agent:
    sum(rate(jaeger_agent_tc_reporter_jaeger_batches_failures[1m])) by (pod)
  • Spans dropped by Collector:
    sum(rate(jaeger_collector_spans_dropped[1m])) by (pod)
  • Queue latency (p95) of Collector:
    histogram_quantile(0.95, sum(rate(jaeger_collector_in_queue_latency_bucket[1m])) by (le, pod))
这些指标为了解每个组件的性能提供了重要的见解,历史数据应被用来进行最佳设置。
转载请注明来源:https://janrs.com/2023/03/在kubernetes上使用jaeger的分布式追踪基础设施/

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
继续阅读请点击广告

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表