本文作者:蔡高揚,Apache RocketMQ Committer, 阿里雲智慧技術專家。
背景
上圖左側為 RocketMQ 4.x版本叢集,屬於非切換架構。NameServer 作為無狀態節點可以部署多份,broker 叢集可以部署多組 broker ,每一組有一個 Broker Master 和多個 Broker Slave 。執行過程中如果某一組 master 故障,訊息傳送會路由到正常的 master 上,普通訊息可以從原 Broker Slave 繼續消費。
但非切換架構存在若干問題,比如定時訊息或事務訊息需要由 Master 進行二次投遞,如果 Master 故障,則需要人工介入將 Master 重新恢復。因此, RocketMQ 5.0 提出了自主切換架構。
自主切換架構新增了一個 Controller 模組,負責選主。當某個 Broker Master 故障,會選擇合適的 Broker Slave 提升為 Master,無需人工介入。
如果要在生產環境中部署一套叢集,需要規劃整個叢集的機器資源(比如哪些模組部署在哪些機器、哪些機器需要什麼樣的資源規格等),然後安裝 JDK 等依賴軟體。每個元件還需要準備有其配置檔案、啟動指令碼,再啟動各個元件。整個過程十分耗費人力,而且存在誤操作可能。
而在雲基礎設施上部署 RocketMQ 面臨更多挑戰:
首先,在雲基礎設施上建立不同規格的虛擬機器更為方便,因此在雲基礎設施上部署時,一個虛擬機器上往往只會部署一個模組,以實現資源隔離。然而,多節點部署也帶了更高的操作成本。而系統內部元件的當機、恢復、遷移等行為也需要進行支援。
從社群角度看,因為社群面向不同使用者,不同使用者往往會在不同雲基礎服務提供商上進行部署。但是從 IaaS 層設施看,不同雲廠商提供的介面並不統一。
為了解決上述問題,社群借鑑了面向介面程式設計的思路:不直接操作基礎設施,而是透過標準介面。而 Kubernetes 正是這樣一個容器編排的“標準介面”。於是,社群在解決 RocketMQ 在雲基礎設施的部署問題時,選擇基於 Kubernetes 進行部署,不同雲廠商負責從 Kubernetes 到具體雲 IaaS 層的排程:將有狀態 RocketMQ 叢集託管到 Kubernetes 叢集,充分利用 Kubernetes 提供的部署、升級、自愈相關能力,同時也能享受到 Kubernetes 社群的生態紅利。
Kubernetes 將 Pod、Deployment、Service、Ingress 等都封裝成抽象資源。在部署 RocketMQ 叢集時,只需將相關的 Kubernetes 資源編排好,而資源最終如何在雲基礎設施上進行編排則交由雲服務提供商來完成。
然而,直接基於 Kubernetes 原生資源進行部署也存在一些不足。
比如用 Kubernetes 部署時,經常需要操作 YAML 檔案,會涉及到 Deployment、StatefulSet、Service、Ingress 之類的資源需要大量配置,碰到複雜的資源定義就像“面向 YAML 程式設計”。
另外, Kubernetes 在支援有狀態應用的管理上也存在侷限。RocketMQ 叢集的狀態可歸納為兩塊。其一為叢集拓撲關係,包括 RocketMQ Broker 主備關係以及 RocketMQ 不同模組之間的相互依賴(比如 Broker 需要依賴 NameServer、Controller 等);其二為儲存狀態,包括 Cluster 名稱、 Broker 名稱、 Broker ID 、新擴容 Broker 後設資料等。
如何自動化管理以上狀態也是必須解決的問題。
於是社群成立了 RocketMQ Operator 專案,用於支撐 RocketMQ 叢集在雲基礎設施上的自動化運維與管理。
如上圖所示,最右側為 RocketMQ Operator 模組,實時與 Kubernetes API Server 進行互動。一方面會將 RocketMQ 叢集(包括 NameServer、Broker 等模組)正常部署,同時也會利用 RocketMQ Admin Tool 實時地維護叢集狀態,比如 NameServer 地址等。
一、Kubernetes Operator原理
Kubernetes Operator 是一種相對簡單靈活且程式設計友好的管理應用狀態的解決方案。其工作原理分為兩部分:一部分是利用自定義 API 資源( CRD )描述管理狀態應用,可以認為是一個面向使用者的介面,使用者描述需要部署或運維的資源;另一部分是自定義控制器,根據自定義資源物件的變化完成運維動作。
上圖中間的 Operator 控制迴圈可以視作自定義資源和 Kubernetes 資源之間的橋樑,它會不斷監聽自定義資源的狀態變化,根據狀態以及內部邏輯更新 Kubernetes 資源。同時也會根據 Kubernetes 資源的變化更新自定義資源的狀態。
自定義物件與 Pod 或 Deployment 類似,只是它並不是 Kubernetes 內部提供的物件,而是需要使用者自定義,並告知 Kubernetes。Custom Resource 指自定義API 資源的例項,Custom Resource Definition 指 CR 的定義。
如上圖,比如有一個型別為 Container 的 CRD,定義了三個屬性,分別是 Container 名稱、Container 對應的 image 和 Container 監聽的埠。下面的 CR 為具體自定義資源,其名稱為 nginx,image 為 DockerHub 上的最新版本,監聽80埠。與大家比較熟悉的資料庫表進行類比,Container 可以認為是一張表,具體定義(Spec)可以類比為每一列的定義,每一行資料即不同的自定義資源(CR)。
自定義資源提供 API 物件,真正負責將自定義資源轉換成 Kubernetes 內部資源的工作則由自定義控制器實現。
自定義控制器裡有 Informer 模組,會不斷地呼叫 Kubernetes API Server 的 listAndWatch 介面,以獲得所監聽 CR 的變化。CR 的變化事件和 CR 物件會被加入 Delta FIFO Queue,以 Key-Value 的方式儲存在本地儲存,並將 Key 加入 WorkQueue。最右側的控制迴圈會不斷地從 WorkQueue 取出相關 Key,根據 Key 從 Informer 的 Local Store 查詢對應的 CR 物件。接下來將物件定義的期望狀態與目前實際狀態進行比對,如果有差異,則執行內部邏輯。最終使得實際狀態與 CR 定義的期望狀態達到一致。
二、RocketMQ Operator設計
社群在實現 RocketMQ Operator 時,並非直接透過 controller-runtime 底層介面,而是依賴 Operator SDK 作為腳手架,幫助生成相關程式碼。開發人員在進行 RocketMQ Operator 開發時,只需要專注 RocketMQ 叢集本身的編排邏輯。
目前 RocketMQ Operator 的模組有 Name Service、Controller、Broker、TopicTransfer 和 Console,與 RocketMQ 模組基本一致。各模組透過不同 CRD 進行編排,其優點為架構、程式碼比較清晰,不同物件均有獨立的 CRD 定義和對應的 Controller 實現。缺點為缺少 RocketMQ 叢集維度的描述,程式碼實現、配置上可能存在重複。關於 CRD 的演進,歡迎社群同學結合各自的實踐提出建議。
Name Service 模組負責 NameServer 在 Kubernetes 叢集的運維管控操作,包括部署、擴縮容、提供 NameServer 叢集IP列表等。Broker 需要向 NameServer 註冊路由資訊,因此 NameServer 的地址極為重要,需要作為內部狀態實時地進行維護。當 NameServer 進行擴縮容時,Broker 叢集能夠自動感知 NameServer 地址的更新。
上圖為 NameServer CR 定義示例,主要屬性有:
- NameServer 叢集例項數
- NameServer 映象
- hostNetwork 可以設定為 true 或 false ,true 則表示透過 hostNetwork 提供 Node IP,供 Kubernetes 叢集外部的客戶端訪問。
Controller 模組定義了 Dledger controller 叢集。當 Broker 啟用自主切換模式時,需要維護 Controller 的訪問地址,其中採用了兩種機制:
第一種:Service。該方式會暴露 Controller 叢集的統一訪問地址供Broker 訪問。Broker 的訪問請求會路由到任意 Controller 節點,Controller 節點會返回 Controller 主節點的訪問地址,Broker 再與 Controller 主節點進行通訊。
第二種:Headless Service。該機制為每一個 Controller 的提供訪問地址,用於 Controller 間進行服務發現。在組建 Controller 叢集時,Controller 節點必須與具體的某個 Controller 節點進行通訊,因此必須為一對一關係。
Controller 的定義相對簡單,只需提供 Controller 數量(數量必須為奇數)、 Controller image ,其他與 NameServer 類似,比如資源、儲存的定義。
Broker 模組用於定義 Broker 叢集,維護 Broker 組數量以及每組的節點數量,同時負責處理 Broker 叢集的運維操作,包括部署、擴縮容以及後設資料複製。擴容時,如果新擴容的 Broker 沒有 Topic 等後設資料,使用者流量實際上不會路由到此 Broker。因此,Broker 模組還會負責 Broker 擴容後進行後設資料複製。
Broker 的定義相對複雜,包括:
- Broker組的數量。
- 每組的節點數。
- clusterMode定義了broker叢集模式,預設部署非切換叢集,設定為Controller則部署自主切換叢集。
- 其餘還包括資源、儲存定義等。
TopicTransfer 不是與 RocketMQ 直接對映的模組,它定義了 Topic 和 Consumer Group 後設資料遷移運維操作。使用 TopicTransfer 遷移後設資料時,首先在目標叢集建立指定的 Consumer group 和 Topic ;建立完成後禁寫原叢集,使得訊息不會傳送到原叢集;等待原叢集的訊息消費完成後,會將原叢集的後設資料進行清理。在此過程中,任何一步失敗均會進行回滾,確保後設資料正確遷移。
Console 模組負責部署 RocketMQ 控制檯以及維護其用到的 NameServer 地址,功能相對簡單,目前 RocketMQ Console 為無狀態節點,其定義方式與 Deployment 的相同。
接下來介紹幾個重要的控制器實現。
NameServer 控制器首先會判斷 Name Service 對應的 StatefulSet 是否存在,如果不存在,則建立或更新 StatefulSet,直至 NameServer 節點數與期望值相同。然後列出 NameServer 對應的 Pod 地址,並判斷地址是否發生了變化。如果是,則會將叢集中的 NameServer 地址進行更新,從而保證 Broker 或其他模組能夠獲取到正確的 NameServer 地址。
Dledger Controller 先建立 Headless Service 用作組建 Controller 叢集時服務發現的入口,接著判斷 Controller 節點數量是否與期望值一致,如果不一致,則建立 StatefulSet。建立 StatefulSet 時會自動為每個 Dledger Controller 分配 controllerDledgerSelfId。期望節點數目與實際節點數目一致後,才會暴露 Controller 的 Service 地址,供 Broker 訪問。
Broker 是有狀態應用,因此在擴容或縮容時需要 Broker Controller 進行額外動作。Broker Controller 會以 Broker 組為單位進行排程,每一個 broker 組有 1個 master 節點,並配置 0 到多個 slave 節點。當 Broker 進行擴容時,會新增一組 Broker 並按照使用者配置複製後設資料到新擴容的 Broker。
Broker 依賴 NameServer和 DLedger Controller,因此會等待 NameServer 、 DLedger Controller 啟動完成且兩者均正常提供服務後,才會進一步建立 Broker 對應的 StatefulSet ,直到實際節點數目與期望節點數一致。
如果出現擴容情況,則會根據在 CR 定義 的 ScalePodName 欄位對應 Pod 將後設資料(包括 Topic 、消費組)複製到新擴容的 Broker 。
三、快速部署RocketMQ叢集
首先,將 RocketMQ Operator 專案克隆到本地,解壓後執行 install-operator.sh 指令碼即可完成 RocketMQ Operator 的安裝。
第二步,配置 Name Service CR。Name Service CR 配置較為重要的欄位有兩個,其一為 size,即需要部署了多少個 NameServer 節點,其二為 hostNetwork ,預設 false ,此時客戶端只能在 Kubernetes 叢集內與 NameServer 進行通訊。如果Kubernetes 叢集外的客戶端需要訪問到 RocketMQ 叢集,需要將 hostNetwork 配為 true ,NameServer 的接入點需要配置為 NameServer 所在的 Node IP。
第三步,配置 Controller CR。注意 size 需要配置為奇數。Controller 的資料需要持久化儲存,可以利用雲服務提供商提供的 StorageClass,無需自行維護儲存。如果希望配置自己的儲存,GitHub 上 RocketMQ Operator 專案程式碼提供了配置 NFS 儲存的相關示例。
第四步,配置 Broker CR。示例中配置了兩組 Broker,每組有一個備節點,同時將 clusterMode 設定為 Controller,啟動自主切換架構叢集。
準備好以上三個模組的相關配置檔案之後,執行 kubectl apply 命令提交給 Kubernetes 叢集。其餘的部署、運維等動作均交由 RocketMQ Operator 自動完成。
成功部署後,可以透過 kubectl get po 命令檢視部署的Pod。可以看到部署了4個Broker節點、Controller 和 NameSever 節點各3個。
進入一個 Broker Pod,可以使用 clusterlist 命令檢視叢集狀態,可以看到叢集有兩組 Broker,每一組各有一主(BID=0)一備。
四、未來展望
RocketMQ Operator 將不斷完善,全面支援 RocketMQ 5.0,後續規劃主要包含以下工作:
① 映象統一。目前 RocketMQ Operator 內部也維護了一套 RocketMQ 映象,但是已經有 RocketMQ Docker 專案,沒必要再維護一套映象。因此,未來社群希望將對兩邊映象進行統一,降低管理成本。
② 叢集管理。RocketMQ 5.0 版本還提供了另外一種叢集部署方式—— BrokerContainer 對等部署。與 4.0 版本傳統的主備方式不同, BrokerContainer 會在程式中同時啟動一主一備,有兩個 BrokerContainer 中的 Broker 互為主備。某一個Container 的主節點故障時,則配對的 Container 中的備節點會進入 Slave Acting Master 狀態,負責代理主節點進行定時訊息或事務訊息等二級訊息的處理。
③ 支援部署更多 RocketMQ 元件,包括 RocketMQ Schema Registry、RocketMQ Proxy、RocketMQ Exporter 等。