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

用於協調高可用性應用程式的自訂 Kubernetes 排程器

只要您願意遵守規則,在 Kubernetes 上部署和搭乘飛機可能非常愉快。通常情況下,事情會「順利運作」。但是,如果有人有興趣攜帶必須存活的鱷魚旅行,或擴展必須保持可用的資料庫,情況可能會變得更加複雜。就此而言,甚至可能更容易建造自己的飛機或資料庫。撇開與爬蟲類動物旅行不談,擴展高度可用的有狀態系統絕非易事。

擴展任何系統都有兩個主要組成部分

  1. 新增或移除系統將在其上執行的基礎架構,以及
  2. 確保系統知道如何處理新增和移除的自身額外執行個體。

大多數無狀態系統(例如,網路伺服器)的建立不需要感知對等節點。有狀態系統(包括像 CockroachDB 這樣的資料庫)必須與其對等執行個體協調並重新分配資料。幸運的是,CockroachDB 處理資料重新分配和複寫。棘手的部分是在這些操作期間能夠容忍故障,方法是確保資料和執行個體分佈在多個故障網域(可用性區域)中。

Kubernetes 的職責之一是將「資源」(例如,磁碟或容器)放置到叢集中,並滿足它們請求的限制。例如:「我必須在可用性區域 A」(請參閱在多個區域中執行),或「我不能與另一個 Pod 放置在同一個節點上」(請參閱親和性和反親和性)。

作為這些限制的補充,Kubernetes 提供了 Statefulsets,為 Pod 提供身分識別以及「跟隨」這些已識別 Pod 的持久儲存。StatefulSet 中的身分識別由 Pod 名稱結尾處遞增的整數處理。重要的是要注意,這個整數必須始終是連續的:在 StatefulSet 中,如果 Pod 1 和 3 存在,則 Pod 2 也必須存在。

在底層,CockroachCloud 將 CockroachDB 的每個區域部署為其自身 Kubernetes 叢集中的 StatefulSet - 請參閱在單個 Kubernetes 叢集中協調 CockroachDB。在本文中,我將著眼於單個區域、一個 StatefulSet 和一個 Kubernetes 叢集,該叢集分佈在至少三個可用性區域中。

一個三節點 CockroachCloud 叢集看起來會像這樣

3-node, multi-zone cockroachdb cluster

當向叢集新增額外資源時,我們也會將它們分佈在各個區域中。為了獲得最快速的使用者體驗,我們同時新增所有 Kubernetes 節點,然後擴展 StatefulSet。

illustration of phases: adding Kubernetes nodes to the multi-zone cockroachdb cluster

請注意,反親和性無論 Pod 指派給 Kubernetes 節點的順序如何都會得到滿足。在範例中,Pod 0、1 和 2 分別指派給區域 A、B 和 C,但 Pod 3 和 4 以不同的順序指派給區域 B 和 A。反親和性仍然得到滿足,因為 Pod 仍然放置在不同的區域中。

若要從叢集中移除資源,我們以相反的順序執行這些操作。

我們首先縮減 StatefulSet,然後從叢集中移除任何缺少 CockroachDB Pod 的節點。

illustration of phases: scaling down pods in a multi-zone cockroachdb cluster in Kubernetes

現在,請記住,大小為 n 的 StatefulSet 中的 Pod 必須具有範圍 [0,n) 中的 ID。當將 StatefulSet 縮減 m 時,Kubernetes 會移除 m 個 Pod,從最高序數開始並朝最低序數移動,與新增順序相反。考慮以下叢集拓撲

illustration: cockroachdb cluster: 6 nodes distributed across 3 availability zones

當序數 5 到 3 從此叢集中移除時,statefulset 繼續在所有 3 個可用性區域中存在。

illustration: removing 3 nodes from a 6-node, 3-zone cockroachdb cluster

但是,Kubernetes 的排程器並未保證上述我們最初預期的放置。

我們對以下內容的綜合了解導致了這種誤解。

  • Kubernetes 將 Pod 自動分散到各個區域的能力
  • 具有 n 個副本的 StatefulSet 的行為,當部署 Pod 時,它們是依序從 {0..n-1} 建立的。有關更多詳細資訊,請參閱 StatefulSet

考慮以下拓撲

illustration: 6-node cockroachdb cluster distributed across 3 availability zones

這些 Pod 是依序建立的,並且分散在叢集中的所有可用性區域中。當序數 5 到 3 終止時,此叢集將失去在區域 C 中的存在!

illustration: terminating 3 nodes in 6-node cluster spread across 3 availability zones, where 2/2 nodes in the same availability zone are terminated, knocking out that AZ

更糟糕的是,當時我們的自動化會移除節點 A-2、B-2 和 C-2。將 CRDB-1 留在未排程狀態,因為持久卷僅在最初建立它們的區域中可用。

為了修正後一個問題,我們現在採用「搜尋和啄食」方法從叢集中移除機器。與其盲目地從叢集中移除 Kubernetes 節點,不如只移除沒有 CockroachDB Pod 的節點。更艱鉅的任務是駕馭 Kubernetes 排程器。

一次腦力激盪會議為我們留下了 3 個選項

1. 升級到 kubernetes 1.18 並使用 Pod Topology Spread Constraints

雖然這看起來可能是完美的解決方案,但在撰寫本文時,Kubernetes 1.18 在公有雲中最常見的兩種託管 Kubernetes 服務 EKS 和 GKE 上不可用。此外,pod topology spread constraints 在 1.18 中仍然是 Beta 功能,這意味著即使 v1.18 可用,也不能保證在託管叢集中可用。整個努力令人擔憂地讓人想起在 Internet Explorer 8 仍在流行時檢查 caniuse.com

2. 每個區域部署一個 statefulset。

與其擁有一個分佈在所有可用性區域中的 StatefulSet,不如針對每個區域使用節點親和性的單個 StatefulSet,這樣可以手動控制我們的區域拓撲。我們的團隊過去曾考慮過將其作為一個選項,這使其特別有吸引力。最終,我們決定放棄此選項,因為這將需要對我們的程式碼庫進行大規模修改,並且在現有客戶叢集上執行遷移也將是一項同樣龐大的工作。

3. 撰寫自訂 Kubernetes 排程器。

感謝來自 Kelsey Hightower 的範例和來自 Banzai Cloud 的部落格文章,我們決定一頭栽入並撰寫我們自己的自訂 Kubernetes 排程器。一旦我們的概念驗證部署並執行,我們很快發現 Kubernetes 的排程器也負責將持久卷映射到它排程的 Pod。 kubectl get events 的輸出使我們相信還有另一個系統在運作。在我們尋找負責儲存聲明映射的元件的過程中,我們發現了 kube-scheduler 外掛程式系統。我們的下一個 POC 是一個 Filter 外掛程式,它通過 Pod 序數確定適當的可用性區域,並且它完美地工作!

我們的 自訂排程器外掛程式 是開源的,並在我們所有的 CockroachCloud 叢集中執行。控制我們的 StatefulSet Pod 的排程方式使我們能夠自信地擴展。一旦 pod topology spread constraints 在 GKE 和 EKS 中可用,我們可能會考慮停用我們的外掛程式,但維護開銷一直出奇地低。更好的是:外掛程式的實作與我們的業務邏輯是正交的。部署它或停用它(就此而言)就像更改我們 StatefulSet 定義中的 schedulerName 欄位一樣簡單。


Chris Seto 是 Cockroach Labs 的軟體工程師,致力於 CockroachCloud(CockroachDB)的 Kubernetes 自動化。