KubeCon 2023 上海之 OpenTelemetry 采樣分享
因為一直有關(guān)注 Kubernetes 相關(guān)的內(nèi)容,在 4 月 19 日 KubeCon + CloudNativeCon Europe 2023 舉行期間,就已經(jīng)知道時隔 3 年,CNCF 的旗艦會議要回到國內(nèi)線下舉行了。恰逢最近半年學(xué)習(xí)重心轉(zhuǎn)向 Observability,往往在起步階段接觸的新東西變多,就容易萌生一點 “自以為是” 和 “向別人分享” 的想法,于是提交了 CFP 碰碰運氣。
7 月末得知 Proposal 被接受了,可以看得出來主辦方是眷顧新人的。當(dāng)時心情頗為復(fù)雜,一邊是可以在更大的舞臺跟眾多開發(fā)者交流,一邊是工作還沒著落,很多準備材料都還處于原始的階段。后續(xù)又熬過了2個月,熬出了只言片語,也不知道聽眾的接受度會有多高,所以到正式分享前都非常緊張。整個過程中有很多人給過我建議,特別需要感謝 Juraci Paix?o Kr?hling、Reese Lee、陸家靖老師和趙梓旗老師的 Review,帶著這些的指導(dǎo)意見,我才得以進一步完善我的分享、輸出觀點。
Slides: https://docs.google.com/presentation/d/16PHf3XxZBuLjD0b07SMJmk0yfFAHB2jJpMPGgONngRE/edit?usp=sharing
1. 可觀測性之旅
云原生時代,微服務(wù)的使用越來越廣泛,大家都將原有的單體服務(wù)拆分成小塊。趣丸,一家主打興趣社交和電子競技的科技公司,亦是如此。在企業(yè)內(nèi),大部分的項目都運行在 Kubernetes 之上,搭配 Istio。對應(yīng)用服務(wù)的觀察,數(shù)據(jù)來源于服務(wù)內(nèi) SDK 上報、Istio 以及其他部署在節(jié)點上的 Agent。聚焦在分布式追蹤這方面,我們每日產(chǎn)生超過百億的 Span。通用的基礎(chǔ)設(shè)施能提供非常多的監(jiān)控指標,但仍有一些不起眼的角落缺乏足夠的關(guān)注。為了覆蓋這些場景,我們探索了 eBPF 的使用,嘗試為各種沒有 Istio 支持,或者出于各種原因缺乏埋點的應(yīng)用提供請求級別的可觀測性支持。
在所有這些探索的過程中,越來越多的數(shù)據(jù)被送入處理流程。例如,在一個規(guī)模中等的集群使用 eBPF 采集數(shù)據(jù)時,每天有 40 TiB 的 Trace 數(shù)據(jù)產(chǎn)生。使用這些數(shù)據(jù)是有成本的,我們必須考慮它是否值得:
-
有多少的 Trace 數(shù)據(jù)對問題排查有幫助? -
怎樣做才可以減少這些數(shù)據(jù)的產(chǎn)生呢?
OpenTelemetry 生態(tài)中提供了一些簡單的手段應(yīng)對上面的問題。
2. The OpenTelemetry Ways
在詳細了解解決方案之前,我們花幾分鐘再介紹一下分布式追蹤。
分布式追蹤可以為用戶描述一次請求所經(jīng)歷的過程,形成一張調(diào)用時序圖,我們稱這次調(diào)用過程調(diào)用鏈路為 Trace。而 Trace 由每個服務(wù)或者說服務(wù)處理一個請求的過程組成,這些過程稱為 Span。下圖中,這次調(diào)用產(chǎn)生了 4 個 Span,4 個 Span 組成一條 Trace。
一條 Trace 中包含了很多信息,包括各個 Span 的耗時、標記位、自定義的 Key-Value 屬性等等。Trace 是由不同服務(wù)上報的 Span 串聯(lián)而來的,如果去做篩選、采樣,讓每天產(chǎn)生的 1000 萬條 Trace 實際上只保留下來 1 萬條,可以怎樣實現(xiàn)呢?
第一種簡單的方法,當(dāng)請求抵達整個鏈路中第一個服務(wù)時,這個服務(wù)為這次請求生成一個獨一無二的 Trace ID,以及其他的標記位,這些信息會貫穿整次請求。標記位中包含采樣標記位,由服務(wù)依照一定的概率或者規(guī)則生成,代表要采樣或者不采樣。Trace ID 、標記位和其他上下文信息通過 RPC Header、Metadata 等方式傳播給后續(xù)的服務(wù),后續(xù)服務(wù)遵循第一個服務(wù)的采樣決定,也隨之采樣上報或不上報。這種采樣的方式稱為頭部采樣。
頭部采樣的特點在于,它是由頭部服務(wù),或者說第一個產(chǎn)生的 Span 決策的,傳播至后續(xù)的 Span,整個采樣決策發(fā)生客戶端 SDK 層面,僅有少量被采樣的數(shù)據(jù)允許被發(fā)送出去,因此,后續(xù)的處理流程中也只有少量數(shù)據(jù)流轉(zhuǎn)、存儲、展示。
頭部采樣最大的問題在于第一個 Span 的視野非常有限,無法預(yù)知未來會發(fā)生什么,也就無法依照整個請求是否有錯誤、是否執(zhí)行時間過長來做決策。
為了解決這些問題,我們也可以把所有數(shù)據(jù)都上報,交由一個中間平臺暫存、清洗,再決定保留還是丟棄整條 Trace。如下圖所示,當(dāng)請求抵達時,每個服務(wù)都將他自身產(chǎn)生的 Span 發(fā)送至 OpenTelemetry Collector,Collector 上的尾部采樣組件在接收到第一個 Span 后,會等待一段時間(如 5 秒)以繼續(xù)收集來自其他服務(wù)的、具有相同 Trace ID 的 Span。等待結(jié)束后,大量 Span 按照 Trace ID 歸類,然后對同一個 Trace ID 下的 Span 進行遍歷,以檢查其中是否包含錯誤信息、累計耗時是否超過閾值等,有依據(jù)地篩選高價值的 Trace 進入后續(xù)的流程。
這種采樣的模式稱為尾部采樣,它由 Collector 依據(jù)完整 Trace 的信息進行決策,決策的依據(jù)比頭部采樣豐富很多。但是由于需要承載大量臨時數(shù)據(jù),所以對基礎(chǔ)設(shè)施要求很高。它的效果在于:
-
持久化的數(shù)據(jù)有依據(jù)、有規(guī)律、有價值; -
減少了展示有價值數(shù)據(jù)所需的成本,例如存儲資源,并且對提高查詢速度也有幫助。
需要注意的是,在實際部署中,往往會存在多個 Collector 節(jié)點,而同一個 Trace 的不同 Span 是由不同服務(wù)產(chǎn)生的,這些服務(wù)位于不同地方,尾部采樣要求他們都落入相同的 Collector 節(jié)點,那么必然需要一層負載均衡架設(shè)在 Collector 之前,依照 Trace ID 進行轉(zhuǎn)發(fā)。這些負載均衡器可以是另一堆 OpenTelemetry 的 Collector,可以是以 Deployment 或 DaemonSet 形式部署的;也可以直接集成在 Client SDK 中。
以上就是 OpenTelemetry 提供的采樣、減少 Trace 數(shù)量的現(xiàn)成手段,你可能想知道實施他們需要多少成本,在下一小節(jié)中,我們將以一個 Demo 為例進行簡要分析。
3. 資源使用
為了直觀對比頭部采樣和尾部采樣的差異,我們準備了如下圖所示的系統(tǒng),包括:
-
可配置的 Trace 生成器; -
OpenTelemetry Collector; -
OTLP 協(xié)議的 Trace Receiver。
Trace 生成器自然是根據(jù)我們的需要生成 Span;Collector 用于處理或不處理數(shù)據(jù);這些數(shù)據(jù)最后會被送往 Receiver,進行統(tǒng)計和分析。每個組件的的資源使用量被限制在 4 CPU 和 8 GiB 內(nèi)存。
在生成器端,有 1% 和 100% 的概率進行上報,模擬頭部采樣的情況。當(dāng)生成器 100% 上報時,會由 Collector 暫存數(shù)據(jù) 5 秒,然后對包含錯誤和長耗時的 Trace 進行尾部采樣。另外還有一組對照組,Collector 不進行尾部采樣,直接將所有數(shù)據(jù)送往 Receiver。所有測試都會執(zhí)行多次,平均每次處理超過 40 萬個 Span,2 萬條 Trace。
在全流程耗時的比較中,可以看到不同采樣設(shè)計的耗時差異比較大,頭部采樣 1% 時平均為 20ms,頭部采樣 100% 時則上漲到了 260ms,在進一步加入尾部采樣后,耗時在 600ms 左右,在壓力進一步增大時缺少尾部采樣的延遲數(shù)據(jù)是因為其他方面的指標已經(jīng)超過臨界值,所以無法再記錄數(shù)據(jù)。結(jié)果很好理解,不同頭部采樣比例的耗時與每秒發(fā)送的 Span 數(shù)量是成正比的,正好是 10 倍的關(guān)系,而尾部采樣中,我們配置了針對錯誤和耗時的 2 個采樣策略,每個策略均會被執(zhí)行、遍歷所有 Span,因此需要一些額外的時間處理,數(shù)據(jù)才能抵達 Receiver。
在 CPU 使用率方面,1% 頭部采樣與另外兩種采樣方案的差別在 15-20 倍左右。當(dāng)數(shù)據(jù) 100% 上報時,有無進行尾部采樣對 CPU 使用率的影響相對小很多,只有 10% 的上漲??梢圆聹y,CPU 有更多的時間花在對上報數(shù)據(jù)的序列化、反序列化等等過程,而非采樣決策本身。
我們之前也說過尾部采樣需要更多的基礎(chǔ)設(shè)施資源,這在內(nèi)存上體現(xiàn)得比較明顯。隨著每秒上報數(shù)量的不斷增加,在每秒上報超過 20000 Span 之后,實施尾部采樣時所需的內(nèi)存就超過了 7 GiB,已經(jīng)到達組件的內(nèi)存上限了,所以也出現(xiàn)了延遲大幅上漲的情況。相比來說,頭部采樣 1% 時內(nèi)存壓力就非常小了,僅使用了 200 MiB。所以,為了搭建尾部采樣的體系,不少成本需要投資在內(nèi)存上,例如,測試里處于臨界的壓力時,同樣是 100% 上報,有尾部采樣需要使用無尾部采樣時接近 2 倍內(nèi)存資源。
那么投資了這些資源之后,能獲得什么呢?我們檢查 Receiver 收到的數(shù)據(jù),全量上報時平均收集到 22 萬個 Span,而 頭部采樣 1% 和尾部采樣只收集到了幾千個 Span,數(shù)量是大幅減少的。他們之間的量級差距影響的是存儲的成本,以及數(shù)據(jù)檢索的性能。尾部采樣與頭部采樣 1% 相比,在較為接近的數(shù)據(jù)量的前提下,能獲取更多對排查有價值的數(shù)據(jù),因為尾部采樣的結(jié)果都是包含 Error 或者總耗時超過 5 秒的 Trace。
以上就是對幾個測試場景的對比概述,基于這些結(jié)果,我們通常會推薦大家為每 8000 Span/s 的壓力準備 1 CPU 和 2 GiB 的內(nèi)存資源,這個恰好也是 OpenTelemetry 目前官方的推薦比例。但是這些數(shù)字受實際數(shù)據(jù)集的影響很大,Demo 更多是為了給大家基本的成本印象。
另外一個可能有助于控制成本的建議就是配置動態(tài)化、中心化,接受信息反饋。OpenTelemetry 和其他分布式追蹤平臺提供的 SDK 配置都是靜態(tài)的??捎^測性平臺作為基礎(chǔ)設(shè)施的提供方,需要有能力去實時控制用戶的配置,這樣有助于在發(fā)生問題的時候及時調(diào)整,防止單點拖垮整個平臺。其次,可觀測性平臺通常也具有數(shù)據(jù)分析的能力,分析之后的結(jié)果可以用于動態(tài)修正鏈路的采樣率,讓每一分成本都花在更有意義的地方。
4. 更有趣的想法?
前面簡要分析過一些現(xiàn)成的采樣策略,他們能覆蓋到生產(chǎn)中的很多場景,但是生產(chǎn)環(huán)境的數(shù)據(jù)的混沌程度遠比想象中高,依然會有非常多的邊緣 case 無法通過這些策略識別。這一小節(jié)針對這些邊緣情況介紹和討論一些未進入 OpenTelemetry 中的采樣策略。
以下圖為例,左側(cè)的服務(wù) A-E,如果依照順序進行調(diào)用,會得到右側(cè)的 Trace。
當(dāng)系統(tǒng)每天有數(shù)百萬請求,若他們都非常正常一致,通過頭部采樣即可篩選出其中少數(shù)幾個,作為問題排查的樣例。某一天,系統(tǒng)中出現(xiàn)了一些包含 Error 的調(diào)用,或者出現(xiàn)了很慢的調(diào)用,那需要進一步通過尾部采樣捕獲這些樣本。
隨著時間的推移,生產(chǎn)環(huán)境的數(shù)據(jù)越來越奇怪,如下圖中框住的例子,他們比起正常調(diào)用或是多了一個 Span,或者少了一個 Span,但是執(zhí)行速度很快,也不包含任何錯誤。這其實非常常見,例如某些邏輯分支會多執(zhí)行一條 SQL,或者多一次 RPC 調(diào)用。
作為開發(fā)者,在觀察業(yè)務(wù)健康情況的時候,自然是希望各種不同執(zhí)行路徑的數(shù)據(jù)都有一定量的樣本,那怎么去識別這些所謂的 “執(zhí)行路徑不同” 的調(diào)用呢?如果我們這里的數(shù)據(jù)不是 Trace,而是二叉樹,在一堆二叉樹中找出與眾不同的樹,一種暴力的方法是:把所有數(shù)據(jù)序列化,求 MD5,再找出 MD5 不同的目標,Trace 也可以做類似的處理。
在尾部采樣時,對原始的 Trace 數(shù)據(jù),按照時間和調(diào)用父子關(guān)系把 Trace 序列化,并通過摘要計算(如 MD5)得到一個新的結(jié)果,這個結(jié)果可以稱為 Type ID,它代表的是一類請求路徑相同的 Trace。如果在 Collector 用簡單的頻率計數(shù)統(tǒng)計每種類型的頻率,就可以讓所有類型的數(shù)據(jù)都留下樣本。這種方案目前在社區(qū)中已有相關(guān)提案。
但是如果我們考慮成本,這種方案依然是基于尾部采樣的,意味著整體處理流程中仍需要足夠多的數(shù)據(jù)上報,這是影響 CPU 和內(nèi)存用量的主要因素,其次才是遍歷和分析過程。那么,有沒有辦法進一步減少上報的數(shù)據(jù)量呢?這或許需要從上報的 Client SDK 端入手。
以往,Trace 上下文信息都是自頂向下單向傳播的,這讓上層 Span 的上報決策無法受下層 Span 的狀態(tài)影響,也就導(dǎo)致了頭部采樣決策信息有限的問題。因此我們很容易聯(lián)想到,如果 Trace 信息能夠自底向上反向傳遞會如何?
如下圖所示,服務(wù)在收到調(diào)用時,執(zhí)行自己的邏輯,在執(zhí)行過程中如果有任何感興趣的情況出現(xiàn)(例如有 Error、走入異常分支),可以不遵循頭部采樣決策,上報自身的 Span,并將這個情況通過 RPC Response 返回給調(diào)用方。調(diào)用方此時未結(jié)束流程,仍有機會根據(jù)響應(yīng)上報 Span。若每個服務(wù)都能接受 Response 的采樣提示,并返回給自己的調(diào)用方,即可形成反向采樣,或者回溯采樣(Retroactive Sampling)。
很顯然,這樣的采樣方式帶來幾個好處:
-
相比頭部采樣,它授予了所有應(yīng)用采樣決策的權(quán)利; -
相比需要收集大量數(shù)據(jù)的尾部采樣,它減少了發(fā)往應(yīng)用之外的數(shù)據(jù),降低流通在整個數(shù)據(jù)采集鏈路中的數(shù)據(jù)量,對整體資源成本控制非常有幫助。
但是有利即有弊,這種采樣無法收集到已經(jīng)結(jié)束的 Span,例如上圖中最左側(cè)的服務(wù),在全鏈路發(fā)生錯誤之前已經(jīng)完成了請求的處理和響應(yīng),因此不會受到任何采樣的通知,最終采樣的只是完整鏈路的一部分。其次,這種采樣高度依賴統(tǒng)一的通信框架或協(xié)議,可以想象,在基建不統(tǒng)一的情況下,想要整個企業(yè)都插裝、換用協(xié)議、修改響應(yīng)字段(/ Header / Metadata)是非常難推行的。
不過拋開少數(shù)障礙來看,比起頭部采樣和尾部采樣,回溯采樣的效果仍然是非常讓人期待的,是否有辦法繞過這些障礙將它落地呢?在探索實現(xiàn)的過程中,我們關(guān)注到了今年 NSDI 的一篇論文,解決的痛點和我們的目標很接近,下面來看看它的幾個實現(xiàn)要點。
如下圖所示,不同的服務(wù)位于不同的 Kubernetes 節(jié)點,每個節(jié)點上均有一個回溯采樣的 Agent,在節(jié)點之外會存在協(xié)調(diào)全局的 Collector。當(dāng)請求發(fā)生時,每個服務(wù)都將自己的 Span 送往 Agent,Span 中會包含采樣決策。正常情況下,Agent 僅與 Collector 交換少量 Metadata,如 Trace ID - Agent ID 關(guān)系,讓 Collector 知道某個 Trace 的相關(guān)數(shù)據(jù)會存在于哪些 Agent。
若后續(xù)流程正常,這些 Metadata 和原始數(shù)據(jù)很快會被丟棄。但若某個服務(wù)發(fā)現(xiàn)感興趣的事情,期望采樣時,Agnet 會將完整的數(shù)據(jù)及采樣要求發(fā)往 Collector,完整數(shù)據(jù)中會包含面包屑形式的調(diào)用路徑,如 A->B->C->D。Collector 收到這些信息之后,可以:
-
根據(jù)面包屑調(diào)用路徑尋找對應(yīng)服務(wù)的 Agnet 索要完整 Span 數(shù)據(jù); -
根據(jù) Trace ID 以及之前收集到的 Metadata 尋找對應(yīng) Agent 索要完整數(shù)據(jù)。
不管以何種方式,Collector 反向請求得到數(shù)據(jù)后,就可以完成回溯采樣,形成一條有價值的 Trace。
這種方式的回溯采樣可以節(jié)約節(jié)點之間的數(shù)據(jù)傳輸量,大部分時間里都只有 Metadata 被發(fā)往節(jié)點之外。當(dāng)且僅當(dāng)有問題發(fā)生時,Collector 才會索要完整數(shù)據(jù)。對比尾部采樣,它的缺點在于將原來需要 Collector 暫存的數(shù)據(jù)轉(zhuǎn)移到了應(yīng)用所在節(jié)點上,“嚴格控制 Agnet 資源的用量” 和 “讓 Agnet 盡可能留存數(shù)據(jù)” 是存在矛盾的,只能在其中作取舍。
但這種回溯采樣還是非常有競爭力的,因為它同時滿足兩個關(guān)鍵點:
-
讓每個應(yīng)用自行決策; -
上報數(shù)據(jù)量少。
在資源評估中,我們也可以看到它的潛力,在延遲、帶寬、邊緣場景覆蓋率上,和各種采樣方案相比都不落下風(fēng),更多的數(shù)據(jù)可以在論文中查閱。
5. 展望
采樣策略自 Dapper 論文發(fā)表以來,已經(jīng)發(fā)展了很多年,但是其實我們依然能看到現(xiàn)在主流的頭部采樣、尾部采樣方案各有優(yōu)缺點,甚至可以說短板仍然很明顯。而其他的新興的采樣方案,要么依賴特定基礎(chǔ)設(shè)施,要么還未經(jīng)過大規(guī)模的實踐驗證,所以還有很大的發(fā)展空間。
OpenTelemetry 作為 CNCF 托管項目中,活躍度僅次于 Kubernetes 的項目,有著非常良好的社區(qū)氛圍,在可觀測性方向可以做的事情也很多,取決于開發(fā)者們的想象力。期望有興趣的同學(xué)可以在社區(qū)中進一步交流采樣和其他各方面的想法。
6. 現(xiàn)場組圖
原文鏈接:https://jiekun.dev/posts/kubecon-2023-otel-sampling/,已獲作者授權(quán)發(fā)布。
