戰(zhàn):CloudNativePG部署與故障切換指南)
不少團(tuán)隊(duì)在K8s上部署高可用PostgreSQL時(shí)第一反應(yīng)是照搬裸機(jī)上的老經(jīng)驗(yàn)起三個(gè)Pod、配置流復(fù)制、再掛一層負(fù)載均衡或者VIP。結(jié)果折騰到半夜發(fā)現(xiàn)K8s的探針、調(diào)度、網(wǎng)絡(luò)模型每一樣都在跟數(shù)據(jù)庫(kù)的模式較勁。我做了一輪完整的部署、壓測(cè)和故障演練之后把整套方案固化成了下面這份文檔。這篇文章主要針對(duì)已經(jīng)有K8s基礎(chǔ)、想在集群內(nèi)部運(yùn)行生產(chǎn)級(jí)PostgreSQL的運(yùn)維或平臺(tái)工程師也會(huì)照顧到剛接觸有狀態(tài)服務(wù)的同學(xué)盡量把每個(gè)選擇背后的原因講透。我最終選型的是基于Operator的原生方案——CloudNativePG用它管理一個(gè)三副本的高可用PostgreSQL集群。選它的理由、部署步驟、參數(shù)含義、故障切換實(shí)測(cè)結(jié)果、備份恢復(fù)演練以及踩過的那些沒寫在官方文檔里的坑下面全部展開。1. 有狀態(tài)應(yīng)用進(jìn)K8s為什么比無狀態(tài)難一個(gè)量級(jí)無狀態(tài)服務(wù)在K8s里是好公民ReplicaSet只管Pod數(shù)量HPA只管擴(kuò)縮容ConfigMap一掛重啟完事。但數(shù)據(jù)庫(kù)進(jìn)了K8s至少要跨過三道坎。第一道坎是數(shù)據(jù)盤和節(jié)點(diǎn)的綁定關(guān)系。一個(gè)無狀態(tài)Pod在節(jié)點(diǎn)宕機(jī)后可以立刻在別處重建但數(shù)據(jù)庫(kù)Pod的數(shù)據(jù)目錄往往是本地卷或者RWOReadWriteOnce云盤新Pod要等存儲(chǔ)層的調(diào)度和掛載完成后才有機(jī)會(huì)真正啟動(dòng)。換句話說K8s的調(diào)度器可以秒級(jí)重建一個(gè)空殼Pod但存儲(chǔ)層不可能秒級(jí)把幾百GB的數(shù)據(jù)“搬”到新節(jié)點(diǎn)。這也是為什么數(shù)據(jù)庫(kù)高可用不能指望“Pod重啟”來解決必須靠數(shù)據(jù)庫(kù)自己的復(fù)制機(jī)制。第二道坎是角色和拓?fù)涞拇嬖凇8s的Deployment只能告訴你“我要3個(gè)副本”它不關(guān)心這三個(gè)副本誰是主、誰是備、誰在追趕WAL。但PostgreSQL高可用集群恰恰最關(guān)心這個(gè)。裸機(jī)上我們習(xí)慣用Keepalived搶VIP、用etcd做選主、用HAProxy做流量分發(fā)這套組件在K8s里面全部要重寫一遍而且K8s本身沒有VIP概念Service的Endpoints更新又是個(gè)異步過程。第三道坎是探針的誤判。K8s用livenessProbe和readinessProbe來管理Pod生命周期但數(shù)據(jù)庫(kù)有自己的健康語義——比如備庫(kù)短暫追不上主庫(kù)是正?,F(xiàn)象如果readiness探針在這種時(shí)候把備庫(kù)摘出去故障轉(zhuǎn)移發(fā)生時(shí)你可能連一個(gè)合格的備庫(kù)都找不到。反過來liveness探針如果過于激進(jìn)網(wǎng)絡(luò)抖動(dòng)一次就可能把主庫(kù)Pod干掉觸發(fā)一次毫無必要的failover。所以結(jié)論很明確在K8s上做高可用數(shù)據(jù)庫(kù)本質(zhì)上不是“把數(shù)據(jù)庫(kù)塞進(jìn)容器”而是“讓K8s的調(diào)度、探針、網(wǎng)絡(luò)模型去適配數(shù)據(jù)庫(kù)復(fù)制、選主、腦裂防護(hù)這套自有邏輯”。能把這層適配做干凈的目前只有Operator模式。2. PostgreSQL高可用方案選型為什么最終選了CloudNativePG我最早考慮過三條路線踩過一輪評(píng)估之后才定的方向。簡(jiǎn)單說自己搭Patroni全家桶、用托管類Operator、用云上全托管數(shù)據(jù)庫(kù)這三條路線各有各的取舍。2.1 三條路線的橫向?qū)Ρ确桨附M件復(fù)雜度故障轉(zhuǎn)移邏輯歸屬適合場(chǎng)景常見痛點(diǎn)Patroni etcd HAProxy 自建高外部編排器對(duì)選主邏輯有強(qiáng)定制需求etcd本身要3節(jié)點(diǎn)、升級(jí)鏈路長(zhǎng)、排障牽扯面廣CloudNativePG 這類數(shù)據(jù)庫(kù)Operator中K8s控制器集群已經(jīng)重度K8s化、希望聲明式管理需要對(duì)CRD有信任跨大版本升級(jí)需謹(jǐn)慎云廠商全托管RDS低云平臺(tái)不想管理數(shù)據(jù)庫(kù)、能接受一定鎖定網(wǎng)絡(luò)路徑出集群、部分SQL能力受限我個(gè)人的判斷是如果你的業(yè)務(wù)已經(jīng)跑在K8s里而且短期不會(huì)遷走用Operator把數(shù)據(jù)庫(kù)納管進(jìn)來收益遠(yuǎn)大于成本。它把備份、故障轉(zhuǎn)移、滾動(dòng)升級(jí)、存儲(chǔ)配置全部收口到CRD和控制器里你可以用管理Deployment的方式來管理數(shù)據(jù)庫(kù)集群這在運(yùn)維心智上是巨大的減負(fù)。2.2 CloudNativePG的架構(gòu)精髓一實(shí)例一Pod不共享數(shù)據(jù)盤CloudNativePG的核心設(shè)計(jì)跟傳統(tǒng)“共享存儲(chǔ)集群軟件”完全不同。它不給多個(gè)Pod掛同一個(gè)存儲(chǔ)卷而是每個(gè)PostgreSQL實(shí)例一個(gè)PodPod之間通過標(biāo)準(zhǔn)的流復(fù)制協(xié)議保持?jǐn)?shù)據(jù)一致。這套邏輯和Patroni在裸機(jī)上的主備同步非常像但編排層被Operator接管了。簡(jiǎn)單類比Patroni加etcd像是在家里自己裝一套中央決策系統(tǒng)而CloudNativePG是把決策系統(tǒng)內(nèi)置進(jìn)了“物業(yè)公司”——控制器監(jiān)控每個(gè)Pod的狀態(tài)當(dāng)主節(jié)點(diǎn)不可達(dá)時(shí)控制器會(huì)根據(jù)一份預(yù)先定義的規(guī)則選出數(shù)據(jù)最新的備庫(kù)并提升為主庫(kù)然后自動(dòng)修改Service的后端Endpoints。整個(gè)過程不需要人在半夜爬起來敲命令。還有一個(gè)容易被忽略的點(diǎn)它默認(rèn)不依賴etcd這類額外組件選主用的是一套基于quorum的機(jī)制。這意味著整個(gè)高可用方案的“可故障面”少了一大塊etcd集群自身出問題的概率被直接消除了。3. 先把環(huán)境磨平Operator安裝與存儲(chǔ)層準(zhǔn)備選型定了之后我開始準(zhǔn)備環(huán)境。這里多說一句不要急著創(chuàng)建Cluster資源先花十分鐘把Operator裝好、把StorageClass確定下來否則后面大概率要推倒重來。3.1 版本搭配與前置檢查我用的K8s版本是1.28左右CloudNativePG Operator對(duì)K8s版本和PostgreSQL鏡像版本都有一套兼容矩陣建議在官方release頁(yè)面確認(rèn)一下你的組合在支持列表內(nèi)。操作前先檢查集群狀態(tài)kubectl get nodes -o wide kubectl version --short kubectl get crd | grep cnpg如果沒有CRD說明Operator還沒裝按下一步安裝。這個(gè)前置檢查很重要我見過有人直接apply一個(gè)Cluster定義結(jié)果CRD不在報(bào)了一堆奇怪錯(cuò)誤浪費(fèi)了不少時(shí)間。3.2 安裝Operator和kubectl插件官方支持Helm安裝和直接apply manifest兩種方式我用的是Helm好處是后續(xù)可以跟著operator的版本走h(yuǎn)elm repo add cnpg https://cloudnative-pg.github.io/charts helm repo update helm install cnpg cnpg/cloudnative-pg \ --namespace cnpg-system \ --create-namespace \ --set replicaCount2這里把replicaCount設(shè)為2是給控制器本身做了一份高可用副本避免控制器單點(diǎn)。Operator裝好后強(qiáng)烈建議再裝官方提供的kubectl插件cnpg后面查看集群狀態(tài)、發(fā)起備份、手動(dòng)切換主庫(kù)都會(huì)用到kubectl krew install cnpg3.3 存儲(chǔ)層本地盤還是云盤以及最容易踩的ReclaimPolicy高可用數(shù)據(jù)庫(kù)對(duì)存儲(chǔ)延遲非常敏感。流復(fù)制本身不需要共享存儲(chǔ)但每個(gè)實(shí)例的WAL日志寫入要快。如果條件允許優(yōu)先給數(shù)據(jù)庫(kù)Pod掛本地NVMe盤如果用的是云上CSI卷要特別注意兩個(gè)參數(shù)。第一個(gè)是reclaimPolicy。我見過不少集群把默認(rèn)StorageClass的回收策略設(shè)成了Delete一旦誤刪PVC底層云盤跟著沒了數(shù)據(jù)直接清零。數(shù)據(jù)庫(kù)場(chǎng)景強(qiáng)烈建議改成Retain。apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-nvme provisioner: local.csi.openebs.io reclaimPolicy: Retain volumeBindingMode: WaitForFirstConsumer allowVolumeExpansion: true第二個(gè)是volumeBindingMode。這里用WaitForFirstConsumer是為了讓K8s先把Pod調(diào)度到某個(gè)節(jié)點(diǎn)再在那個(gè)節(jié)點(diǎn)上創(chuàng)建卷。這樣做的好處是卷和Pod在同一個(gè)節(jié)點(diǎn)上避免Pod已經(jīng)啟動(dòng)、卷還在跨節(jié)點(diǎn)掛載的等待時(shí)間。如果你只是想快速驗(yàn)證整套流程跑一個(gè)小型本地測(cè)試集群也可以用local-path-provisioner這類輕量方案。但記住一條原則任何存儲(chǔ)層方案都必須支持WaitForFirstConsumer否則數(shù)據(jù)庫(kù)Pod的啟動(dòng)時(shí)序會(huì)變得不可控。4. 三副本Cluster規(guī)格說明書一份能直接改的YAML環(huán)境準(zhǔn)備好之后真正的核心工作就是寫Cluster定義。這份YAML是整個(gè)高可用方案的靈魂我在這里踩的坑也最多所以逐字段拆開講。4.1 一份可直接實(shí)測(cè)的Cluster YAML下面這份配置我加了注釋你可以直接復(fù)制改參數(shù)但務(wù)必先看后面的字段解釋apiVersion: cnpg.io/v1 kind: Cluster metadata: name: postgres-ha namespace: database spec: instances: 3 imageName: ghcr.io/cloudnative-pg/postgresql:16.4 storage: size: 20Gi storageClass: local-nvme affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - topologyKey: kubernetes.io/hostname labelSelector: matchLabels: cnpg.io/cluster: postgres-ha postgresql: parameters: shared_buffers: 512MB max_connections: 300 synchronous_commit: on wal_keep_size: 512MB max_wal_senders: 10 pg_hba: - host all all all md5 bootstrap: initdb: database: appdb owner: appuser secret: name: app-db-secret backup: volumeSnapshot: className: local-nvme-snapshot monitoring: enablePodMonitor: true4.2 幾個(gè)關(guān)鍵字段為什么非配不可先看instances: 3。三副本是高可用數(shù)據(jù)庫(kù)的基礎(chǔ)形態(tài)。兩個(gè)副本雖然也能做主備切換但一旦備庫(kù)宕機(jī)主庫(kù)就失去了故障轉(zhuǎn)移的“后備力量”。三副本意味著即使一個(gè)節(jié)點(diǎn)下線仍然有足夠的備庫(kù)可以承擔(dān)選舉。再來看affinity這段。requiredDuringSchedulingIgnoredDuringExecution是硬反親和配合topologyKey: kubernetes.io/hostname可以強(qiáng)制三個(gè)副本落到三個(gè)不同節(jié)點(diǎn)上。這一步?jīng)]做對(duì)等于把三個(gè)雞蛋放在同一個(gè)籃子里——節(jié)點(diǎn)一宕整個(gè)集群可能全部不可用。然后看postgresql.parameters。這里有一個(gè)很微妙的權(quán)衡synchronous_commit: on保證主庫(kù)在收到備庫(kù)確認(rèn)后才提交事務(wù)可以做到RPO幾乎為零但對(duì)高并發(fā)寫入會(huì)有明顯性能損耗。如果業(yè)務(wù)允許幾秒內(nèi)的數(shù)據(jù)丟失可以把這一項(xiàng)改成off切換速度可以更快寫入性能也更接近單機(jī)。我在壓測(cè)中的經(jīng)驗(yàn)是默認(rèn)先開同步等業(yè)務(wù)反饋寫入延遲是否可以接受再?zèng)Q定是否降級(jí)。最后看bootstrap.initdb。初始化階段最好直接用Secret方式創(chuàng)建數(shù)據(jù)庫(kù)用戶而不是把明文密碼寫在YAML里。Secret可以先手動(dòng)創(chuàng)建再在Cluster里引用Operator會(huì)在初始化時(shí)自動(dòng)把賬號(hào)建好。kubectl create secret generic app-db-secret \ -n database \ --from-literalusernameappuser \ --from-literalpasswordYourStrongPassword4.3 探針和PDB很多人忽略的下半場(chǎng)Cluster里的實(shí)例Pod會(huì)由Operator自動(dòng)生成對(duì)應(yīng)的探針配置但我還是建議在生成的StatefulSet或Deployment上再檢查一下探針參數(shù)。CloudNativePG默認(rèn)生成的readinessProbe用的是pg_isready這比直接執(zhí)行SQL更溫和。重點(diǎn)是把initialDelaySeconds適當(dāng)調(diào)大因?yàn)镻ostgreSQL實(shí)例剛剛啟動(dòng)時(shí)要恢復(fù)內(nèi)存中的shared buffer一個(gè)健康的備庫(kù)也可能需要十幾秒才能完全進(jìn)入就緒狀態(tài)如果探針太急會(huì)出現(xiàn)備庫(kù)反復(fù)被摘除又加回來的現(xiàn)象。另一個(gè)必須提前創(chuàng)造的是PodDisruptionBudgetPDB。PDB的作用是告訴K8s節(jié)點(diǎn)維護(hù)時(shí)同一個(gè)時(shí)間點(diǎn)最多只能刪掉一個(gè)數(shù)據(jù)庫(kù)實(shí)例。否則你執(zhí)行kubectl drain的時(shí)候K8s調(diào)度器很可能一次性把多個(gè)副本都打掉集群就直接癱瘓了。apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: postgres-ha-pdb namespace: database spec: minAvailable: 2 selector: matchLabels: cnpg.io/cluster: postgres-ha這里minAvailable: 2意味著任何主動(dòng)驅(qū)逐操作比如drain最多只能讓一個(gè)副本不可用給故障轉(zhuǎn)移留足了緩沖。5. 故障演練實(shí)錄把主節(jié)點(diǎn)拔掉之后發(fā)生了什么配置寫好了YAML apply上去Pod也起來了但這只是開始。真正檢驗(yàn)高可用方案的唯一標(biāo)準(zhǔn)是故障發(fā)生時(shí)的真實(shí)表現(xiàn)。所以我做了一輪完整的故障注入測(cè)試步驟和觀察點(diǎn)都記錄在這里。5.1 第一步確認(rèn)三副本同步狀態(tài)故障注入之前先看集群正常狀態(tài)。三個(gè)實(shí)例都Ready其中一個(gè)角色是Primary另外兩個(gè)是Standby彼此延遲極小。kubectl cnpg status postgres-ha正常情況下輸出里能看到類似下面的信息注意查看WAL延遲列實(shí)例角色狀態(tài)延遲postgres-ha-1PrimaryReady-postgres-ha-2StandbyReady0spostgres-ha-3StandbyReady0s如果延遲單位是秒或更大先不要做故障演練應(yīng)該先檢查網(wǎng)絡(luò)和磁盤IO。備庫(kù)追不上主庫(kù)的時(shí)候強(qiáng)行切換等于把數(shù)據(jù)丟失風(fēng)險(xiǎn)直接暴露給生產(chǎn)。5.2 第二步模擬主庫(kù)物理故障最真實(shí)的故障是節(jié)點(diǎn)級(jí)宕機(jī)但為了可控我先做了一次Pod級(jí)刪除模擬“主庫(kù)進(jìn)程被誤殺”的場(chǎng)景# 先找到當(dāng)前主庫(kù)Pod PRIMARY_POD$(kubectl get pods -n database -l cnpg.io/clusterpostgres-ha \ -o jsonpath{.items[?(.metadata.labels.roleprimary)].metadata.name}) # 刪掉它 kubectl delete pod $PRIMARY_POD -n database刪除命令執(zhí)行后我開始計(jì)時(shí)并持續(xù)觀察集群行為。大約幾秒后Operator檢測(cè)到主庫(kù)不可達(dá)開始在兩個(gè)備庫(kù)之間發(fā)起選主。選主的邏輯并不復(fù)雜誰的WAL位置最新誰就優(yōu)先被提升為新的Primary。在我們這次模擬項(xiàng)目X的實(shí)測(cè)里切換耗時(shí)大概在20秒到40秒之間主要取決于探針掃描周期、選舉時(shí)間和Service Endpoints的傳播延遲。新主產(chǎn)生后Operator會(huì)自動(dòng)把往舊主的Service流量切到新主上。你不需要手動(dòng)調(diào)整任何東西這本身就是整個(gè)方案最值錢的地方。5.3 第三步觀察應(yīng)用側(cè)連接不中斷很多人在數(shù)據(jù)庫(kù)層做了高可用但應(yīng)用連接是固定的IP或者寫死的連接串這等于高可用只做了一半。業(yè)務(wù)側(cè)訪問數(shù)據(jù)庫(kù)應(yīng)該通過Service而不是直接打到某個(gè)Pod IP上。CloudNativePG默認(rèn)會(huì)生成兩個(gè)Servicepostgres-ha-rw指向當(dāng)前Primary負(fù)責(zé)讀寫流量postgres-ha-ro在所有實(shí)例之間做負(fù)載均衡負(fù)責(zé)只讀流量在故障切換過程中postgres-ha-rw的Endpoints會(huì)自動(dòng)從舊主更新到新主。應(yīng)用如果用的是這個(gè)Service域名就能實(shí)現(xiàn)無縫切換。我在測(cè)試時(shí)用一個(gè)虛擬連接腳本循環(huán)建連、執(zhí)行select 1整個(gè)故障切換期間只出現(xiàn)了少量的連接超時(shí)和重連沒有出現(xiàn)徹底斷連的情況。5.4 腦裂怎么防quorum與fencing機(jī)制分布式的核心難題都在“腦裂”。如果網(wǎng)絡(luò)分區(qū)把集群分成兩半舊主和新主同時(shí)認(rèn)為自己是Primary就會(huì)出現(xiàn)雙寫數(shù)據(jù)一致性直接崩盤。CloudNativePG給出的方案是quorum思路備庫(kù)在提升之前會(huì)先嘗試確認(rèn)自己在多數(shù)派中。如果網(wǎng)絡(luò)分區(qū)導(dǎo)致一個(gè)備庫(kù)聯(lián)系不上其他副本它不會(huì)貿(mào)然提升。同時(shí)如果Operator檢測(cè)到舊主雖然不可達(dá)但進(jìn)程還活著它會(huì)嘗試對(duì)舊主執(zhí)行fencing操作把它的寫入能力“隔離掉”確保只有一個(gè)主節(jié)點(diǎn)在對(duì)外服務(wù)。這個(gè)機(jī)制我不建議你自己實(shí)現(xiàn)也不要試圖繞過去。我見過有人為了加快切換速度把quorum相關(guān)參數(shù)調(diào)得非常激進(jìn)甚至強(qiáng)制允許備庫(kù)在沒有任何確認(rèn)的情況下提升結(jié)果真的發(fā)生過雙主寫庫(kù)的慘案。6. 備份、恢復(fù)與日常維護(hù)高可用集群的最后一公里故障切換能跑通不等于集群可以不運(yùn)維了。高可用解決的是“無故障時(shí)不怕故障”備份恢復(fù)解決的是“故障已發(fā)生且數(shù)據(jù)損壞時(shí)還能兜底”。這兩件事是互補(bǔ)關(guān)系缺一不可。6.1 定期備份與WAL歸檔在Cluster定義里已經(jīng)配置了backup.volumeSnapshot這意味著Operator會(huì)基于CSI卷快照做物理備份。除了周期性快照還要配合持續(xù)性的WAL歸檔才能做到時(shí)間點(diǎn)恢復(fù)PITR。實(shí)際生產(chǎn)中我建議兩條腿走路基礎(chǔ)備份每天一次由ScheduledBackupCRD控制WAL歸檔持續(xù)開啟把每天的WAL文件增量保存到對(duì)象存儲(chǔ)ScheduledBackup的定義類似這樣apiVersion: cnpg.io/v1 kind: ScheduledBackup metadata: name: postgres-ha-daily-backup namespace: database spec: schedule: 0 2 * * * cluster: name: postgres-ha backupOwnerReference: self引用backupOwnerReference: self是為了防止集群刪除時(shí)備份資源被誤刪這個(gè)參數(shù)很容易被漏掉。6.2 一次真實(shí)的恢復(fù)演練備份如果不做恢復(fù)演練等于沒有備份。我強(qiáng)烈建議每季度至少做一次完整的恢復(fù)演練。實(shí)操方式很簡(jiǎn)單用一個(gè)全新的名字從這個(gè)集群的備份中恢復(fù)出一個(gè)獨(dú)立集群然后驗(yàn)證數(shù)據(jù)。kubectl cnpg backup postgres-ha kubectl cnpg promote postgres-ha嚴(yán)格來說恢復(fù)演練用的不是promote而是下面這種通過bootstrap恢復(fù)的方式apiVersion: cnpg.io/v1 kind: Cluster metadata: name: postgres-ha-restore-test spec: instances: 1 bootstrap: recovery: source: postgres-ha recoveryTarget: backupID: latest storage: size: 20Gi這個(gè)恢復(fù)集群起來之后我會(huì)執(zhí)行幾條關(guān)鍵的SQL檢查數(shù)據(jù)表行數(shù)、最新寫入時(shí)間、以及是否恢復(fù)到目標(biāo)時(shí)間點(diǎn)。整個(gè)過程如果能在半小時(shí)完成備份體系才算真正可靠。6.3 節(jié)點(diǎn)維護(hù)和版本升級(jí)的推薦順序日常運(yùn)維里最常見的兩個(gè)動(dòng)作是節(jié)點(diǎn)維護(hù)和PostgreSQL小版本升級(jí)。節(jié)點(diǎn)維護(hù)時(shí)正確流程是先cordon再drain。cordon讓調(diào)度器不再往該節(jié)點(diǎn)調(diào)度新Poddrain則會(huì)把已有Pod重新調(diào)度走。由于我們配置了PDB和反親和drain時(shí)Operator會(huì)等待故障轉(zhuǎn)移完成后才允許刪除Pod不會(huì)出現(xiàn)同時(shí)停兩個(gè)副本的情況。我的一次實(shí)測(cè)里兩節(jié)點(diǎn)同時(shí)維護(hù)耗時(shí)因?yàn)镻DB保護(hù)而被拉長(zhǎng)了一些但換來的是集群全程沒有單點(diǎn)故障這個(gè)等待是值得的。PostgreSQL小版本升級(jí)更簡(jiǎn)單修改Cluster里的imageName指向新版本鏡像apply之后Operator會(huì)按“先備庫(kù)、后主庫(kù)”的順序滾動(dòng)更新。升級(jí)前一定要看release note尤其是大版本之間的升級(jí)跨大版本通常需要先做邏輯備份再重建集群不建議原地直接改鏡像。6.4 日常巡檢清單最后分享一份我每天會(huì)看的巡檢清單都是很輕量的命令但能提前暴露大多數(shù)隱患檢查項(xiàng)命令預(yù)期結(jié)果Pod狀態(tài)kubectl get pods -n database所有實(shí)例Ready且持續(xù)運(yùn)行集群健康度kubectl cnpg status postgres-ha主備角色正常WAL延遲接近0WAL歸檔是否積壓查看對(duì)象存儲(chǔ)上的WAL目錄沒有大量未歸檔文件備份是否成功kubectl get scheduledbackup -n database最后一次備份狀態(tài)為Completed存儲(chǔ)容量kubectl get pvc -n database使用率未超過告警閾值這套巡檢不需要專門做一個(gè)大平臺(tái)一條cron腳本就能把這些命令串起來輸出到一個(gè)統(tǒng)一的地方。我個(gè)人的經(jīng)驗(yàn)是高可用數(shù)據(jù)庫(kù)很少死于單次故障多半是死于日常小問題的累積——比如WAL歸檔悄悄失敗了一個(gè)星期、存儲(chǔ)使用率漲到90%沒人發(fā)現(xiàn)這些問題遠(yuǎn)比一次主庫(kù)宕機(jī)更致命。最后再分享一個(gè)小技巧數(shù)據(jù)庫(kù)這類有狀態(tài)服務(wù)的變更永遠(yuǎn)不要在業(yè)務(wù)高峰時(shí)段做??赡艿脑挵压收涎菥氁舶才旁诹璩?。你會(huì)感謝那個(gè)在夜深人靜時(shí)給自己留出試錯(cuò)空間的決定。