Kubernetes 中的負載平衡和擴展長期連接

Kubernetes 中的負載平衡和擴展長期連接
本文將幫助您了解負載平衡在 Kubernetes 中的工作原理、擴展長期連接時會發生什麼,以及為什麼在使用 HTTP/2、gRPC、RSockets、AMQP 或其他長期協議時應考慮客戶端平衡。 

關於 Kubernetes 中流量如何重新分配的一些信息 

Kubernetes 為部署應用程式提供了兩個方便的抽象:服務和部署。

部署描述了在任何給定時間應用程式的運作方式和數量。 每個應用程式都部署為 Pod 並分配一個 IP 位址。

服務在功能上與負載平衡器類似。 它們旨在跨多個 Pod 分配流量。

讓我們看看它是什麼樣子.

  1. 在下圖中,您可以看到同一應用程式的三個實例和一個負載平衡器:

    Kubernetes 中的負載平衡和擴展長期連接

  2. 負載平衡器稱為服務並分配 IP 位址。 任何傳入請求都會重定向到其中一個 Pod:

    Kubernetes 中的負載平衡和擴展長期連接

  3. 部署場景決定了應用程式的實例數量。 您幾乎永遠不需要直接在以下位置展開:

    Kubernetes 中的負載平衡和擴展長期連接

  4. 每個 Pod 都分配有自己的 IP 位址:

    Kubernetes 中的負載平衡和擴展長期連接

將服務視為 IP 位址的集合很有用。 每次存取服務時,都會從清單中選擇一個 IP 位址並用作目標位址。

看起來像這樣.

  1. 服務收到一個curl 10.96.45.152請求:

    Kubernetes 中的負載平衡和擴展長期連接

  2. 該服務選擇三個 pod 位址之一作為目標:

    Kubernetes 中的負載平衡和擴展長期連接

  3. 流量被重定向到特定的 Pod:

    Kubernetes 中的負載平衡和擴展長期連接

如果您的應用程式由前端和後端組成,那麼您將為每個應用程式提供一個服務和一個部署。

當前端向後端發出請求時,它不需要確切知道後端服務了多少個 Pod:可能有 XNUMX 個、XNUMX 個或 XNUMX 個。

此外,前端也不知道為後端提供服務的 Pod 的位址。

當前端向後端發出請求時,使用後端服務的IP位址,該IP位址不會改變。

下面是它的外觀.

  1. 1下請求內部後端組件。 它不是為後端選擇特定的後端,而是向服務發出請求:

    Kubernetes 中的負載平衡和擴展長期連接

  2. 此服務選擇後端 Pod 之一作為目標位址:

    Kubernetes 中的負載平衡和擴展長期連接

  3. 流量從 Pod 1 流向 Pod 5,由服務選擇:

    Kubernetes 中的負載平衡和擴展長期連接

  4. Under 1 並不確切知道有多少像 under 5 這樣的 pod 隱藏在服務後面:

    Kubernetes 中的負載平衡和擴展長期連接

但該服務到底如何分發請求呢? 好像用了循環平衡? 讓我們弄清楚一下。 

Kubernetes 服務中的平衡

Kubernetes 服務不存在。 沒有為服務分配 IP 位址和連接埠的進程。

您可以透過登入叢集中的任何節點並執行 netstat -ntlp 命令來驗證這一點。

您甚至無法找到指派給該服務的 IP 位址。

服務的IP位址位於控制層,在控制器中,並記錄在資料庫-etcd。 另一個元件 - kube-proxy 使用相同的位址。
Kube-proxy 接收所有服務的 IP 位址列表,並在叢集中的每個節點上產生一組 iptables 規則。

這些規則規定:“如果我們看到服務的 IP 位址,我們需要修改請求的目標位址並將其發送到其中一個 Pod。”

服務 IP 位址僅用作入口點,偵聽該 IP 位址和連接埠的任何進程都不會提供服務。

讓我們看看這個

  1. 考慮一個包含三個節點的叢集。 每個節點都有 pod:

    Kubernetes 中的負載平衡和擴展長期連接

  2. 塗成米色的捆綁豆莢是服務的一部分。 由於該服務不作為進程存在,因此顯示為灰色:

    Kubernetes 中的負載平衡和擴展長期連接

  3. 第一個 Pod 請求服務,並且必須轉到關聯的 Pod 之一:

    Kubernetes 中的負載平衡和擴展長期連接

  4. 但服務不存在,進程不存在。 它是如何運作的?

    Kubernetes 中的負載平衡和擴展長期連接

  5. 在請求離開節點之前,它會經過 iptables 規則:

    Kubernetes 中的負載平衡和擴展長期連接

  6. iptables 規則知道該服務不存在,並將其 IP 位址替換為與該服務關聯的 Pod 的 IP 位址之一:

    Kubernetes 中的負載平衡和擴展長期連接

  7. 此請求收到一個有效的IP位址作為目的位址並正常處理:

    Kubernetes 中的負載平衡和擴展長期連接

  8. 根據網路拓撲,請求最終到達 pod:

    Kubernetes 中的負載平衡和擴展長期連接

iptables 可以負載平衡嗎?

不,iptables 用於過濾,並不是為了平衡而設計的。

但是,可以編寫一組像這樣工作的規則 虛擬平衡器.

這正是 Kubernetes 中實現的。

如果你有三個 pod,kube-proxy 會寫以下規則:

  1. 以33%的機率選擇第一個子,否則進入下一條規則。
  2. 選擇第二個的機率為 50%,否則進入下一條規則。
  3. 選擇下面的第三個。

該系統導致每個 pod 被選中的機率為 33%。

Kubernetes 中的負載平衡和擴展長期連接

並且不能保證 Pod 2 會在 Pod 1 之後被選取。

注意:iptables使用隨機分佈的統計模組。 因此,平衡演算法是基於隨機選擇的。

現在您了解了服務的工作原理,讓我們來看看更有趣的服務場景。

Kubernetes 中的長連線預設不會擴展

從前端到後端的每個 HTTP 請求都由單獨的 TCP 連線提供服務,該連線可以開啟和關閉。

如果前端每秒向後端發送 100 個請求,則開啟和關閉 100 個不同的 TCP 連線。

您可以透過開啟一個 TCP 連線並將其用於所有後續 HTTP 請求來減少請求處理時間和負載。

HTTP 協定有一個稱為 HTTP keep-alive 或連線重複使用的功能。 在這種情況下,單一 TCP 連線用於傳送和接收多個 HTTP 請求和回應:

Kubernetes 中的負載平衡和擴展長期連接

預設不啟用此功能:伺服器和用戶端都必須進行相應配置。

設定本身很簡單,並且適合大多數程式語言和環境。

以下是一些不同語言範例的連結:

如果我們在 Kubernetes 服務中使用 keep-alive 會發生什麼事?
假設前端和後端都支援 keep-alive。

我們有一份前端副本和三份後端副本。 前端發出第一個請求並開啟到後端的 TCP 連線。 請求到達服務,選擇後端 Pod 之一作為目標位址。 後端發送回應,前端接收回應。

與 TCP 連線在收到回應後關閉的通常情況不同,它現在保持開啟以接受進一步的 HTTP 請求。

如果前端向後端發送更多請求會發生什麼?

為了轉發這些請求,將使用開放的 TCP 連接,所有請求都將發送到第一個請求所在的相同後端。

iptables 不應該重新分配流量嗎?

不是在這種情況下。

建立 TCP 連線時,它會通過 iptables 規則,選擇流量將前往的特定後端。

由於所有後續請求都位於已開啟的 TCP 連線上,因此不再呼叫 iptables 規則。

讓我們看看它是什麼樣子.

  1. 第一個 pod 向服務發送請求:

    Kubernetes 中的負載平衡和擴展長期連接

  2. 你已經知道接下來會發生什麼事。 該服務不存在,但有 iptables 規則將處理該請求:

    Kubernetes 中的負載平衡和擴展長期連接

  3. 將選擇後端 Pod 之一作為目標位址:

    Kubernetes 中的負載平衡和擴展長期連接

  4. 請求到達 Pod。 此時,兩個 pod 之間將建立持久的 TCP 連線:

    Kubernetes 中的負載平衡和擴展長期連接

  5. 來自第一個 pod 的任何後續請求都將透過已建立的連線:

    Kubernetes 中的負載平衡和擴展長期連接

結果是更快的回應時間和更高的吞吐量,但您失去了擴展後端的能力。

即使後端有兩個 pod,且連線持續,流量也將始終流向其中之一。

這可以解決嗎?

由於 Kubernetes 不知道如何平衡持久連接,所以這個任務就落在了你的身上。

服務是稱為端點的 IP 位址和連接埠的集合。

您的應用程式可以從服務獲取端點列表,並決定如何在它們之間分發請求。 您可以開啟與每個 Pod 的持久連接,並使用循環法平衡這些連接之間的請求。

或申請更多 複雜的平衡演算法.

負責平衡的客戶端程式碼應遵循以下邏輯:

  1. 從服務取得端點清單。
  2. 為每個端點開啟持久連線。
  3. 當需要發出請求時,使用開啟的連線之一。
  4. 定期更新端點列表,建立新端點或在列表變更時關閉舊的持久連線。

這就是它的樣子.

  1. 您可以在客戶端平衡請求,而不是第一個 pod 將請求傳送到服務:

    Kubernetes 中的負載平衡和擴展長期連接

  2. 您需要編寫程式碼來詢問哪些 pod 是服務的一部分:

    Kubernetes 中的負載平衡和擴展長期連接

  3. 獲得清單後,將其保存在客戶端並使用它連接到 Pod:

    Kubernetes 中的負載平衡和擴展長期連接

  4. 您負責負載平衡演算法:

    Kubernetes 中的負載平衡和擴展長期連接

現在問題來了:這個問題只適用於HTTP keep-alive嗎?

客戶端負載平衡

HTTP 不是唯一可以使用持久 TCP 連線的協定。

如果您的應用程式使用資料庫,則每次您需要發出請求或從資料庫檢索文件時,不會開啟 TCP 連線。 

相反,會開啟並使用與資料庫的持久 TCP 連線。

如果您的資料庫部署在 Kubernetes 上,並且存取作為服務提供,那麼您將遇到上一節中描述的相同問題。

一個資料庫副本將比其他資料庫副本負載更多。 Kube-proxy 和 Kubernetes 不會幫助平衡連線。 您必須注意平衡對資料庫的查詢。

根據您用來連接資料庫的庫,您可能有不同的選項來解決此問題。

以下是從 Node.js 存取 MySQL 資料庫叢集的範例:

var mysql = require('mysql');
var poolCluster = mysql.createPoolCluster();

var endpoints = /* retrieve endpoints from the Service */

for (var [index, endpoint] of endpoints) {
  poolCluster.add(`mysql-replica-${index}`, endpoint);
}

// Make queries to the clustered MySQL database

還有許多其他協定使用持久 TCP 連線:

  • WebSocket 和安全性 WebSocket
  • HTTP / 2
  • 遠程過程調用
  • RSockets
  • 空氣質量計劃

您應該已經熟悉其中的大多數協議。

但如果這些協議如此受歡迎,為什麼沒有標準化的平衡解決方案? 為什麼客戶端邏輯需要改變? 有原生的 Kubernetes 解決方案嗎?

Kube-proxy 和 iptables 旨在涵蓋部署到 Kubernetes 時的最常見用例。 這是為了方便。

如果您使用的是公開 REST API 的 Web 服務,那麼您很幸運 - 在這種情況下,不使用持久 TCP 連接,您可以使用任何 Kubernetes 服務。

但是一旦開始使用持久 TCP 連接,您就必須弄清楚如何在後端之間均勻分配負載。 Kubernetes 不包含針對這種情況的現成解決方案。

然而,當然有一些選擇可以幫助。

平衡 Kubernetes 中的長期連接

Kubernetes 中有四種類型的服務:

  1. 集群IP
  2. 節點端口
  3. 負載平衡器
  4. 無頭

前三個服務基於虛擬 IP 位址運行,kube-proxy 使用該位址來建立 iptables 規則。 但所有服務的根本基礎是無頭服務。

無頭服務沒有任何與其關聯的 IP 位址,僅提供一種用於檢索與其關聯的 Pod(端點)的 IP 位址和連接埠清單的機制。

所有服務均基於無頭服務。

ClusterIP 服務是一個無頭服務,並且增加了一些內容: 

  1. 管理層為其分配一個IP位址。
  2. Kube-proxy 產生必要的 iptables 規則。

這樣您就可以忽略 kube-proxy 並直接使用從無頭服務取得的端點清單來平衡您的應用程式的負載。

但是我們如何為叢集中部署的所有應用程式添加類似的邏輯呢?

如果您的應用程式已經部署,那麼此任務似乎是不可能的。 然而,還有一個替代選擇。

服務網格將為您提供協助

您可能已經注意到客戶端負載平衡策略是相當標準的。

當應用程式啟動時,它:

  1. 從服務取得 IP 位址清單。
  2. 開啟並維護連線池。
  3. 透過新增或刪除端點定期更新池。

一旦應用程式想要發出請求,它:

  1. 使用某種邏輯(例如循環)選擇可用連線。
  2. 執行請求。

這些步驟適用於 WebSocket、gRPC 和 AMQP 連線。

您可以將此邏輯分離到一個單獨的庫中並在您的應用程式中使用它。

但是,您可以使用 Istio 或 Linkerd 等服務網格來代替。

Service Mesh 透過以下流程增強您的應用程式:

  1. 自動搜尋服務IP位址。
  2. 測試 WebSockets 和 gRPC 等連線。
  3. 使用正確的協定平衡請求。

Service Mesh 有助於管理叢集內的流量,但它非常消耗資源。 其他選項包括使用 Netflix Ribbon 等第三方程式庫或 Envoy 等可程式代理程式。

如果忽略平衡問題會發生什麼?

您可以選擇不使用負載平衡,但仍然不會注意到任何變化。 讓我們來看幾個工作場景。

如果您的客戶端多於伺服器,那麼這不是一個大問題。

假設有五個客戶端連接到兩個伺服器。 即使沒有平衡,兩台伺服器都會被使用:

Kubernetes 中的負載平衡和擴展長期連接

連線可能不均勻分佈:可能有四個客戶端連接到同一台伺服器,但很有可能兩台伺服器都會被使用。

更成問題的是相反的情況。

如果您的用戶端較少且伺服器較多,您的資源可能未充分利用,並且會出現潛在的瓶頸。

假設有兩個客戶端和五個伺服器。 在最好的情況下,五台伺服器中的兩台將有兩個永久連接。

其餘伺服器將處於空閒狀態:

Kubernetes 中的負載平衡和擴展長期連接

如果這兩台伺服器無法處理客戶端請求,則水平擴展將無濟於事。

結論

Kubernetes 服務旨在大多數標準 Web 應用程式場景中運作。

但是,一旦您開始使用使用持久 TCP 連線的應用程式協定(例如資料庫、gRPC 或 WebSockets),服務就不再適合。 Kubernetes 不提供平衡持久 TCP 連線的內部機制。

這意味著您在編寫應用程式時必須考慮客戶端平衡。

團隊準備的翻譯 Mail.ru 的 Kubernetes aaS.

關於該主題還可以閱讀什麼:

  1. Kubernetes 中的三個級別的自動縮放以及如何有效地使用它們
  2. 本著盜版精神的 Kubernetes 提供了實作模板.
  3. 我們關於數位轉型的 Telegram 頻道.

來源: www.habr.com

添加評論