本文已超過一年。較舊的文章可能包含過時的內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

在 Kubernetes 上實現 gRPC 負載平衡,不再令人困擾

許多新的 gRPC 使用者驚訝地發現,Kubernetes 的預設負載平衡通常無法與 gRPC 搭配使用。例如,當您採用 簡單的 gRPC Node.js 微服務應用程式 並將其部署在 Kubernetes 上時,就會發生以下情況

雖然此處顯示的 voting 服務有多個 Pod,但從 Kubernetes 的 CPU 圖表中可以清楚看出,只有一個 Pod 實際上在執行任何工作——因為只有一個 Pod 正在接收任何流量。為什麼?

在這篇網誌文章中,我們將描述這種情況發生的原因,以及如何透過將 gRPC 負載平衡新增到任何 Kubernetes 應用程式中來輕鬆解決此問題,方法是使用 Linkerd,這是一個 CNCF 服務網格和服務 Sidecar。

為什麼 gRPC 需要特殊的負載平衡?

首先,讓我們了解為什麼我們需要為 gRPC 做一些特殊的事情。

gRPC 正逐漸成為應用程式開發人員的常見選擇。與 JSON-over-HTTP 等替代協定相比,gRPC 可以提供一些顯著的優勢,包括大幅降低 (反) 序列化成本、自動類型檢查、標準化的 API 以及更少的 TCP 管理開銷。

但是,gRPC 也破壞了標準的連線層級負載平衡,包括 Kubernetes 提供的負載平衡。這是因為 gRPC 建構於 HTTP/2 之上,而 HTTP/2 的設計是具有單一長效 TCP 連線,所有請求都在該連線上進行多工處理——表示在任何時間點都可以在同一個連線上啟用多個請求。通常,這很棒,因為它可以減少連線管理的開銷。但是,這也表示(正如您可能想像的那樣)連線層級平衡不是很有用。一旦建立連線,就沒有更多平衡可以做了。所有請求都將釘選到單一目標 Pod,如下所示

為什麼這不會影響 HTTP/1.1?

此問題不會在 HTTP/1.1 中發生的原因是,HTTP/1.1 也具有長效連線的概念,因為 HTTP/1.1 具有多個自然導致 TCP 連線循環的功能。因此,連線層級平衡「已經足夠好」,而且對於大多數 HTTP/1.1 應用程式,我們不需要做更多的事情。

為了了解原因,讓我們更深入地了解 HTTP/1.1。與 HTTP/2 相比,HTTP/1.1 無法多工處理請求。每個 TCP 連線一次只能啟用一個 HTTP 請求。用戶端發出請求,例如 GET /foo,然後等待伺服器回應。在請求-回應週期發生時,無法在該連線上發出其他請求。

通常,我們希望同時發生大量請求。因此,為了同時發出 HTTP/1.1 請求,我們需要建立多個 HTTP/1.1 連線,並在所有連線上發出我們的請求。此外,長效 HTTP/1.1 連線通常會在一段時間後過期,並由用戶端(或伺服器)關閉。這兩個因素結合起來表示 HTTP/1.1 請求通常會在多個 TCP 連線之間循環,因此連線層級平衡可以運作。

那麼我們如何進行 gRPC 負載平衡?

現在回到 gRPC。由於我們無法在連線層級進行平衡,為了進行 gRPC 負載平衡,我們需要從連線平衡轉移到請求平衡。換句話說,我們需要開啟與每個目標的 HTTP/2 連線,並在這些連線之間平衡請求,如下所示

在網路術語中,這表示我們需要在 L5/L7 而不是 L3/L4 層級做出決策,也就是說,我們需要了解透過 TCP 連線傳送的協定。

我們如何完成這項任務?有幾種選擇。首先,我們的應用程式程式碼可以手動維護其自身的目標負載平衡池,我們可以將 gRPC 用戶端配置為使用此負載平衡池。這種方法為我們提供了最大的控制權,但在 Kubernetes 等環境中可能會非常複雜,因為當 Kubernetes 重新排程 Pod 時,池會隨著時間而變化。我們的應用程式將必須監看 Kubernetes API,並使自身與 Pod 保持同步。

或者,在 Kubernetes 中,我們可以將我們的應用程式部署為無頭服務。在這種情況下,Kubernetes 將在服務的 DNS 記錄中建立多個 A 記錄。如果我們的 gRPC 用戶端足夠先進,它可以自動從這些 DNS 記錄維護負載平衡池。但是這種方法將我們限制在某些 gRPC 用戶端,而且很少有可能只使用無頭服務。

最後,我們可以採取第三種方法:使用輕量級代理。

在 Kubernetes 上使用 Linkerd 進行 gRPC 負載平衡

Linkerd 是一個 CNCF 託管的 Kubernetes 服務網格。與我們的目的最相關的是,Linkerd 也充當服務 Sidecar,它可以應用於單一服務——即使沒有叢集範圍的權限。這表示當我們將 Linkerd 新增到我們的服務時,它會將一個微小、超快速的代理新增到每個 Pod,而這些代理會監看 Kubernetes API 並自動執行 gRPC 負載平衡。然後我們的部署看起來像這樣

使用 Linkerd 有幾個優勢。首先,它可以與以任何語言、任何 gRPC 用戶端以及任何部署模型(無論是否為無頭服務)編寫的服務搭配使用。由於 Linkerd 的代理是完全透明的,它們會自動偵測 HTTP/2 和 HTTP/1.x 並執行 L7 負載平衡,並且它們會將所有其他流量作為純 TCP 傳遞。這表示一切都會正常運作。

其次,Linkerd 的負載平衡非常精細。Linkerd 不僅會監看 Kubernetes API 並在 Pod 重新排程時自動更新負載平衡池,Linkerd 還使用回應延遲的指數加權移動平均來自動將請求傳送到最快的 Pod。如果一個 Pod 速度變慢,即使只是暫時的,Linkerd 也會將流量從該 Pod 移開。這可以減少端到端尾部延遲。

最後,Linkerd 基於 Rust 的代理非常快速且體積小巧。它們引入了 <1 毫秒的 p99 延遲,並且每個 Pod 需要 <10mb 的 RSS,這表示對系統效能的影響將微不足道。

60 秒內的 gRPC 負載平衡

Linkerd 非常容易嘗試。只需按照 Linkerd 入門說明中的步驟操作——在您的筆記型電腦上安裝 CLI,在您的叢集上安裝控制平面,然後「網格化」您的服務(將代理注入到每個 Pod 中)。您將立即在您的服務上執行 Linkerd,並且應該立即看到正確的 gRPC 平衡。

讓我們再次看看我們的範例 voting 服務,這次是在安裝 Linkerd 之後

正如我們所看到的,所有 Pod 的 CPU 圖表都是活動的,表示所有 Pod 現在都在接收流量——而無需更改一行程式碼。瞧,gRPC 負載平衡就像變魔術一樣!

Linkerd 還為我們提供了內建的流量層級儀表板,因此我們甚至不需要再從 CPU 圖表中猜測發生了什麼事。這是 Linkerd 圖表,顯示每個 Pod 的成功率、請求量和延遲百分位數

我們可以看見每個 Pod 大約每秒收到 5 個請求。我們也可以看到,雖然我們已經解決了負載平衡問題,但我們仍然需要在這個服務的成功率方面做一些工作。(演示應用程式是使用故意的失敗建構的——作為給讀者的練習,看看您是否可以使用 Linkerd 儀表板找出原因!)

總結

如果您有興趣以非常簡單的方式將 gRPC 負載平衡新增到您的 Kubernetes 服務中,無論它是以何種語言編寫、您使用的是哪個 gRPC 用戶端,或是如何部署的,您都可以使用 Linkerd 在幾個命令中新增 gRPC 負載平衡。

Linkerd 還有很多其他功能,包括安全性、可靠性以及偵錯和診斷功能,但這些是未來網誌文章的主題。

想了解更多資訊嗎?我們很樂意邀請您加入我們快速成長的社群!Linkerd 是一個 CNCF 專案,託管在 GitHub 上,並且在 SlackTwitter郵件列表 上擁有蓬勃發展的社群。快來加入樂趣吧!