譯者 | 陳峻
審校 | 孫淑娟
無論您是否已經實現了微服務,您的系統往往會由反向代理、應用程序、以及數據庫等多個組件組成。只要服務請求流經的組件數量越多,您對于監控的需求就越強烈。當然,監控只是狀態跟蹤的開始,您更需要一個能夠橫跨所有組件的聚合性視圖,通過指標和日志兩個維度,來實現可觀察性。
1.W3C的規范
具有跟蹤能力的解決方案通常能夠通過異構技術棧,來規范化監控的標準格式。目前,市場上已經存在了幾個不同的實現規范。而在大多數情況下,正如下面著名的XKCD漫畫所描述的那樣,一個新的規范需求往往會導致另一個額外規范的實施。
在此,我向你介紹一個新的、市場正在遵守的W3C規范--Trace Context。該規范定義了標準的HTTP標頭和一種數值的格式,來支持分布式跟蹤場景的上下文信息。畢竟,上下文信息不但能夠唯一地標識分布式系統中的各種請求,而且定義了一種添加和傳播提供者特定信息的方法。而Trace Context規范恰好標準化了上下文信息在服務之間的發送和修改方式。其中也包含了兩個如下圖所示的關鍵性概念:
- 跟蹤(Trace)需要遵循跨越多個組件的請求路徑。
- 跨越(Span)被綁定到了單個組件上,并通過父子關系,鏈接到另一個跨越。
目前Trace Context已經有了多種實現,而其中一種便是OpenTelemetry。
2.OpenTelemetry可作為黃金標準
OpenTelemetry是一種工具、API和SDK的集合,適用于多種語言。用戶可以使用它來檢測、生成、收集和導出指標、日志和跟蹤等遙測類型的數據,以便分析軟件的性能和行為。
OpenTelemetry是一個由CNCF管理的項目。在它之前已有如下兩個項目:
- OpenTracing,顧名思義是專注于跟蹤的
- OpenCensus,其目標是管理指標和跟蹤
通過合并兩個項目的日志功能,OpenTelemetry目前提供了一組專注于可觀察性的層面。該層面具有如下特征:
- 通過多種語言檢測API
- 用不同的語言規范化實現
- 提供諸如收集器等基礎設施組件
- 提供諸如W3C Trace Context的互操作性格式
值得注意的是,OpenTelemetry雖然是Trace Context的一種實現,但是其功能更為廣泛。Trace Context將自身限制在HTTP場景中,而OpenTelemetry則可以跨越到Kafka等非Web組件上。
3.用例
下面,讓我們以某個電商網站為例,進行深入探討。假設該電商網站是圍繞微服務設計的,其中包含了管理產品的catalog和處理產品價格的pricing,這兩個微服務。如下圖所示,catalog是一個用Kotlin編寫的Spring Boot應用,而pricing一個Python Flask應用。
每個微服務都可以通過REST API被訪問到,并且受到API網關的保護。當用戶訪問該應用時,主頁會從后臺獲取所有產品、及其價格的信息,予以呈現。而跟蹤則可以讓我們跨過微服務或數據庫網關,去跟蹤某個請求的路徑。
4.網關處的跟蹤
我們將使用Apache APISIX在入口點(也就是網關)處生成跟蹤ID。此處的Apache APISIX提供了諸如:負載均衡、動態上游、金絲雀發布、斷路器、身份驗證、可觀察性等,豐富的流量管理功能。同時,基于插件架構的Apache APISIX,通過提供OpenTelemetry插件,來根據OpenTelemetry的規范產生跟蹤數據。不過,該插件僅支持基于HTTP的二進制編碼--OLTP。
下面,讓我們來配置opentelemetry插件:
#1:在獨立模式下運行Apache APISIX,以便應用易于被理解。
#2:將opentelemetry配置為全局插件。
#3:設置服務的名稱。它將成為出現在跟蹤顯示組件中的名稱。
#4:將跟蹤發送到jaeger服務處。我們將在下文中詳細討論。
為了跟蹤每一條路由,我們需要通過如下代碼,將插件設置為全局插件,而無需向每條路由添加插件:
#1:由于跟蹤本身也會對性能產生影響,而且追蹤的內容越多,影響也就越大,因此,我們全面仔細平衡性能影響與可觀察性的收益。不過,在本例中,我們仍然希望能夠跟蹤每個請求。
5.收集、存儲和顯示跟蹤
雖然Trace Context是一種W3C規范,而且OpenTelemetry是事實上的標準,但是目前市場上仍存在許多收集、存儲和顯示跟蹤的解決方案。例如,Elastic技術棧雖然可以處理各種存儲和顯示,但是用戶必須依靠其他產品進行收集;而Jaeger和Zipkin則能夠通過一個完整的套件,全面實現收集、存儲和顯示跟蹤。
早于OpenTelemetry的Jaeger和Zipkin雖然有著各自不同的跟蹤傳輸格式,但是它們都能夠與OpenTelemetry的格式相集成。鑒于Jaeger能夠提供一個一體化的Docker鏡像,而且每個功能都有其對應的組件,同時它們被嵌入在同一個鏡像中,以方便配置。因此,我在此選用Jaeger。其鏡像的相關端口分配如下:
端口 | 協議 | 組件 | 功能 |
16686 | HTTP | 查詢 | 服務前端 |
4317 | HTTP | 收集器 | 如果啟用的話,接受通過gRPC的OpenTelemetry協議(OTLP) |
4318 | HTTP | 收集器 | 如果啟用的話,接受通過HTTP的OpenTelemetry協議 |
其Docker Compose的代碼為:
#1:使用all-in-one的鏡像。
#2:啟用OpenTelemetry格式的收集器(非常重要)。
#3:暴露UI端口。
至此,我們已經完成了基礎設施的構建,下面我們將專注于在應用中啟用跟蹤。
6.Flask應用的跟蹤
pricing服務是一個簡單的Flask應用程序。它提供了從數據庫中獲取單個產品與價格的端點。下面是對應的代碼:
#1:端點
#2:路由需要產品的ID。
#3:使用SQLAlchemy從數據庫中獲取數據。
#4:在此,我們會隨機產生價格。當然,真實定價引擎并非如此。
值得注意的是,為了觀察跟蹤,我們在此采用的是低效的、每次僅調用并獲取單一價格的方式。而在現實生活中,路由應該能夠接受多個產品的ID,并在一個“請求-響應”中獲取所有相關的價格。
我們可以用自動和手動兩種方式來檢測應用。由于手動需要付出開發時間,而自動既省力又快速,因此我建議您從自動開始,如需添加手動的方式。
首先,我們需要添加幾個Python包:
- opentelemetry-distro[otlp]==0.33b0
- opentelemetry-instrumentation
- opentelemetry-instrumentation-flask
接著,我們需要配置如下參數:
左右滑動查看完整代碼
#1:將跟蹤發送給Jaeger。
#2:設置服務的名稱。它將成為出現在跟蹤顯示組件中的名稱。
#3:在此,我們暫時忽略日志和指標。
然后,我們并不使用標準的flask run命令,而是將其進行如下包裝:
至此,我們已經從方法調用和Flask路由中,收集到了跨越。
下面,我們以手動的方式,按需添加額外的跨度:
#1:使用配置的標簽和屬性添加一個額外的跨度。
7.Spring Boot應用的跟蹤
catalog服務是用Kotlin開發的Reactive Spring Boot應用。它提供了如下兩個端點:
- 獲取單個產品
- 獲取所有產品
兩者都是先查看產品數據庫,再查詢上述pricing服務的價格。
而對于Python而言,我們同樣可以利用自動和手動兩種檢測方法。在此,讓我們先從唾手可得的自動化開始。在JVM上,我們通過一個代理來實現:
與Python一樣,它為每個方法的調用和HTTP的入口點創建了跨越。同時,它還會檢測JDBC的調用。在本例的Reactive棧中,我們使用的是R2DBC。因此,我們需要配置如下默認行為:
#1:將跟蹤發送給Jaeger。
#2:設置服務的名稱。它將成為出現在跟蹤顯示組件中的名稱。
#3:在此,我們暫時忽略日志和指標。
而對于Python而言,我們直接添加手動檢測。當然,目前有兩個選項可被采用:程序化和基于注釋。除非我們引入Spring Cloud Sleuth,否則前者會略顯復雜。下面,讓我們來添加額外的依賴性:
其對應的注釋性代碼為:
#1:使用配置的標簽添加一個額外的跨越。
#2:將參數用作屬性,將鍵(key)設置為id,其對應的值則設置為參數的運行時數值。
8.輸出結果
下面,我們通過如下命令來查看該示例的結果:
通過如下Jaeger UI,我們可以找到兩條跟蹤(每次調用一條):
我們也可以深入研究單個跟蹤的跨越:
值得注意的是,我們還可以在沒有前文那張UML圖表的情況下,推斷出其數據流圖。此類數據流圖很好地顯示了組件的內部調用。而且,每個跨越都包含了由自動與手動檢測添加進來的屬性:
9.小結
綜上所述,我通過簡單的示例,展示了如何使用OpenTelemetry,跨過微服務或數據庫網關,實現對某個請求路徑的端對端跟蹤。
雖然在現實世界中,跟蹤可能會涉及與HTTP無關的組件,例如Kafka和消息隊列等,但是大多數系統仍然會以某種方式去依賴HTTP。而且,跨組件地跟蹤HTTP請求,是實現系統可觀察性的良好開端。
原文鏈接:https://dzone.com/articles/end-to-end-tracing-with-opentelemetry