在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庫從環境變數初始化追蹤器配置,并啟用客戶端指標,
package tracer
import (
"io"
"github.com/uber/jaeger-client-go/config"
jprom "github.com/uber/jaeger-lib/metrics/prometheus"
)
func NewTracer() (opentracing.Tracer, io.Closer, error) {
// load config from environment variables
cfg, _ := jaegercfg.FromEnv()
// 博客原來:janrs.com
// create tracer from config
return cfg.NewTracer(
config.Metrics(jprom.New()),
)
}
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,
package grpc_server
import (
"github.com/opentracing/opentracing-go"
"github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
"github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc"
"github.com/masroorhasan/myapp/tracer"
)
func NewServer() (*grpc.Server, error) {
// initialize tracer
tracer, closer, err := tracer.NewTracer()
defer closer.Close()
if err != nil {
return &grpc.Server{}, err
}
opentracing.SetGlobalTracer(tracer)
// initialize grpc server with chained interceptors # janrs.com
s := grpc.NewServer(
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
// add opentracing stream interceptor to chain
grpc_opentracing.StreamServerInterceptor(grpc_opentracing.WithTracer(tracer)),
)),
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
// add opentracing unary interceptor to chain
grpc_opentracing.UnaryServerInterceptor(grpc_opentracing.WithTracer(tracer)),
)),
)
return s, nil
}
為了實作對gRPC服務的上游和下游請求的追蹤,gRPC客戶端也必須用客戶端開放追蹤攔截器進行初始化,如下例所示,
package grpc_client
import (
"github.com/opentracing/opentracing-go"
"github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
"github.com/grpc-ecosystem/go-grpc-middleware"
"google.golang.org/grpc"
"github.com/masroorhasan/myapp/tracer"
)
func NewClientConn(address string) (*grpc.ClientConn, error) {
// initialize tracer #博文來源:janrs.com
tracer, closer, err := tracer.NewTracer()
defer closer.Close()
if err != nil {
return &grpc.ClientConn{}, err
}
// initialize client with tracing interceptor [#博文來源:janrs.com#] using grpc client side chaining
return grpc.Dial(
address,
grpc.WithStreamInterceptor(grpc_middleware.ChainStreamClient(
grpc_opentracing.StreamClientInterceptor(grpc_opentracing.WithTracer(tracer)),
)),
grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(
grpc_opentracing.UnaryClientInterceptor(grpc_opentracing.WithTracer(tracer)),
)),
)
}
由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設定的,
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
namespace: default
labels:
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: masroorhasan/myapp
ports:
- containerPort: 80
env:
- name: JAEGER_SERVICE_NAME
value: myapp
- name: JAEGER_AGENT_HOST
value: localhost # default
- name: JAEGER_AGENT_PORT
value: "6831"
resources:
limits:
memory: 500M
cpu: 250m
requests:
memory: 500M
cpu: 250m
# sidecar agent
- name: jaeger-agent
image: jaegertracing/jaeger-agent:1.6.0
ports:
- containerPort: 5775
protocol: UDP
- containerPort: 5778
protocol: TCP
- containerPort: 6831
protocol: UDP
- containerPort: 6832
protocol: UDP
command:
- "/go/bin/agent-linux"
- "--collector.host-port=jaeger-collector.monitoring:14267"
resources:
limits:
memory: 50M
cpu: 100m
requests:
memory: 50M
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系結,
# Auth : janrs.com
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
namespace: default
labels:
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: masroorhasan/myapp
ports:
- containerPort: 80
env:
- name: JAEGER_SERVICE_NAME
value: myapp
- name: JAEGER_AGENT_HOST # NOTE: Point to the Agent daemon on the Node
valueFrom:
fieldRef:
fieldPath: status.hostIP
- name: JAEGER_AGENT_PORT
value: "6831"
resources:
limits:
memory: 500M
cpu: 250m
requests:
memory: 500M
cpu: 250m
---
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: jaeger-agent
namespace: monitoring
labels:
app: jaeger
jaeger-infra: agent-daemonset
spec:
template:
metadata:
labels:
app: jaeger
jaeger-infra: agent-instance
spec:
hostNetwork: true # NOTE: Agent is configured to have same IP as the host/node
dnsPolicy: ClusterFirstWithHostNet
containers:
- name: agent-instance
image: jaegertracing/jaeger-agent:1.6.0
command:
- "/go/bin/agent-linux"
- "--collector.host-port=jaeger-collector.monitoring:14267"
- "--processor.jaeger-binary.server-queue-size=2000"
- "--discovery.conn-check-timeout=500ms"
ports:
- containerPort: 5775
protocol: UDP
- containerPort: 6831
protocol: UDP
hostPort: 6831
- containerPort: 6832
protocol: UDP
- containerPort: 5778
protocol: TCP
resources:
requests:
memory: 200M
cpu: 200m
limits:
memory: 200M
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)前置,可以從代理到不同收集器實體的內部流量進行負載平衡,
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: jaeger-collector
namespace: monitoring
labels:
app: jaeger
jaeger-infra: collector-deployment
spec:
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: jaeger
jaeger-infra: collector-pod
spec:
containers:
- image: jaegertracing/jaeger-collector:1.6.0
name: jaeger-collector
args: ["--config-file=/conf/collector.yaml"]
ports:
- containerPort: 14267
protocol: TCP
- containerPort: 14268
protocol: TCP
- containerPort: 9411
protocol: TCP
readinessProbe:
httpGet:
path: "/"
port: 14269
volumeMounts:
- name: jaeger-configuration-volume
mountPath: /conf
env:
- name: SPAN_STORAGE_TYPE
valueFrom:
configMapKeyRef:
name: jaeger-configuration
key: span-storage-type
volumes:
- configMap:
name: jaeger-configuration
items:
- key: collector
path: collector.yaml
name: jaeger-configuration-volume
resources:
requests:
memory: 300M
cpu: 250m
limits:
memory: 300M
cpu: 250m
---
apiVersion: v1
kind: Service
metadata:
name: jaeger-collector
namespace: monitoring
labels:
app: jaeger
jaeger-infra: collector-service
spec:
ports:
- name: jaeger-collector-tchannel
port: 14267
protocol: TCP
targetPort: 14267
selector:
jaeger-infra: collector-pod
type: ClusterIP
view raw
Jaeger Query 查詢服務
查詢服務是支持用戶界面的Jaeger服務器,它負責從存盤器中檢索痕跡,并將其格式化以顯示在用戶界面上,根據查詢服務的使用情況,它的資源占用率非常小,
設定一個內部Jaeger用戶界面的入口,指向后端查詢服務,
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: jaeger-query
namespace: monitoring
labels:
app: jaeger
jaeger-infra: query-deployment
spec:
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: jaeger
jaeger-infra: query-pod
spec:
containers:
- image: jaegertracing/jaeger-query:1.6.0
name: jaeger-query
args: ["--config-file=/conf/query.yaml"]
ports:
- containerPort: 16686
protocol: TCP
readinessProbe:
httpGet:
path: "/"
port: 16687
volumeMounts:
- name: jaeger-configuration-volume
mountPath: /conf
env:
- name: SPAN_STORAGE_TYPE
valueFrom:
configMapKeyRef:
name: jaeger-configuration
key: span-storage-type
resources:
requests:
memory: 100M
cpu: 100m
limits:
memory: 100M
cpu: 100m
volumes:
- configMap:
name: jaeger-configuration
items:
- key: query
path: query.yaml
name: jaeger-configuration-volume
---
apiVersion: v1
kind: Service
metadata:
name: jaeger-query
namespace: monitoring
labels:
app: jaeger
jaeger-infra: query-service
spec:
ports:
- name: jaeger-query
port: 16686
targetPort: 16686
selector:
jaeger-infra: query-pod
type: ClusterIP
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: jaeger-ui
namespace: monitoring
annotations:
kubernetes.io/ingress.class: traefik # or nginx or whatever ingress controller
spec:
rules:
- host: jaeger.internal-host # your jaeger internal endpoint
http:
paths:
- backend:
serviceName: jaeger-query
servicePort: 16686
Storage Configuration 存盤配置
Jaeger同時支持ElasticSearch和Cassandra作為存盤后端,使用ElasticSearch作為存盤,可以擁有一個強大的監控基礎設施,將跟蹤和日志記錄聯系在一起,采集器處理管道的一部分是為其存盤后端索引跟蹤--這將使跟蹤顯示在你的日志UI(例如Kibana)中,也將跟蹤ID與你的結構化日志標簽系結,你可以通過SPAN_STORAGE_TYPE的環境變數將存盤型別設定為ElasticSearch,并通過配置配置存盤端點,
Kubernetes ConfigMap用于設定一些Jaeger組件的存盤配置,例如,Jaeger收集器和查詢服務的存盤后端型別和端點,
apiVersion: v1
kind: ConfigMap
metadata:
name: jaeger-configuration
namespace: monitoring
labels:
app: jaeger
jaeger-infra: configuration
data:
span-storage-type: elasticsearch
collector: |
es:
server-urls: http://elasticsearch:9200
collector:
zipkin:
http-port: 9411
query: |
es:
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的分布式追蹤基礎設施/
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/545629.html
標籤:其他
下一篇:輕量級CI/CD發布部署環境搭建及使用_05_安裝宿主機環境(jdk、nodejs、maven、python2)
