
本文作者:蔡高揚,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 等。