溫故知新-EverDB容器化之旅
今天給大家帶來“你好,我是EverDB!”系列文章的第二篇—容器化之旅。
容器天生的部署快速、輕量、便于調度特性非常適合故障場景的模擬,因此EverDB容器化是我們的第一步工作。本篇文章將介紹基于k8s的EverDB容器化實現方案。
為什么選擇k8s
k8s全稱Kubernetes,是一個開源的、基于容器技術的分布式架構解決方案,提供了容器自動化部署、管理、編排,伸縮等能力,使應用容器化更加簡單高效。同時,k8s平臺在故障轉移,資源調度、隔離,負載均衡方面的特性,也更契合EverDB自身架構特點和測試、管理需求,因此EverDB容器化方案選定基于k8s來實現。
部署到k8s的技術路線
Helm工具
Helm是k8s的包管理器,類似我們在Ubuntu中使用的apt、Centos中使用的yum一樣,能快速查找、下載和安裝軟件包。在Helm里面,最重要的應用包叫Charts,這是一個應用的定義描述,里面包括了這個應用的一些元數據,以及該應用的k8s資源定義模板及其配置。在擁有足夠完善的Charts情況下,只需要簡單的install就可以快速部署服務。
Operator思路
Operator是用k8s原生方式去管理應用的一種實現思路,通過k8s擴展API,使用CRD自定義資源對象,并實現對應的控制器來實現對應用的部署及管理。
Helm和Operator兩種方案在k8s應用管理上各有優勢,前者的優勢在于將資源模板化,方便共享,并在不同的配置中復用;后者則更加針對復雜應用的自動化管理。此次部署到k8s平臺,考慮到實現成本和當前需求,決定基于Helm來實現EverDB容器化方案。
整體方案
在k8s平臺上,應用可分為有狀態和無狀態兩種。EverDB的數據節點MySQL、調度節點Grid、配置節點ZooKeeper均需要保持運行狀態參數并對外提供穩定服務,數據節點和配置節點還需要將數據和配置信息持久化到存儲器,因此部署到k8s上均屬于StatefulSet類型的有狀態應用。
EverDB架構圖
EverDB的三個組件設計需要配置的k8s資源如下表所示:
Pod控制器類型 | ConfigMap | NodePort | 無頭服務 | 持久化存儲 | 輔助容器 | |
Mysql | StatefulSet | ● | ● | ● | ● | ● |
Zookeeper | StatefulSet | ● | ○ | ● | ● | ○ |
dbscale | StatefulSet | ● | ● | ● | ○ | ○ |
MySQL作為EverDB的底層數據存儲引擎,在部署至k8s時,除需具備數據實例配置、實例初始化、數據持久化存儲,對外訪問服務等功能外,還要有監控、備份等輔助容器;
ZooKeeper作為EverDB的配置管理節點,其所管理的配置信息同樣需要持久化存儲,對外提供訪問服務;
Grid作為調度節點,其元數據保存在底層數據節點上,而配置參數通過初始化ConfigMap完成參數加載后,保存到遠端配置節點ZooKeeper上,即使發生Pod故障,Grid可以從ZooKeeper拉取配置信息,因此其不需要持久化的PV存儲數據。
服務訪問
EverDB對外需要提供數據庫服務,對內組件間也需要能夠互聯互通,那么各個Pod獨立運行,他們之間的聯系由誰來建立呢?
這就要介紹k8s的核心資源對象中Service,Service是一個抽象概念,它定義了一組Pod的邏輯集合和一個訪問它們的負載均衡策略。k8s的Service可以定義一個集群內部的服務訪問入口地址(ClusterIP),Service與Pod間通過LabelSelector來建立關聯,應用通過這樣一個入口地址訪問其背后的一組Pod實例。這樣,在Pod發生銷毀或重建導致PodIP發生變化時,Service可以自動感知且提供的ClusterIP不會發生改變,我們仍可以通過Service訪問后端的Pod。
而對于EverDB集群內部節點之間的通信,需要實現一對一通信且不受PodIP 變化的影響。比如EverDB集群中的Mysql主從實例,在進行主從同步時,從實例(Slave)需要能直接訪問主實例(Master)這一確切Pod,并不需要負載均衡,且在任何PodIP 發生變化時主從同步均不受影響,顯然上述Service的定位并不適合這樣的場景。
別急,k8s還設計了HeadlessService(無頭服務)這一特殊類型的Service。HeadlessService不分配ClusterIP,訪問者可以通過解析該Service的DNS來獲取Pod的地址,就像訪問域名一樣。HeadlessServie 的域名一般是“{podname}.{headlessservice}.{namespace}”的形式。與Deployment類型Pod的隨機化podname相比,StatefulSet類型的Pod,其podname格式為{StatefulSetname}-{固定編號},這也使得即使對Pod進行重啟、節點遷移等操作,域名本身并不會發生變動。
EverDB數據節點部署至k8s示例圖
因此對于EverDB集群,我們使用基于ClusterIP 類型的Service對外提供EverDB數據庫服務統一入口,對內提供多個Grid調度節點的負載均衡能力;使用HeadlessService 實現EverDB集群內部節點間的通信能力,且不受PodIP 變化影響。
持久化存儲
EverDB作為有狀態的應用,部署在k8s平臺需要解決存儲問題,即當應用Pod被刪除或重新創建時,內部數據不會丟失。PV(PersistentVolume)可以看作k8s集群可用的存儲資源,PVC(PersistentVolumeClaim)則是對存儲資源的需求。對于存儲資源,k8s平臺支持兩種供應模式:靜態模式(Static)和動態模式(Dynamic),在EverDB集群中,已支持這兩種供應模式。
在靜態模式中,需要集群管理員通過手動方式創建PV,EverDB采用的是基于LocalPV方法的持久化存儲,該方法主要應用于生產環境中,LocalPV對應的存儲介質通常是一塊額外掛載在宿主機的磁盤,實現“一個PV一塊盤”,不僅能夠有效減少宿主機宕機導致的數據丟失,而且增強了集群存儲擴展的能力。
靜態模式下LocalPV和PVC原理圖
在動態模式中,EverDB采用了基于HostPath的方法,該方法主要應用于開發測試環境中,使用宿主機本地目錄,有效避免IO開銷并擁有更高的讀寫性能。同時為了避免單機測試的問題,結合了Github開源項目LocalPath Provisioner,可以有效利用集群節點中的本地存儲,通過SrorgeClass(存儲類)的設置,只需PVC對存儲類型進行聲明,系統將自動完成PV的創建和綁定。
動態模式下StorageClass、PV和PVC原理圖
配置管理
容器的啟動總是需要些參數的,給容器內應用傳遞參數通常有以下幾種方式:
1、直接將配置文件打包到鏡像中;
2、在定義Pod時,添加自定義命令行參數,設定args:[“命令參數”];
3、使用環境變量來給Pod中的應用傳參修改配置。
ConfigMap的設計就是為了讓鏡像和配置文件解耦,以便實現鏡像的可移植性和可復用性,一個ConfigMap其實就是一系列配置信息的集合。
ConfigMap存在兩種方式將配置參數注入到容器中:
1、將環境變量直接定義在ConfigMap中,當Pod啟動時,通過env來引用ConfigMap中定義的環境變量;
2、將一個完整的配置文件封裝,通過共享卷的方式掛載進Pod中實現給應用傳參。
EverDB的三個組件的Pod在容器拉起時,需要配置性能參數,主從信息等。EverDB配置容器運行參數時,對于復雜的配置文件信息,采用ConfigMap創建配置文件的形式實現,在Pod啟動時,將其掛載到容器中,而對于一些簡單的運行參數,則是通過環境變量的形式注入到容器中。
依據上述EverDB各組件對ConfigMap、StatefulSet、持久化存儲、服務訪問等需求,創建對應的Helm模板,將EverDB的各組件封裝為Chart包,在啟動子組件時,只需要對一些必要的參數進行更改設置,即可完成定制化的EverDB集群安裝。即避免了手動部署易于出錯的問題,又能方便集群在k8s上的的定制化與快速部署,穩定且高效,靈活而優雅。
結束語
EverDB容器化實現不僅便于我們在混沌實驗中實現故障注入,也使我們在數據庫云化道路上邁出了里程碑式的一步!