基於 eBPF 構建下一代智能可觀測系統

本文基於 KubeCon China 2023 分享整理我們今天分享的主題是基於 eBPF 構建下一代智能可觀測系統。

在開始之前呢,我先介紹一下我們自己。我是劉愷,花名是千陸,目前是阿里雲 ARMS K8s 監控子產品的負責人。這位是我的同事董善東博士,花名梵登,他是阿里雲 ARMS 產品 AIOps 領域的負責人。

K8s 中的可觀測挑戰

本次的分享主要分爲三部分內容。我們先來看第一部分,K8s 中的可觀測挑戰。

隨着雲原生、K8s、微服務等概念的興起,我們的應用發生了很多變化,比如微服務化、容器化等等。那麼一切都朝着統一的標準發展,這就帶給我們非常多的好處。比如極致的彈性、高效的運維、標準的運行時環境等等。但同時,K8s 也爲開發者帶來了很多的問題。

我們在公有云上收集了 1000 多個 K8s 中的工單,開發者將自己的基礎架構遷移到 K8s 之後,實際都遇到了哪些問題呢?

通過分析工單,我們可以分析出三大挑戰:

  • 第一個,K8s 基礎設施問題不容樂觀,從統計圖中可以看到網絡相關的問題佔比在 56% 以上,那麼開發者在遇到這些問題的時候,作爲可觀測系統,我們需要採集什麼數據才能回答異常的原因呢?
  • 第二個,由於應用很大的一部分複雜度下沉到了基礎設施中,那麼我們不僅僅需要採集應用層指標、還需要採集容器、網絡等觀測數據,那麼在 K8s 中,我們怎樣能夠優雅的採集這些數據呢?
  • 第三個,當我們採集到這麼多的數據之後,我們又該如何去使用這些數據來協助開發者排查問題?

K8s 中的數據採集方案

好的,讓我們帶着這三個問題,我們進入後面的內容:K8s 中的數據採集方案。

 

我們通常來說 K8s 應用我們可以分爲很多層。最上面一層是業務層,包括我們的一些前端頁面、小程序;應用層就是對標我們的後端應用;容器層可以有很多種運行時,比如 containerd,docker 等;基礎設施層包括 K8s。那麼到此就結束了嗎?其實並沒有。因爲 K8s 應用它的複雜度在不斷的下降,所以說往往我們需要繼續去深入。可能會涉及到網絡插件、Linux 內核,甚至是一些硬件驅動等。到此我們就可以回答之前提到的第一個問題了。在 K8s 中我們到底需要哪些數據?答案就是我們目之所及的所有數據。無論在任何一層出現故障,都可能會導致我們整體系統的異常。

接下來我們再回顧第二個問題,就是我們怎樣去採集這些數據。

其實目前來說,我們可觀測領域在每一層都有很多獨立的解決方案。比如在業務層,有撥測、前端監控、業務日誌分析等方案;在應用層,有 Opentelemetry、Jaeger 等 APM 系統;對於容器層和基礎設施層,有 Prometheus 搭配多種 Exporters 的觀測方案。但是當他繼續深入的時候,這些傳統的可觀測系統就很少涉及了。這就出現了觀測盲區的問題。另外呢,雖然在很多層都有各自的觀測方案,但是他們的數據缺乏關聯性。它們彼此都是相對獨立的,沒有一個統一的關聯維度能將各層的數據進行融合,因此存在數據孤島現象。

那麼有沒有辦法去解決這些問題呢?

答案就是 eBPF 技術。我先簡單介紹一下 eBPF 技術。eBPF 是一個運行在 Linux 內核中的虛擬機,它提供一套特殊的指令集並允許我們在不重新編譯內核、也不需要重啓應用的情況下加載自定義的邏輯。

 

該圖展示了使用 eBPF 的基本流程。我們需要寫一段 eBPF 代碼,然後通過編譯工具將其編譯成字節碼。然後我們可以將這段字節碼通過 bpf 系統調用,掛載到指定的掛載點。掛載點可以是系統調用,也可以是內核方法,甚至也可以是用戶空間的業務代碼。比如右下角,我們可以動態的將 eBPF 字節碼掛載到 hello_world 方法的進入和返回位置,並通過 eBPF 的指令集採集方法的運行時信息。

eBPF 技術具有三大特點:第一是無侵入,動態掛載,目標進程無需重啓;第二是高性能,eBPF 字節碼會被jit成機器碼後執行,效率非常高;第三是更加安全,它會運行在自己的沙箱中,不會導致目標進程崩潰,而且在加載時有嚴格的 verifier 驗證,會保證我們的這加的這段代碼是正常的,不會有死循環或者訪問非法內存等問題。

 

該表格對比了 eBPF 與傳統 apm 探針的差異。eBPF 技術在覆蓋性、侵入性、性能和安全上都具有明顯的優勢。

我們基於 eBPF 能做到能怎樣去採集數據呢?我們構建的第一個能力就是架構感知。架構感知可以自動的去分析集羣的應用架構、運行狀態以及網絡流向。

 

K8s 微服務之間的通信,從內核層面來看,就是 Linux 網絡協議棧的收發包過程。我以 udp 收包爲例,簡單介紹一下收包過程。從左上角開始,當數據包到達網卡時候,會觸發軟中斷,由 do_softirq 來處理軟中斷並進入到 net_rx_action 方法中,該方法會通過網卡驅動,主動的 Poll 網卡上的數據包。

拿到數據包後,會將數據包遞交到網絡協議棧中。也就是一個非常關鍵的方法 netif_receive_skb,很多抓包工具也都工作在這個位置。該方法會根據 Packet 的協議,比如 arp 還是 ip,tcp 還是 udp,經過不同的數據包處理邏輯之後,放到用戶進程的 skb 接收隊列中。我們的用戶進程,在調用 recvfrom syscall 來發起收包的時候,就會將 skb 隊列中的報文讀取到用戶空間。

 

有了這個理論基礎,再來回顧架構感知,它其實要做的就是探測網絡流向和網絡質量。所以,我們可以在 Linux 收發包路徑中,觀測一些重要的內核方法來實現這些目的。

該表格中我列舉了部分觀測點,比如 netif_receive_skb 和 dev_queue_xmit,可以統計收發數據包的次數、大小以及網絡流向,tcp_drop/tcp_retransmit_skb 等方法用來觀測丟包和重傳,可以衡量網絡質量。

 

該頁面就是架構感知在我們在我們產品中的一個落地頁。我們通過這張圖可以非常清晰的看到集羣的概覽、網絡流向,拓撲信息以及每個節點的狀態。從中我們可以看到上面這兩有兩個節點它是標黃了,它是變成了橙色。那就說明這兩個節點它的網絡狀態是有問題的。可能說它的丟包會增高了,或者說重傳增高了。但是它向上最終會反映到應用層是什麼樣的現象呢?

接下來就是我們構建的第二個能力,就是應用性能的一個觀測。其實剛纔的架構,我們的架構感知更多的是從傳輸層去衡量這個基礎設施的健康度的。其實在更多我們日常的開發過程中,我們會更希望去看到一些應用層的一些信息。比如說我們 HTTP 服務的一個 4XX 和 5XX 錯誤是不是增高了,然後它的處理時延是不是增高了等等這些。

那麼如何去觀測應用層呢?我們先來分析 K8s 應用之間的調用流程,通常我們的應用會調用三方 RPC 庫,再由三方庫調用系統調用,接下來到了內核中,會依次經過傳輸層、網絡層最後通過網卡驅動發送出去。

那麼對於傳統可觀測探針,通常都是在 RPC 庫的位置進行埋點,這一層會受到編程語言和通信框架的影響。

eBPF 同樣也可以選擇和傳統 APM 一樣的掛載點,在 RPC 庫中通過 uprobe 來採集數據。但是,由於 eBPF 的掛載點是非常多的,我們還可以將埋點位置下沉,比如系統調用層、IP 層、網卡驅動層,那麼我們就可以攔截網絡的收發字節流,然後再通過協議解析來分析網絡請求和響應,從而得到服務性能數據。這樣的好處就是,網絡字節流就與編程語言解耦了,我們就無需去適配每種 RPC 協議的不同語言、不同框架的實現了,大幅度的降低了開發的複雜度。

 

該表格對比了不同掛載位置的優劣。可以看到在系統調用這個位置埋點是相對更優的方案。因爲 syscall 具備進程信息,方便我們向上關聯一些容器的 metadata;而且 syscall 的掛載點穩定、開銷也很低。

 

那麼確定了掛載位置爲 syscall 之後,我再來簡單介紹一下我們分析服務性能的原理。

以 Linux 系統爲例,對於數據讀寫的 syscall 是可以枚舉的,比如 read、write、sendto、recvfrom 等,因此我們選擇將 eBPF 字節碼掛載到這些 syscall 中,就可以很直觀的獲取讀寫的應用層數據。舉個例子,比如我們在觀測一個 HTTP 服務,我們通過 read 埋點採集到了上面示例的接收數據,通過 write 埋點採集到了下面示例的發送數據。

接着,收發數據會分別進入到協議解析器中。協議解析器,會按照應用協議的標準,對數據進行解析。比如 HTTP 協議的規定了,請求行中,首行的格式是 Method、Path、Version,響應首行格式是 Version、StatusCode、Message,所以我們就可以提取出本次 HTTP 請求的 Method 是 Get、Path 是 /index.html,返回狀態碼是 200。此外,根據 read 和 write 觸發的時間,我們可以計算出服務端從收到請求到返回響應的處理時延。

提取到這些關鍵信息之後,經過一些聚合處理流程,我們就可以將服務數據導出到可觀測平臺。

 

這張圖片是應用性能觀測我們在我們產品的一個落地頁。我們可以通過這張圖片可以清晰的看到,我們在通過 eBPF 可以無侵入的去觀測到 HTTP Server 服務的一個黃金三指標,也就是請求數、錯誤數,還有平均延遲。在這裏面還可以看到一些問題,就是它的請求數發生了下降。它爲什麼發生下降?這可能是出現了一些異常,但是在這張圖中我們沒有辦法去找到他的原因是什麼。

所以說我們需要做第三件事情,就是多維數據的關聯。之前提到了,其實我們在針對 K8s 應用中的每一層都有對應的解決方案。但是他們都是沒有辦法去互相融合的。這裏說所說的融合就是他們缺乏統一的關聯維度。比如說我們針對容器的一些觀測數據,它通常都會包含 container ID 的維度。比如說 Container 的 CPU/Memory 使用率。然後針對於 K8s 資源的觀測,它們都會有 K8s 實體這樣一個維度,比如說是哪個 Pod、哪個 Deployment。那麼針對應用性能監控,就比如說像我們的一些傳統的一些 APM 平臺,它通常會包含 ServiceName 或者 trace ID 等維度。因此,它們是沒有統一的維度來進行關聯的,可以這樣就沒有辦法讓觀測數據進行融合。

如何去融合這些數據呢?eBPF 就是一個非常好的一個樞紐,因爲它運行在內核態,它可以採集到一些額外的信息。舉幾個例子,首先我們比如說針對一些進程類的掛載點。那麼 eBPF 在掛載的時候可以去採集到它的一些進程號,還有 PID namespace 等信息。這些信息再結合到容器的一個運行時,我們就可以很方便的去分析出來它的 container ID。這樣我們就可以完成與容器數據的一個融合。

再比如網絡相關的一些掛載點。我們可以通過 eBPF 採集到 skb 的三元組,也就是請求端的地址,對端的地址,還有它的網絡命名空間。我們通過這些地址信息,去反查 K8s 的 API Server,就可以決議出它屬於哪個 K8s 實體,這樣就完成了 K8s 資源的一個融合。

最後是應用性能監控的數據。通常來說 APM 系統會將 serviceName、trace ID 等 trace context 攜帶在應用層協議中的某些字段中。那麼我們在eBPF進行協議解析的時候,就可以額外解析這些數據,然後從而完成這一層的關聯。比如 HTTP 協議會通過 Headers 攜帶 trace context 信息。

 

好,接下來的這個數據就是我們的產品的一些落地。我可以看到這張圖中可以能看到架構感知與應用層數據的一個融合。

 

然後這張圖展示了 K8s 資源與容器觀測數據的一個融合。

以上的的內容中,我主要介紹了我們之前提到的兩大問題:我們需要採集哪些數據以及我們怎樣去採集這些數據。那麼,在得到這些數據之後,如何去使用它們進行故障定位呢?接下來由我的同事董善東博士繼續爲大家分享。

 

K8s 中的故障定位探索實踐

大家下午好,我是董善東。非常感謝千陸剛纔的介紹和分享。千陸向我們詳細介紹了 K8s 中可觀測方面的挑戰,並介紹了使用 eBPF 技術進行數據採集和關聯的方法。接下來,我很高興帶領大家一起探索一下在 K8s 中故障定位的實踐。

 

首先我們先來看一下在 ARMS K8s 監控中,人工排查故障的一個路徑。假設在 gateway 到 product service 存在着一個網絡慢調用的故障。這時候人工一般是怎麼發現和排查呢?首先我們會對於入口應用和關鍵應用都會配上告警,如應用的黃金三指標 RT(平均響應時間),Error Rate(錯誤率),請求量(QPS)指標的告警。當收到告警時,我們可以知道 gateway 應用出現異常。但是我們如何知道問題是哪裏導致的,是下游節點還是存在網絡問題呢?

我們首先要關注的是左邊這個拓撲圖中的 gateway 服務節點。可以發現在黃金指標中,RT 出現了突增,同時還有慢調用的指標。

沿着這個拓撲圖點擊 gateway 的下游節點,包括 cart service、 nacos 和 product service。發現,只有 product service 的黃金指標與入口的 gateway 指標相似,可以初步判斷故障的可能發生在 gateway 到 product service 的鏈路上。接下來,我們可以重複剛剛的動作,繼續分析 product service 的下游節點,可以發現其下游節點都是正常的。

最後,我們可以再確認從 gateway 到 product service 的網絡調用是否存在問題。點擊圖中的虛線(gateway 到 product service 的網絡調用邊),可以看到網絡調用的指標,如包重傳數和平均 RT。發現這兩個指標也出現了類似突增的現象。

綜上所述,根據拓撲圖的人工遊走和分析,可以初步判斷故障發生在 gateway 到 product service 的鏈路上,與網絡調用的兩個指標也存在關聯,大概率爲網絡問題。

 

人工排查的路徑對我們做智能根因定位有很大的參考價值。直覺思考是我們應該直接將上述流程實現自動化。

可以先檢查 Gateway 自身服務指標情況,發現黃金三指標中的 RT 出現異常突增,同時其資源指標,如 CPU、內存和磁盤使用率,都是正常的。這表明自己的服務指標存在問題。

緊接着我們通過拓撲關係可以獲取所對應的下游的節點。和檢查 Gateway 類似的思路:去檢查下游所有節點的服務黃金三指標和資源指標是否存在異常。發現下游節點中只有 product service 是異常的。clothservice 的錯誤率雖然也有一個負相關的下跌,但錯誤率下跌從業務上看並不是異常,結合業務語義可以排除掉。

最後,可以檢查服務的網絡調用是否存在問題。發現 gateway 調用 product service 的網絡指標存在相似的異常。到這裏,其實就把剛剛人工分析的流程實現了自動化。

更進一步,我們可以對日誌做分析。例如:我們可以通過日誌模板提取的方式,對錯誤服務節點日誌進行模式識別。可以發現:調 product service 接口出現了 time out 的錯誤日誌,且次數是 24 次。對關鍵錯誤日誌彙總後,將自動化分析過程和結果,以及錯誤日誌模板信息和次數,輸出爲檢查報告發送給運維專家。專家則根據這些信息實現智能輔助定位。

 

那故事到這裏就結束了嗎?

我們再回顧下剛剛整個流程,發現其實只是對整個系統做了一個異常掃描。我們把這些所有異常的信息匯聚在一起,提供給了運維專家。那我們可以是否可以再進一步的去直接推導出出根因結論呢?這是第一個思考。

第二個思考則是整套方法體系是在 K8s 監控的場景裏面應用的。我們是不是可以把它推廣到應用監控,前端監控、業務監控,甚至基礎設施監控。不同監控場景下用同一套算法模型來實現?

經過總結,我們給出的答案就是我們左邊的這三個步驟,分別是維度歸因、異常定界和 FTA(故障樹分析法)。我們來詳細的看一下這三個核心步驟。

 

首先是維度下鑽分析,那爲什麼要做維度下鑽分析呢?

我們剛剛從 gateway 知道它的下游是三個節點,然後我們需要去遍歷查看三個下游的。如果我們有了維度歸因,我們其實可以從直接從 gateway 的指標上可以分析出它的下游是 product service 出現問題。

看一下業界常見的維度下鑽的做法:整體指標出現了異常後,目標想知道異常部分是哪些維度導致的?由於整體指標是各個維度值 group by 組成。舉個例子,如整體錯誤率指標,其中維度包括了 service,region,host。每一個維度裏存在多種值,例如:region 下有北京、上海、杭州。

當發現整體的指標出現異常時,通過對每一個維度進行下鑽分析來確定異常是由哪個維度哪個值引起的。在各個維度分析的過程中,發現有些維度(如 region)每個值都異常,而有些維度(host,service)只有部分值存在異常。對於這種只有一部分值異常的維度,則是我們更需要關注的。通過單維度下鑽定位,我們可以知曉各個維度下哪些值存在異常。

接着,我們可以對異常維度和值進行組合,實現從單維度分析到組合維度分析。例如發現 host 的值和 service 的值存在交集,即異常值既是 host1,也是 service1。那麼則可以進行合併,從而確定根因爲組合維度:host1 & service1。

總結下,通過對單維度下鑽分析和組合維度分析,我們可以找到導致整體指標異常的特定維度和值,快速縮小了故障範圍。

 

而在微服務拓撲中,節點是存在多層調用關係的。有了單層的維度歸因,還需要知道朝着哪些方向去做維度歸因。

在 K8s 的環境中,整體的拓撲圖是比較複雜的,裏面可能有應用的拓撲,有資源的拓撲。那我們對於整個微服務的拓撲做了一個簡化。可以簡單認爲拓撲圖只有兩種類型的節點,一種叫服務節點,一種叫機器節點。簡化節點後,對應的關係則分爲:服務跟服務之間是調用和被調用關係,服務跟機器之間則是依賴和被依賴的關係。通過這樣一個梳理,我們將一個雜亂無章,看起來無從下手的的微服務拓撲圖,轉爲從任意一個節點開始,都可以做水平和垂直的下鑽分析。

以剛纔的故障爲例,假設 gateway 是目標異常服務。從它開始,可以去水平的做維度歸因,完成調用的下鑽分析。在垂直方向上,則主要是資源依賴的關係。沿着資源層做下鑽分析來檢查是否存在異常的機器 ip。最後一步則是看他們所依賴的這個網絡邊服務調服務的網絡通信是不是有問題。

通過對水平歸因、垂直歸因,以及對網絡通信的歸因,可以更有層次和邏輯的實現根因分析。結合逐層的服務拓撲分析,我們可以分析出整個鏈條中所有的異常節點。

 

最後一步則是這個故障樹分析法。我們定位到了這個異常節點或者異常網絡通信後,我們知道在哪些鏈路上發生了問題。但是具體是什麼類型的問題,如是網絡的慢調用問題?還是資源的 CPU 問題?

這裏通常的有幾種方案,一種是做一個有監督的模型,實現故障分類,但這種方案對於有標籤的數據集要求很高,以及模型的泛化性、可解釋性都較差。第二種則是故障樹分析法的方案。具體來看下:

首先需要將我們在做微服務排障和治理的經驗,整理總結爲一個 FTA 的樹。有了 FTA 樹後,在定位到具體的節點和邊後,可以進行類似決策樹的條件判定來實現故障的分類。例如已經定位到是 gateway 調 product service 節點這條鏈路存在問題。我們可以檢查 gateway 是否有下游服務依賴,是否有錯慢下游,以及它的網絡指標是不是存在問題。三個條件都滿足,那則說明這是一個網絡類型的問題。

通過這樣一個方式實現了更加有邏輯性和可解釋性的故障分類模型。

 

最後,我們將根因定位的三個核心步驟模型集成到了根因分析產品 Insights 中,如上圖所示,展示了 Insights 分析的異常事件列表和根因報告。Insights 提供了兩個核心頁面:事件異常事件列表頁面和根因分析報告頁面。目前 Insights 支持應用監控、Kubernetes 監控等多種場景。

而在具體的產品使用流程上,當應用接入 ARMS 後,Insights 會進行實時的智能異常檢測。檢測到的異常事件則在異常事件列表頁面中展示出來。點擊事件詳情即可查看詳細的根因分析報告,報告中包括了事件的現象描述、關鍵指標、異常根因概要、根因列表以及故障傳播鏈路圖。用戶還可以通過點擊根因列表中的具體異常,查看更多的異常分析結果,例如異常調用方法棧、MySQL 執行的慢 SQL 信息、異常信息等。

總結

好,到這裏我跟千陸的分享內容就結束了。簡單總結一下,我們今天介紹了 K8s 環境下可觀測的三大挑戰,然後講解了在 K8s 環境中做數據採集的方案:如何通過 eBPF 去實現是不同層級的數據採集,以及實現數據的關聯。最後則分享了在 K8s 環境中做智能根因定位的實踐探索。其核心的步驟是維度歸因、異常定界和 FTA 故障樹分析。

希望今天的內容能給大家帶來一些啓示和思考,謝謝大家的傾聽!

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章