Linkerd 創始人:eBPF 將影響服務網格的未來

eBPF(Extended Berkeley Packet Filter)是一種相當酷的技術,它爲雲原生世界提供了很多幫助。它已經成爲 Kubernetes 集羣裏 CNI 層的主流選擇,譬如說 Cilium 這樣的項目。Linkerd 這樣的服務網格部署了和 Cilium 一樣的 CNI 層,結合了 Linkerd 強大的 L7 處理和 Cilium 超快的 L3/4 處理。

但 eBPF 的網絡技術到底有多強大呢?比如,它是否允許我們完全替換 Linkerd 的 sidecar 代理,並且將所有的事務都在內核中運行?

本文,我們會盡量來評估這種可能性,特別是它對用戶的影響。我將描述 eBPF 是什麼以及它能做什麼和不能做什麼。我將闡述 sidecar 與其他模型的深入研究,並從運維和安全角度對它們進行對比。最後,我將闡述我的結論——我們 Linkerd 團隊認爲服務網格的未來與 eBPF 相關。

—__1 

自我介紹

我是威廉 · 摩根(William Morgan),Linkerd 創始人之一,Linkerd 是第一個服務網格,也是它定義了服務網格的各個術語。我同時還是 Buoyant 公司的首席執行官,該公司幫助世界各地的組織採用 Linkerd。沒準大家可能還記得我曾經發表的又長又枯燥的技術文章:《Service Mesh:每個軟件工程師都需要知道的這個被全世界高估的技術 [1]》和《Kubernetes 工程師關於 mTLS 的指南:相互認證所帶給你的愉悅和效益 [2]》。

我一直在強調 Linkerd,也許是我的偏見。但我也很高興以務實的態度從實施的層面看待它。Linkerd 的最終目標是爲我們的用戶提供最簡單的服務網格,Linkerd 如何實現這種簡化,其實是一個實施細節。例如,今天 Linkerd 使用 sidecar,但更早的 Linkerd 的 1.x 版本是作爲基於每個主機的代理部署的,這是出於操作性和安全的考慮才做出這樣的變化的。eBPF 被我們關注的是,它有可能讓我們進一步簡化 Linkerd,尤其是在操作性層面。

—__2 

eBPF 是什麼?

在我們進入服務網格細節之前,讓我們先從 eBPF 開始。這個席捲推特圈的熱門新技術到底是什麼?

eBPF 是 Linux 內核的一個設計,它允許應用程序在內核本身中執行某些類型的工作。eBPF 起源於網絡世界,但它不限於網絡,這正是它的亮點:在其他事務中,eBPF 解鎖了整個網絡的可觀察性,這在過去是不可能的,因爲它們會對性能產生影響。

假設你希望應用程序處理網絡數據包。你不能直接訪問宿主機的網絡緩衝區。這個緩衝區由內核管理,因爲內核必須保護它。例如,它必須確保一個進程不能讀取另一個進程的網絡數據包。相反,應用程序可以通過一個系統調用(syscall)來請求網絡數據包信息,這本質上是一個內核的 API 調用:你的應用程序調用 syscall,內核會檢查你是否有權限獲得你請求的數據包;如果有,就返還給你。

Syscall 是可移植的,你的代碼可以在非 Linux 機器上運行,但這會很慢。在現代網絡環境中,你的機器可能每秒處理數千萬個數據包,編寫基於 syscall 的代碼來處理每個數據包是不可能的。

來說說 eBPF。代碼不是在一個緊密的循環中調用 syscall,並在 “內核空間” 和“用戶空間”之間來回傳遞,而是直接將我們的代碼交給內核,讓它自己執行!瞧:現在沒有更多的 syscall 了,我們的應用程序應該可以全速運行了。(當然,正如下面將看到的,事情並沒有這麼簡單。)

eBPF 是最近一系列內核設計中的一個,比如 io_uring(Linkerd 在重度使用中),它改變了應用程序和內核交互的方式。(ScyllaDB 的 Glauber Costa 對此有一篇很棒的文章:《io_uring 和 eBPF 將如何徹底改變 Linux 編程 [3]》。)這些設計以截然不同的方式工作着:io_uring 使用一個特定的數據結構,允許應用程序和內核以一種安全的方式共享內存;eBPF 的工作原理是允許應用程序直接向內核提交代碼。但在這兩種情況下,目標都是通過越過 syscall 的方法來提高性能。

eBPF 雖然是一個巨大的進步,但並不是能解決一切問題的靈丹妙藥。不可能將任意一個應用程序都作爲 eBPF 運行。事實上,能使用 eBPF 做的事情也是非常有限的,請看下文。

—__3 

多租戶爭用是很困難的

在我們理解 eBPF 爲什麼如此有限之前,我們需要討論一下爲什麼內核本身如此有限。爲什麼像 syscall 這樣的東西會存在?爲什麼程序不能直接訪問網絡(或內存或磁盤)?

內核在一個多租戶爭用的世界中運行。多租戶意味着多個 “租戶”(例如人、帳戶、其他形式的參與者)來共享機器,每個人都運行自己的程序。爭用意味着這些租客不是 “朋友”。他們不應該訪問彼此的數據,或者相互干擾。內核需要在它們執行任意程序的時候提供強制約束的行爲。換句話說,內核需要隔離租戶。

這意味着內核不能真正信任任何程序。在任何時候,一個租戶的程序都可能試圖對另一個租戶的數據或程序做一些不好的事情。內核必須確保任何程序都不能停止或破壞另一個程序,或拒絕它的資源,亦或是干擾它的運行能力,或從內存、網絡或磁盤讀取數據,除非得到明確的許可需要這樣做。

這是一個非常關鍵的需求!世界上幾乎所有與軟件相關的安全保證最終都歸結於——內核在執行着此類保護措施。一個程序可以在未經允許的情況下讀取另一個程序的內存或網絡流量,這是一個數據泄露的行徑,也有可能更糟。一個可以寫入另一個程序的內存或網絡流量的程序是欺詐的行徑,甚至更糟。允許程序打破規則的內核調用是一個非常大的問題。而其中一種打破這些規則的方法就是訪問內核的內部狀態——如果你可以讀寫內核內存,那麼你就可以繞過這些規則。

這就是爲什麼應用程序和內核之間的每一次交互都要受到高度審查的原因。失敗的後果是極其嚴重的。內核開發人員們已經爲這個問題付出了日以繼夜的努力。

這也是容器如此強大的原因——它們採用相同的隔離策略,並將其應用於任意一個應用程序和依賴包。多虧了這個先進的內核策略,我們可以彼此隔離地運行容器,並充分利用內核處理多租戶爭用的能力。以前使用虛擬機實現這種隔離的方法很慢,而且成本很高。容器的神奇之處在於,它們以一種非常便宜的方式爲我們提供了(大部分)相同的保證。

我們所認爲的 “原生雲” 幾乎每個方面都依賴於這個隔離保證。

—__4 

eBPF 的侷限性

回到 eBPF。正如我們所討論的,eBPF 允許我們交出內核代碼,並說 “給,請在內核中運行它”。從內核安全的角度來看,我們知道這是一件令人難以置信的可怕的事情——它將繞過應用程序和內核之間的所有障礙(如 syscall),並將我們直接置於安全漏洞的區域。

因此,爲了確保安全,內核對所執行的代碼施加了一些非常重要的約束。在運行它們之前,所有 eBPF 程序必須通過一個驗證器,它檢查它們是否有不正常的行爲。如果驗證程序拒絕這個程序,內核就不會運行它。

程序的自動驗證是比較難實現的,而且驗證器也有可能因爲過於嚴格,導致出現報錯。因此,eBPF 的程序非常有限。例如,它們不能有阻攔其他程序的設定;它們不能有無界循環;它們不能超過預設的大小。同時也因爲複雜性也會受到限制——驗證器需要在所有可能的執行路徑上運行,如果它不能在某些限制內完成,或者不能證明每個循環都有退出機制,程序則不能通過。

有許多安全的程序完美的違反了這些限制。如果你想將其中一個程序作爲 eBPF 運行,那太糟糕了!你需要重寫程序以滿足驗證器。如果你是 eBPF 的粉絲,有個好消息是,隨着每個內核版本中的驗證器變得更智能,這些限制將逐漸放寬,同時也開始有一些創造性的方法可以繞過這些限制。

但總的來說,eBPF 項目所能做的事情非常有限。一些非常重要的事務,例如處理全範圍的 HTTP/2 流量,或協商 TLS 握手,這些都不能在 eBPF 中完成。比較好的情況下,eBPF 也只能完成這項工作的一小部分,剩下大部分還是需要通過調用用戶空間的應用程序來處理,因爲 eBPF 無法處理過於複雜的部分。

—__5 

eBPF 與服務網格對比

瞭解了 eBPF 的基礎知識之後,讓我們回到服務網格。

服務網格用來處理現代雲原生網絡的複雜性。以 Linkerd 爲例,啓動和終止雙方的 TLS;跨連接重試請求;爲提高性能,透明地在代理之間從 HTTP/1.x 升級到 HTTP/2;強制基於工作負載標識的訪問策略;跨 Kubernetes 集羣邊界發送流量;還有很多很多。

與大多數服務網格一樣,Linkerd 通過在每個應用程序的 Pod 中插入一個代理來實現這一點,該代理攔截並增加了與 Pod 之間的 TCP 通信。這些代理與應用程序容器一起在它們自己的容器中運行——“sidecar” 模型。Linkerd 的代理是超輕、超快、基於 Rust 的微代理,但也有其他辦法可行。

十年前,在集羣上部署數百或數千個代理並將它們與每個應用程序的每個實例連起來的想法在操作層面上看簡直是一場噩夢。但多虧了 Kubernetes,突然變得非常簡單。多虧了 Linkerd 巧妙的工程技術(如果我可以自誇的話),它也是可管理的:Linkerd 的微代理不需要調優,因爲它僅僅消耗極少的系統資源。

在這種情況下,eBPF 已經和服務網格配合很多年了。Kubernetes 給世界的禮物是一個具有清晰層間邊界的可組合平臺,eBPF 和服務網格之間的關係正好符合該模型:CNI 負責 L3/L4 流量,而服務網格負責 L7。

服務網格對於平臺的所有者來說是非常棒的。它在平臺層面提供了 mTLS、請求重試、“黃金指標” 等功能,這意味着他們不再需要依賴應用開發人員來構建這些功能。但代價當然是在各處添加大量代理。

所以回到我們最初的問題:我們能做得更好嗎?我們是否可以通過 “eBPF 服務網格” 獲得服務網格的功能,而不需要 sidecar?

—__6 

eBPF 服務網格仍然需要代理

現在有了我們對 eBPF 的理解,我們可以跳進這些渾濁的水域,探索可能潛伏在裏面的東西。

不幸的是,我們很快就觸底了:eBPF 的限制意味着 L7 流量代理仍然需要用戶空間網絡代理來完成繁重的工作。換句話說,任何 eBPF 服務網格仍然需要代理。

—__7 

基於每個主機的代理明顯比 sidecar 差

因此我們的 eBPF 服務網格需要代理。但它是否特別需要 sidecar 代理?如果我們使用基於每個主機的代理會怎麼樣?這會給我們一個沒有 sidecar 的、eBPF 支持的服務網格嗎?

不幸的是,我們在 Linkerd 1.x 中瞭解了太多關於爲什麼這不是一個好主意。同 sidecar 相比,基於主機的代理在操作、維護和安全性方面都更差。

在 sidecar 模型中,應用程序的單個實例的所有流量都通過它的 sidecar 代理處理。這允許代理作爲應用程序的一部分,這是理想的:

在基於主機的模型中,這些細節都不復存在。現在,代理不再是單一的應用程序實例,而是爲任意一個應用實例的一組有效隨機的 Pod 處理通信,同時這些 Pod 是由 Kubernetes 在某個主機上調度的。代理現在與應用程序完全解耦,這就引入了各種微妙和不那麼微妙的問題:

簡而言之,sidecar 保持了從轉移到容器中獲得的隔離保證:內核可以在容器級別執行多租戶的所有安全性和公平性考慮因素,而且一切都可以正常工作。基於主機的模型使我們完全脫離了這個世界,也給我們留下了多租戶爭用的所有問題。

當然,基於每個主機的代理也有一些優點。你可以將請求必須通過的代理數量從 sidecar 模型中的每跳兩個減少到每跳一個,這樣可以節省延遲。你可以使用數量更少、但更大的代理,如果你的代理具有較高的基礎架構成本,那麼這樣可能更有利於資源消耗。(Linkerd 1.x 就是一個很好的例子——很擅長擴大流量規模但不擅長縮小規模)。而且你的網絡架構圖也變得更簡單了,因爲你有更少的節點。

但是與你所遇到的運維性和安全問題相比,這些優點是次要的。而且,除了在網絡圖中減少節點之外,我們可以通過良好的工程設計來減少這些差異——確保我們的 sidercar 儘可能快、儘可能的小。

—__8 

我們能不能改進一下代理?

關於基於每個主機的代理,我們列出的一些問題涉及到我們的多租戶爭用。在 sidecar 領域,我們使用內核的現有解決方案通過容器來解決多租戶爭用。但在基於主機的代理模型中,我們不能這樣做,然而我們可以通過讓基於主機的代理本身能夠處理多租戶的爭用來解決這些問題嗎?例如,一個流行的代理是 Envoy。我們是否可以通過調整 Envoy 來處理多租戶爭用來解決每個主機代理的問題?

如果說是肯定的,是因爲 “它不會違背宇宙的物理定律”。但因爲“這將是一項巨大的工作,不會很好地利用任何人的時間”,所以答案應該是否定的。Envoy 不是爲多租戶爭用設計的,它將需要巨大的努力來改變這一點。有一個很長的有趣的 Twitter 帖子[4],探討了如果你想深入瞭解 Envoy 的細節,必須做很多事情:需要在項目中增加大量非常棘手的工作,以及必須不斷權衡“每個租戶只運行一個 Envoy” 的大量更改——也就是 sidecar。

即使你完成了這項工作,到最後,你仍然會遇到爆炸半徑和安全的問題。

—__9 

服務網格的未來

綜上所述,我們得出了一個結論:不管有沒有 eBPF,服務網格可預見的未來都是通過在用戶空間中運行的 sidecar 代理構建的。

Sidecar 並不是沒有問題,但它們是現有的最好的解決方案,可以在保持容器所提供的隔離性的同時,還要全面處理雲本地網絡的複雜性。說到 eBPF,它能從服務網格分擔工作,它應該通過使用 sidecar 代理來做到這一點,而不是基於主機的代理。

“讓現有的基於 sidecar 的方法更快,同時保留容器化的操作和安全優勢”與 “通過擺脫 sidecar 來解決服務網格的複雜性和性能” 兩者賣點不太一樣,但從用戶的角度來看,這是一種勝利。

eBPF 的功能最終會發展到不需要代理來處理服務網格提供的 L7 工作的全部範圍嗎?也許吧。內核將能夠通過除 eBPF 以外的機制來吸收工作範圍嗎?也許吧。這兩種可能性似乎都不是近在眼前,但如果有一天它們真的出現了,或許我們就可以告別 “sidercar 代理” 了。我們期待這種可能性。

與此同時,從 Linkerd 的角度來看,我們將繼續努力使我們的 sidecar 微型代理儘可能小、快、操作上可以忽略不計,包括將有意義工作交給 eBPF。我們的基本職責是維護我們的用戶和他們在 Linkerd 的運營體驗,通過這個鏡頭,我們必須始終權衡每一個設計和工程。

相關鏈接:

  1. https://buoyant.io/service-mesh-manifesto/

  2. https://buoyant.io/mtls-guide/

  3. https://www.scylladb.com/2020/05/05/how-io_uring-and-ebpf-will-revolutionize-programming-in-linux/

  4. https://twitter.com/mattklein123/status/1522925333053272065

分佈式實驗室 關注分佈式相關的開源項目和基礎架構,致力於分析並報道這些新技術是如何以及將會怎樣影響企業的軟件構建方式。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/B-75u1j-Pp10TYtdcyyySQ