在《應用程式通過 Envoy 代理和 Jaeger 進行分散式追蹤(一)》這篇博文中,我們詳細介紹了單個應用程式通過 Envoy 和 Jaeger 實現鏈路追蹤的過程。通過這個範例我們知道,Istio 支援通過 Envoy 代理進行分散式追蹤,代理會自動為其應用程式生成追蹤 span,只需要應用程式轉發適當的請求上下文即可。特別是,Istio 依賴於應用程式傳播 b3 追蹤 Header 以及由 Envoy 生成的請求 ID,即應用程式服務請求時需攜帶這些 Header。如果請求中沒有 B3 HTTP Header,Istio Sidecar 代理(Envoy) 會自動生成初始化的 Headers。
在《Nginx Ingress Contoller 通過 Envoy 代理和 Jaeger 進行分散式追蹤(二)》這篇博文中,進一步擴充套件鏈路追蹤範圍,演示瞭如何將 Nginx Ingress Controller 與之前提到的應用程式一起使用,從而實現更為複雜的分散式鏈路追蹤。
在本文將繼續擴充套件鏈路追蹤範圍,演示 Nginx Ingress Controller(http協定)——》前端服務(http協定)——》後端服務(http協定)——》告警使用者端後端服務 (grpc協定)——》告警伺服器端後端服務的分散式鏈路追蹤。
其中 Nginx Ingress Controller 服務和前端服務預設會自動轉發請求的上下文資訊(Header),後端服務通過少量的程式碼侵入將原請求的上下文資訊(Header)轉發給告警使用者端後端服務,告警使用者端後端服務通過少量的程式碼侵入將原請求的上下文資訊(Grpc Metadata)轉發給告警伺服器端後端服務。
本文主要目的是講解分散式鏈路追蹤,因此不再講解各元件的部署,這些元件部署時都需要注入 Envoy 邊車,注入邊車後,各元件通過 Envoy 代理和 Jaeger 進行分散式追蹤。
Nginx Ingress Controller 的部署本文略,這裡只展示下 Nginx Ingress Controller 是如何將服務代理到前端服務的。
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: xxxx nginx.ingress.kubernetes.io/service-upstream: "true" nginx.ingress.kubernetes.io/upstream-vhost: dashboard.xxxx.svc.cluster.local name: dashboard-tracing spec: rules: - host: dashboard-tracing.10.20.32.203.nip.io http: paths: - backend: service: name: dashboard port: number: 80 path: / pathType: ImplementationSpecific
前端服務預設會自動轉發請求的上下文資訊(Header)資訊到後端服務,所以前端服務無需侵入,其他前端相關內容本文不再贅餘,我們只需要知道前端服務會自動轉發請求的上下文資訊即可。
後端服務需要通過少量的程式碼侵入將原請求的上下文資訊(Header)資訊轉發給告警使用者端後端服務,下面通過侵入原始碼來分析下是如何將原請求的上下文資訊轉發給告警使用者端後端服務的。
主要是使用 k8s.io/apimachinery 庫的 proxy 包,通過 proxy 包的 ServeHTTP 方法作為使用者端呼叫告警使用者端後端服務。呼叫 ServeHTTP 方法時後端服務會將 request 物件作為引數(request物件除了組織告警使用者端 Host 資訊外,還將前端服務傳遞過來的 Header 資訊放到了request 物件中,此塊程式碼因隱私問題不再貼上),這樣在 ServeHTTP 方法邏輯裡面就能獲取到前端服務傳遞過來的上下文資訊(Header)了。
...... httpProxy := proxy.NewUpgradeAwareHandler(u, http.DefaultTransport, false, false, &errorResponder{}) httpProxy.ServeHTTP(response, request.Request)
下面看下 ServeHTTP 方法邏輯,可以看到通過呼叫 utilnet.CloneHeader 方法將前端服務傳遞過來的上下文資訊(Header)傳遞給告警使用者端後端服務。
// 方法包路徑: k8s.io/[email protected]/pkg/util/proxy/upgradeaware.go // ServeHTTP handles the proxy request func (h *UpgradeAwareHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { ....... // WithContext creates a shallow clone of the request with the same context. newReq := req.WithContext(req.Context()) // 主要看這裡,新的請求會克隆老請求物件裡面的 Header 頭資訊 newReq.Header = utilnet.CloneHeader(req.Header) if !h.UseRequestLocation { newReq.URL = &loc } ...... proxy.ServeHTTP(w, newReq) }
在2.2-2.4中,都是 http 之間的鏈路追蹤,都是通過 header 傳遞鏈路追蹤相關的上下文資訊,2.5告警使用者端後端服務是通過 grpc 協定存取告警伺服器端後端服務的,但是在 grpc 中怎麼傳遞呢?
grpc 底層採用 http2 協定也是支援傳遞資料的,採用的是 Metadata,Metadata 對於 gRPC 本身來說透明, 它使得 client 和 server 能為對方提供本次呼叫的資訊。
就像一次 http 請求的 RequestHeader 和 ResponseHeader,http header 的生命週期是一次 http 請求, Metadata 的生命週期則是一次 RPC 呼叫。
下面來看下告警使用者端後端服務是如何將從後端服務轉發的上下文資訊通過 Metadata 傳遞給告警伺服器端後端服務的。
下面以修改查詢告警訊息列表方法邏輯為例,演示警使用者端後端服務如何將從後端服務轉發的上下文資訊通過 Metadata 傳遞給告警伺服器端後端服務,其他方法修改方式和此方法一致。
...... // 傳播追蹤上下文資訊,將後端服務傳遞過來的上下文資訊放置到metadata物件中,通過context傳遞給告警伺服器端後端服務 // type MD map[string][]string httpHeader := metadata.MD(utilnet.CloneHeader(request.Request.Header)) ctx = metadata.NewOutgoingContext(ctx, httpHeader) ......
使用者端在前端服務存取查詢告警訊息列表功能時,告警使用者端後端服務作為 grpc 使用者端呼叫了兩次告警伺服器端後端服務的介面,總共11個 span,envoy 上報鏈路資訊如下:
(1)Nginx Ingress Controller 入站流量劫持 ——》nginx ——》(2)Nginx Ingress Controller 出站流量劫持 ——》(3)前端服務入站劫持 ——》前端服務 ——》(4)前端服務出站劫持 ——》(5)後端服務入站劫持 ——》 後端服務 ——》(6)後端服務出站劫持 ——》(7)告警使用者端後端服務入站劫持 ——》告警使用者端後端服務 ——》(8)告警使用者端後端服務出站劫持 ——》(9)告警伺服器端後端服務入站劫持 ——》 告警伺服器端後端服務訊息列表方法(回包)——》 告警使用者端後端服務 ——》 (10)告警使用者端後端服務出站劫持 ——》(11)告警伺服器端後端服務入站劫持 ——》 告警伺服器端後端服務訊息歷史方法(回包)
注意 1:如果存取容器的入站流量不是inbound、或者出站流量不是outbound,請檢查對應服務的服務協定是否正確,尤其出站流量是 PassthroughCluster。
這時候可以通過 istioctl pc listener <pod-name>.<pod-namespace> --port 埠命令排查是否沒有進行路由匹配。例如以下範例能成功匹配下面路由的話出站流量就為 outbound。
通過《應用程式通過 Envoy 代理和 Jaeger 進行分散式追蹤(一)》、《Nginx Ingress Contoller 通過 Envoy 代理和 Jaeger 進行分散式追蹤(二)》加上本篇三篇博文,詳細解決了應用程式如何通過 Envoy 代理和 Jaeger 進行分散式追蹤,最主要的還是這一點:為了將各種追蹤 span 整合在一起以獲得完整的追蹤圖,應用程式(不管是http協定、grpc協定、或者是其他Istio支援的能上報鏈路追蹤的服務協定)必須在傳入和傳出請求之間傳播追蹤上下文資訊。