作者 | 李家俊
背景
本文分享的是 Bytedoc 3.0 關(guān)于集群交付方面的內(nèi)容以及一些云原生的實(shí)踐:如何將 Bytedoc 3.0 與云原生的能力結(jié)合起來(lái),交付用戶一個(gè)開箱即用的集群,與軟件層的能力相匹配,最大化展示 Bytedoc 3.0 具備的“彈性”能力。
面臨的問題
數(shù)據(jù)庫(kù)服務(wù)的使用者有兩方:用戶(業(yè)務(wù))和 DBA(運(yùn)維),運(yùn)維能力不斷增強(qiáng),才能給用戶更好的服務(wù)體驗(yàn)。
用戶需求
運(yùn)維需求
目標(biāo)與思路
目標(biāo)
為了解決上述問題,Bytedoc 3.0 軟件層已經(jīng)實(shí)現(xiàn)了相關(guān)的能力,我們需要在交付層提供類似的能力,以匹配整體的“彈性”能力:
目標(biāo) 1:數(shù)據(jù)庫(kù)能快速擴(kuò)展/恢復(fù)從庫(kù),秒級(jí)擴(kuò)展數(shù)據(jù)庫(kù)的讀能力。擴(kuò)容從庫(kù)實(shí)例自動(dòng)化程度要高,擴(kuò)容速度盡可能快;
目標(biāo) 2:計(jì)算資源能夠按需擴(kuò)展。對(duì)計(jì)算密集型業(yè)務(wù),能快速擴(kuò)展計(jì)算資源,而且能分配更多的資源供用戶使用,匹配實(shí)際負(fù)載;
目標(biāo) 3:提升計(jì)算/存儲(chǔ)資源利用率,降低業(yè)務(wù)使用成本。
目標(biāo) 4:更標(biāo)準(zhǔn)的交付能力與更高的交付質(zhì)量,給用戶提供更好的數(shù)據(jù)庫(kù)服務(wù)。
實(shí)現(xiàn)思路
我們通過“Kubernetes 云原生”化來(lái)實(shí)現(xiàn)我們的目標(biāo):
Kubernetes 是業(yè)界通用的,用于自動(dòng)部署,擴(kuò)展和管理容器化應(yīng)用程序的開源編排系統(tǒng)。簡(jiǎn)單地說,它能夠系統(tǒng)化地打包、運(yùn)行、管理你的服務(wù),在這里是 Bytedoc 數(shù)據(jù)庫(kù)服務(wù)。這使得我們能夠結(jié)合已有的運(yùn)維經(jīng)驗(yàn)和業(yè)界通用服務(wù)交付/管理解決方案,提供更好的、更高質(zhì)量的數(shù)據(jù)庫(kù)服務(wù),以發(fā)揮 Bytedoc 3.0 十足的"彈性"能力
在 ByteDoc 1.0 時(shí)期,大多數(shù)數(shù)據(jù)庫(kù)服務(wù)實(shí)例是直接部署到虛擬機(jī)上的,資源的分配受限于虛擬機(jī)規(guī)格的劃分,無(wú)法靈活地、按需要分配機(jī)器資源,導(dǎo)致大部分的機(jī)器資源空閑出來(lái),無(wú)法得到有效利用;同時(shí),因?yàn)橛?jì)算與存儲(chǔ)的資源綁定,在 1.0 的自研部署平臺(tái)中,實(shí)現(xiàn)容器化部署十分困難,虛擬機(jī)部署的方案就一直保留下來(lái)。
直到 ByteDoc 3.0 的出現(xiàn),將數(shù)據(jù)下沉存儲(chǔ)層,交付服務(wù)時(shí),更多關(guān)注計(jì)算層方面的資源分配,使得容器化部署模式可行。結(jié)合 Kubernetes 對(duì)容器部署與管理的解決方案,我們將容器大部分自動(dòng)化管理操作交由給 Kubernetes 控制器管理,專注于 ByteDoc 3.0 服務(wù)編排過程,如集群部署、從庫(kù)擴(kuò)容等,以充分發(fā)揮 3.0 的"彈性"能力。
云原生實(shí)踐
服務(wù)云原生化,實(shí)際上是一個(gè)“遷移”的過程,將原有服務(wù)打包、運(yùn)行、管理能力,以 Kubernetes 提供的解決方案為標(biāo)準(zhǔn),重現(xiàn)出來(lái)。
在 Kubernetes 中,我們把 ByteDoc 3.0 集群定義為一種定制資源(CustomResource)。提供數(shù)據(jù)庫(kù)服務(wù),實(shí)際上就是創(chuàng)建了一種資源;和 K8s 內(nèi)置的工作負(fù)載資源一樣,定制資源也需要一個(gè)控制器來(lái)進(jìn)行管理,通常把這類控制器稱作 Operator。
所以,ByteDoc 3.0 云原生實(shí)踐的關(guān)鍵是構(gòu)建 ByteDoc Operator。
Kubernetes 基本概念
容器化部署
前面說到,Kubernetes 是一個(gè)容器編排系統(tǒng),主要的職責(zé)是管理容器生命周期,所以實(shí)際的應(yīng)用程序應(yīng)該提前打包成一個(gè)容器鏡像,以便于交付給 K8s 來(lái)使用。
Pod
??https://kubernetes.io/zh/docs/concepts/workloads/pods/??
我們打包好的容器最后會(huì)運(yùn)行在 Pod 中,由 Pod 管理起來(lái)。
Pod 是 K8s 中內(nèi)置的最基本的資源之一,也是最小部署的計(jì)算單元,可以類比于一些共享機(jī)器資源的容器組。
如果一個(gè) Pod (內(nèi)的容器)因異常導(dǎo)致退出,通常情況下,這個(gè) Pod 會(huì)被刪除銷毀,高級(jí) workload 會(huì)新建一個(gè)全新的 Pod 來(lái)代替它,而不是“重啟”該 Pod。
高級(jí) workload
通常情況下,我們構(gòu)建的 Operator 不會(huì)去直接創(chuàng)建 Pod;而是使用 K8s 提供的高級(jí) workload 資源,他們會(huì)幫我們把 Pod 創(chuàng)建出來(lái),并提供一些基本的資源管理能力。
Deployment:
維護(hù)一組給定數(shù)量的、完全相同的 Pod,通常用于無(wú)狀態(tài)服務(wù)。
StatefulSet:
同樣維護(hù)一組 Pod,特別的是,每個(gè) Pod 都有穩(wěn)定的網(wǎng)絡(luò)標(biāo)示;在此情景下,如果一個(gè) Pod 被銷毀重建,在外部使用者看來(lái),該 Pod 是進(jìn)行“重啟”了,所以通常用于有狀態(tài)的服務(wù)。
如何與 K8s 交互
Kubernetes 控制面的核心是 API 服務(wù)器。API 服務(wù)器負(fù)責(zé)提供 HTTP API,以供用戶、集群中的不同部分和集群外部組件相互通信。
一般而言,我們與 K8s 交互是通過“聲明式”,而非“動(dòng)作命令式”。
# sample.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
minReadySeconds: 6
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
ByteDoc 3.0 在 Kubernetes 上的架構(gòu)
(ByteDoc 經(jīng)典架構(gòu),由三部分組成)
mongos:
- 代理層,負(fù)責(zé)轉(zhuǎn)發(fā)、簡(jiǎn)單處理用戶請(qǐng)求;
- 需要感知 Config Server,從中獲取 Shard 的拓?fù)?
config server:
- 復(fù)制集模式,區(qū)分主從;
- 存儲(chǔ)集群元信息,包括每個(gè) shard 內(nèi)實(shí)例的地址;
shard:
- 復(fù)制集模式,區(qū)分主從;
- server 層,處理用戶請(qǐng)求;
- mongos - deployment,對(duì)應(yīng)無(wú)狀態(tài)的服務(wù)
- config server/shard - statefulset,對(duì)應(yīng)有狀態(tài)的服務(wù)
對(duì)于 mongo 的復(fù)制集,復(fù)制集成員需要用一個(gè)網(wǎng)絡(luò)標(biāo)示(host、dns、服務(wù)發(fā)現(xiàn)、headless service 等)注冊(cè)進(jìn)配置中,才能與其他復(fù)制集成員進(jìn)行通信;所以在這里,我們使用 StatefulSet 提供的穩(wěn)定的網(wǎng)絡(luò)標(biāo)示作為成員名稱,保證數(shù)據(jù)庫(kù)實(shí)例(對(duì)應(yīng)的 pod)故障恢復(fù)后,以同樣的身份加入到復(fù)制集中,正常地提供服務(wù)。
聯(lián)邦模式
多機(jī)房場(chǎng)景下的架構(gòu)
實(shí)際上,上述 ByteDoc 3.0 集群是在單機(jī)房下的架構(gòu),在線上生產(chǎn)環(huán)境中,還要考慮多機(jī)房容災(zāi)的場(chǎng)景;在多機(jī)房場(chǎng)景下,我們希望一個(gè) 3 副本的集群,每個(gè)計(jì)算實(shí)例是分別部署在不同的機(jī)房,但是這些副本又是需要在同一個(gè)復(fù)制集中的,在參考了幾種跨 K8s 方案后,我們采取了下面的多機(jī)房架構(gòu):
有哪些候選的跨 K8s 的方案?
1、社區(qū)提供的 Federation V1 方案(deprecated,后續(xù)改進(jìn)為 V2 方案)
- 主要提供兩個(gè)主要能力來(lái)支持管理多個(gè)集群:跨集群同步資源 & 跨集群服務(wù)發(fā)現(xiàn)
2、云原生 SRE 團(tuán)隊(duì)提供的通用 operator 方案
spec:
charts:
- name: nginx
chart: charts/nginx
values: '"replicaCount": "{{ .replica_count | default 1 }}"'
cluster:
- placement:
- names:
- cluster-sample
execJobs:
- name: ls
command:
- ls
- "-l"
mustBefore:
- chart.nginx
var:
replica_count: '2'
- 定義一系列的 chart、K8s 集群、執(zhí)行任務(wù)、變量
- 通過通用 operator 在多個(gè) K8s 集群上完成資源部署、任務(wù)執(zhí)行
3、MongoDB 官方提供的跨 K8s 方案
- 通過 mongodb 額外的 k8s-agent 完成跨 K8s 集群場(chǎng)景下,復(fù)制集與集群的構(gòu)建
- 代碼未開源
為什么選擇了現(xiàn)在這種方案?
我們當(dāng)前的方案實(shí)際上和社區(qū)的 Federation 方案比較相似,通過以下的能力搭建完整的 ByteDoc 3.0 集群:
- 資源復(fù)制:operator 連接 worker K8s,創(chuàng)建基本相同的資源
- 服務(wù)發(fā)現(xiàn):分具體的網(wǎng)絡(luò)場(chǎng)景,如果使用 overlay 網(wǎng)絡(luò),可以使用社區(qū)支持的無(wú)頭服務(wù)(headless service);如果是走 underlay 網(wǎng)絡(luò),則需要額外的服務(wù)發(fā)現(xiàn)能力
- 組建 ByteDoc 集群:將實(shí)例組建為集群的核心邏輯,比較復(fù)雜,不太容易通過任務(wù)的形式實(shí)現(xiàn)
因此,我們選擇這種 Meta Operator 的方案,通過 worker K8s 創(chuàng)建資源,后續(xù) Operator 完成集群的搭建工作。
達(dá)到期望狀態(tài)
spec:
global:
chartVersion: "1.0.0.0"
bytedocVersion: "3.0"
image: "example_image"
mongos:
replicaCount: 2
resources:
limits:
cpu: "4"
memory: 2Gi
requests:
cpu: "1"
memory: 1Gi
shard:
shardsCount: 1
replicaCount: 4
config:
replicaCount: 3
placement:
- name: vdc1
vdc: "vdc1"
那么 ByteDoc Operator 到底執(zhí)行了什么邏輯呢?Operator 其實(shí)也滿足 K8s 控制器的設(shè)計(jì)理念,也就是,每個(gè)資源對(duì)象有一個(gè)期望狀態(tài),operator 需要把資源對(duì)象調(diào)整至期望狀態(tài),這個(gè)調(diào)整過程稱為 Reconcile。
再仔細(xì)探究一下,ByteDoc 集群分為 3 個(gè)組件,只要 Mongos、config server、shard 三個(gè)組件都達(dá)到期望狀態(tài),最后將組件“連接”起來(lái),整個(gè)集群也達(dá)到期望狀態(tài)了;
所以我們可以將整個(gè)集群的 Reconcile,分解為對(duì)若干個(gè)模塊 Reconcile,當(dāng)每個(gè)小的模塊都達(dá)到期望狀態(tài),整個(gè)集群也達(dá)到了期望狀態(tài),bytedoc operator 大致是基于這樣的設(shè)計(jì)理念。
所以,我們的 operator 大致流程如下:
狀態(tài)管理 - Status
Status 顧名思義就是用來(lái)存儲(chǔ)一個(gè)資源對(duì)象當(dāng)前狀態(tài)的,可以充當(dāng)狀態(tài)機(jī)或小型存儲(chǔ)使用??梢杂脕?lái)實(shí)現(xiàn):
- 一次性操作的完成情況:如初始化是否完成
- 鏡像/服務(wù)版本
- 組件 status
- 集群升級(jí)進(jìn)度控制
- 備份進(jìn)度控制
- 資源變更是否完成
- 通常記錄在字段 observedGeneration
- 當(dāng)我們對(duì)集群 CR 做變更時(shí),metadata 中的 generation 計(jì)數(shù)會(huì) +1,operator 需要在達(dá)到這次變更的期望狀態(tài)時(shí),設(shè)置相等的 observedGeneration 計(jì)數(shù),以方便查詢 CR 變更已完成。
- 等等
status:
replica_set:
ll2022-shard-0:
inline:
added_as_shard: true
initialized: true
read_only_ready: true
ready: 3
size: 3
status: ready
placement:
vdc1:
ready: 3
size: 3
status: ready
vdc: vdc1
資源模板化管理 - Helm
上面說到,ByteDoc 每個(gè)組件實(shí)際上用了 K8s 內(nèi)置的資源來(lái)管理的,一般情況下,我們不需要從頭編寫整個(gè) CR 的 yaml 文件,更多的是調(diào)整一個(gè)固定模板里的動(dòng)態(tài)參數(shù),比如 mongos 的實(shí)例數(shù),CPU、Mem 資源限制等。
從此出發(fā),工程實(shí)現(xiàn)上就有兩種方向:
- 將 yaml 模板硬編碼到代碼中,通過變量替換,生成目標(biāo)的資源文件;
- 將 yaml 模板以文件的形式存放,通過字符替換,生成目標(biāo)的資源文件;
模板渲染與資源發(fā)布
我們采用的是第二種方法的一個(gè)較為優(yōu)雅的版本,用 Helm (code)管理內(nèi)置資源文件的渲染和發(fā)布 在 operator 鏡像中,我們按照 helm 的標(biāo)準(zhǔn),維護(hù)了一系列 charts 文件。
在創(chuàng)建資源時(shí),利用 helm sdk,將參數(shù)渲染到 charts 模板中,生成實(shí)際的資源 yaml,接著發(fā)布到 worker K8s 集群中,operator 只需要等待資源完全 ready 就可以繼續(xù)執(zhí)行變更了。
模板版本管理
另一個(gè)優(yōu)雅的地方在于,此方案可以存放不同版本的 charts 模板,按版本劃分為不同的目錄;
當(dāng)我們需要發(fā)布一個(gè)新版本的 charts 模板時(shí),舊版本的服務(wù)并不會(huì)受到影響;而新創(chuàng)建的服務(wù)可以使用新版本的 charts 模板。
防止誤操作
引入 K8s 幫助我們交付/管理集群有很多便利之處,但實(shí)際上也是一把雙刃劍,可以不經(jīng)意地誤操作多數(shù)集群,導(dǎo)致數(shù)據(jù)庫(kù)服務(wù)可用性受損。
可能的場(chǎng)景包括:
- 誤刪除集群資源對(duì)象 -> 導(dǎo)致服務(wù)下線
- 誤縮容實(shí)例至 0 -> 無(wú)實(shí)例提供服務(wù)
- 誤刪除 CRD -> 導(dǎo)致所有對(duì)應(yīng)資源對(duì)象刪除,所有服務(wù)下線
Finalizers
??https://kubernetes.io/zh/docs/concepts/overview/working-with-objects/finalizers/??
這是一個(gè) K8s 防止誤刪除原生的解決方案,它是一個(gè)標(biāo)示位,任何涉及刪除的操作,都需要檢查是否存在 finalizers。
- 當(dāng) finalizer 存在時(shí),經(jīng)過 API Server 的刪除資源的請(qǐng)求都會(huì)被 block 住,直到 finalizer 被清除;
- 通常 finalizer 標(biāo)記由對(duì)應(yīng)的 operator 管理,在每次變更發(fā)生時(shí),檢查并添加 finalizer 標(biāo)記;
- 當(dāng)接收到刪除請(qǐng)求時(shí),判斷是否滿足刪除條件。在我們的場(chǎng)景下,一般需要等待 7 天的刪除冷靜期,所以如果遇到誤刪除操作,我們是有 7 天的時(shí)間進(jìn)行恢復(fù)的;
實(shí)例數(shù)量縮容下限
這是一個(gè)很簡(jiǎn)單的方案,在接收到 CR 縮容變更時(shí),檢查其數(shù)量是否 > 0,不接受 = 0 的縮容,避免極端情況下,無(wú)可用實(shí)例的情況。