Nginx Ingress Contoller 通過 Envoy 代理和 Jaeger 進行分散式追蹤(二)

2023-08-22 06:01:08

1、概述

  在《應用程式通過 Envoy 代理和 Jaeger 進行分散式追蹤(一)》一文中,我們詳細介紹了單個應用程式如何通過 Envoy 和 Jaeger 實現鏈路追蹤的過程。然而,單獨追蹤單個應用程式的鏈路在實際場景中往往顯得不夠有意義。因此,在本文中,我們將進一步擴充套件鏈路追蹤範圍,演示如何將 Nginx Ingress Controller 與之前提到的應用程式一起使用,從而實現更為複雜的分散式鏈路追蹤。

2、 通過 Nginx Ingress Controller 代理存取 http_request_printer 服務 

2.1 Nginx Ingress Controller 元件不注入邊車,http_request_printer 服務不注入邊車

流量走向: 使用者端 ——》Nginx Ingress contorller Pod 中的業務容器 ——》http_request_printer Pod 中的業務容器

1)建立 Nginx Ingress contorller 資源使其能夠管理 tracing 名稱空間下的 Ingress 資源,注意 Nginx Ingress contorller 資源物件不要注入邊車,步驟略

2)建立Ingress資源物件,並代理到 http_request_printer 服務

[root@master1 ~]# kubectl get ingress -n=tracing http-request-printer -o yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
  ......
  name: http-request-printer
  namespace: tracing
spec:
  rules:
  - host: http-request-printer.xx.xx.xx.xx.nip.io
    http:
      paths:
      - backend:
          service:
            name: http-request-printer
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific

3)通過 Ingress 存取 http_request_printer 服務

 

4)通過容器紀錄檔檢視請求資訊

可以看到除了自定義 Header 和谷歌瀏覽器裡面的 Postman 外掛加了一些 Header 資訊外 Nginx Ingress Controller 元件還幫我們加了一些 Header 資訊,尤其注意  Nginx Ingress Controller 元件預設會給一個請求生成 X-Request-Id Header 頭資訊,並且它加的這個 X-Request-Id Header 頭資訊和  Envoy 產生的 X-Request-Id Header 頭資訊(xxx-xxx-xxx)格式不一致。

注意,由於 Nginx Ingress Controller 和 http_request_printer 都沒接入 Jaeger,所以此時不管怎麼存取在 Jaeger 裡面都是查不到鏈路資訊的。

2.2 Nginx Ingress Controller 元件不注入邊車,http_request_printer 服務注入邊車

流量走向: 使用者端 ——》Nginx Ingress contorller Pod 中的業務容器 ——》http_request_printer Pod 中的 Envoy 邊車容器入站流量劫持 ——》http_request_printer Pod 中的業務容器

1)重複執行2.1中的步驟1)-3),唯一不同的是 http_request_printer 注入邊車

2)通過容器紀錄檔檢視請求資訊

檢視 http_request_printer 服務對應容器紀錄檔,和2.1範例紀錄檔不一樣在於除了自定義 Header、谷歌瀏覽器裡面的 Postman 外掛加了一些 Header 資訊、以及 Nginx Ingress Controller 元件加的一些 Header 資訊外 http_request_printer 服務注入的邊車 Envoy 加了和鏈路追蹤相關的 Header 頭資訊 。

檢視 Nginx Ingress Controller 元件對應容器紀錄檔,可以看到 http_request_printer 服務使用的 X-Request-Id Header 頭資訊是  Nginx Ingress Controller 元件生成並傳遞給 http_request_printer 這個上游服務的,注意  Nginx Ingress Controller 元件它加的這個 X-Request-Id Header 頭資訊和  Envoy 產生的 X-Request-Id Header 頭資訊(xxx-xxx-xxx)格式不一致。

3) 檢視 http_request_printer 服務上報的鏈路資訊

整個請求存取鏈由於只有 http_request_printer 元件注入了邊車會向 jaeger-collector 元件上報鏈路資訊,所以整個請求鏈只有1個 span。

2.3 Nginx Ingress Controller 元件注入邊車,http_request_printer 服務注入邊車

流量走向: 使用者端 ——》Nginx Ingress contorller Pod 中的 Envoy 邊車容器入站流量劫持 ——》Nginx Ingress contorller Pod 中的業務容器 ——》Nginx Ingress contorller Pod 中的 Envoy 邊車容器出站流量劫持 ——》http_request_printer Pod 中的 Envoy 邊車容器入站流量劫持 ——》http_request_printer Pod 中的業務容器

1)重複執行2.1中的步驟1)-3),唯一不同的是 Nginx Ingress Controller 元件和 http_request_printer 服務都會注入邊車。

注意 1, 定義 Nginx Ingress Contorller  svc 資源物件時需要指定 Nginx Ingress Controller 服務應用層協定,因為 istio 需要知道服務提供什麼七層協定,從而來為其設定相應協定的 filter chain,千萬不要寫成 tcp 協定,否則不會上報鏈路資訊,詳情參見《 Istio 為服務指定協定 》這篇博文。

apiVersion: v1
kind: Service
metadata:
  name: nginx-ingress-controller
spec:
  ports:
  - name: http-80  #需要指定服務應用層協定,千萬不要寫成tcp協定,否則不會上報鏈路資訊
    port: 80
    protocol: TCP
    targetPort: 80
  - name: http-443
    port: 443
    protocol: TCP
    targetPort: 443
  - name: http-10254
    port: 10254
    protocol: TCP
    targetPort: 10254
  selector:
    app: nginx-ingress-controller
  sessionAffinity: None
  type: ClusterIP

2)通過容器紀錄檔檢視請求資訊

檢視 http_request_printer 服務對應容器紀錄檔,和2.2範例紀錄檔不一樣在於多了 X-B3-Parentspanid(父跨度ID)欄位 。

檢視 Nginx Ingress Controller 元件對應容器紀錄檔,可以看到 Nginx Ingress Controller 元件的 X-Request-Id Header頭資訊是 Nginx Ingress Controller 元件邊車生成的,然後 Nginx Ingress Controller 元件將 X-Request-Id Header 頭資訊傳遞給 http_request_printer 這個上游服務的。

3) 檢視 http_request_printer 服務上報的鏈路資訊

整個請求Nginx Ingress contorller Pod 中的 Envoy 邊車容器入站流量劫持、Nginx Ingress contorller Pod 中的 Envoy 邊車容器出站流量劫持、http_request_printer Pod 中的 Envoy 邊車容器入站流量劫持都會向 jaeger-collector 元件上報鏈路資訊,所以整個請求鏈有3個 span。

下面簡單分析下這三個span。

Nginx Ingress contorller Pod 中的 Envoy 邊車容器入站流量劫持 span:

Nginx Ingress contorller Pod 中的 Envoy 邊車容器出站流量劫持 span:

http_request_printer Pod 中的 Envoy 邊車容器入站流量劫持 span: 

注意 1:通過2.2和2.3章節我們知道,即使 Nginx Ingress Controller 元件不注入邊車,預設也會給一個請求生成 X-Request-Id Header 頭資訊,並且它加的這個 X-Request-Id Header 頭資訊是非標準形式形式的。而 Nginx Ingress Controller 元件注入邊車的話,流量先經過 Istio-Proxy 容器,自動加上標準形式的 X-Request-Id Header 頭資訊,再經過 Nginx 容器時,因為請求頭中已經有了 X-Request-Id Header 頭資訊,因此就不會使用 Nginx 生成的非標準 requestID。

注意 2:為什麼 Ingress 注入 Sidecar 跟不注入 Sidecar,X-Request-Id 形式不一樣呢?

當請求存取 Ingress 時,會先經過一個 Sidecar Istio-Proxy Container 後,產生一個標準 ID(xxx-xxx-xxx)。請求轉發至 Ingress Controller 的 Container,此時請求已經有了 RequestID,因此不會被 Nginx 覆蓋。Nginx 組態檔如下:

# Reverse proxies can detect if a client provides a X-Request-ID header, and pass it on to the backend server.
# If no such header is provided, it can provide a random value.
map $http_x_request_id $req_id {  # http_x_request_id 是請求進來的requestId
  default   $http_x_request_id; # 預設使用請求頭中的requestID,即將http_x_request_id的值賦給req_id

  ""        $request_id; # http_x_request_id是空,那麼使用 request_id,request_id是Nginx預設的自帶的變數,一個16位元位元的亂數,用32位元的16進位制數表示。

...
}
proxy_set_header X-Request-ID           $req_id;

注意 3:要使用 Nginx Ingress Controller 作為 Istio 網格入口的話需要在 ingress 資源上新增如下註解,詳情參見《利用Nginx Ingress Controller作為 Istio 網格入口》這篇博文。

nginx.ingress.kubernetes.io/service-upstream: "true"
nginx.ingress.kubernetes.io/upstream-vhost: http-request-printer.tracing.svc.cluster.local

不加這兩個註解的話,流量從 nginx ingress controller envoy 出站的時候直接通過 PodIp:Pod埠 存取上游服務,說明沒有匹配 envoy 設定規則,這時候在鏈路追蹤UI頁面查詢upstream_cluster 和 upstream_cluster.name 值的話都是PassthroughCluster。

 3、總結

通過《應用程式通過 Envoy 代理和 Jaeger 進行分散式追蹤(一)》這篇博文我們知道,Envoy 原生支援 Jaeger,追蹤所需 x-b3 開頭的 Header 和 x-request-id 在不同的服務之間由業務邏輯進行傳遞,並由 Envoy 上報給 Jaeger,最終 Jaeger 生成完整的追蹤資訊。為了將各種追蹤 span 整合在一起以獲得完整的追蹤圖,應用程式必須在傳入和傳出請求之間傳播追蹤上下文資訊。特別是,Istio 依賴於應用程式傳播 b3 追蹤 Header 以及由 Envoy 生成的請求 ID,即應用程式服務請求時需攜帶這些 Header。如果請求中沒有 B3 HTTP Header,Istio Sidecar 代理(Envoy) 會自動生成初始化的 Headers。

在本文我們進一步擴充套件鏈路追蹤範圍,演示瞭如何將 Nginx Ingress Controller 與之前提到的應用程式一起使用,從而實現更為複雜的分散式鏈路追蹤。應用程式通過 Envoy 代理實現分散式鏈路追蹤一定要注意以下幾點:

  1. Envoy 呼叫時會幫助應用程式生成 trace 資訊,而呼叫鏈中關鍵的就是 trace_id 和 span,如果應用不傳遞的話,每次 Envoy 都會生成新的 trace_id,導致檢視到的結果就是一個呼叫鏈中只有一個 span,所以應用程式必須在傳入和傳出請求之間傳播追蹤上下文資訊,否則整個鏈路串不起來。
  2. 定義 svc 資源物件時需要指定服務應用層協定,因為 istio 需要知道服務提供什麼七層協定,從而來為其設定相應協定的 filter chain,千萬不要寫成 tcp 協定,否則不會上報鏈路資訊。( 服務指定協定設定詳情請參見《 Istio 為服務指定協定 》這篇博文)
  3. 要使用 Nginx Ingress Controller 作為 Istio 網格入口的話需要在 ingress 資源上新增設定nginx.ingress.kubernetes.io/service-upstream、nginx.ingress.kubernetes.io/upstream-vhost註解。 (Nginx Ingress Controller 作為 Istio 網格入口設定詳情參見《利用Nginx Ingress Controller作為 Istio 網格入口》這篇博文)

參考:Jaeger講解