chundev
日期:2026-03-23 環境:K3s + CNPG + NFS (NAS)
「備份有在跑」不等於「備份能用來還原」。設計了一個自動化 DR 演練腳本:在隔離的 K8s namespace 中從 NFS 上的 pg_dump 還原資料庫 + 部署 App 驗證連線,全程自動化(make chaos-dr-rebuild),16 秒內完成。三個關鍵設計決策:Secrets 不需要備份(Operator 自動重建)、用 NodePort 讓演練中的 App 可從瀏覽器存取、EXIT trap 確保異常中斷也會清理。
備份策略已到位(增量 + 完整 + pg_dump),但從未驗證過「如果所有 PVC 全毀,能不能真的從 NFS 備份重建整個系統」。需要一個自動化的 DR 演練,證明備份不只是「有在跑」而是「真的能用」。
在 dfaa-dr-test namespace 中執行所有操作:
這是最重要的設計決策。盤點後發現:
| Secret | 來源 | 重建方式 |
|---|---|---|
| PG 認證 | CNPG Operator 自動產生 | 建新 cluster 時自動重建 |
| ES 認證 | ECK Operator 自動產生 | 建新 ES 時自動重建 |
| TLS 憑證 | Cert-Manager / Operator | 自動重建 |
| App 密碼 | 寫在 Makefile config 裡 | 從 config 重建 |
結論:不需要備份 Secrets 到 NFS(反而是安全風險),只要確保能重建。
光驗證 PG 資料完整不夠 — 客戶會問「App 能不能連上」。演練流程需要:
1. 記錄正式環境基準(表行數)
2. 建立 dfaa-dr-test namespace
3. 部署臨時 PG Pod(NFS volume 直接掛載,不用 PVC)
4. 從最新 pg_dump 還原
5. 驗證關鍵表行數(與正式環境比對)
6. 建立 PG Service + App Secrets + ConfigMap
7. 部署 App Pod(指向 DR PG)
8. HTTP health check
9. 暴露 NodePort(讓瀏覽器確認)
10. 寫入演練結果到 ConfigMap(供 Dashboard 顯示)
11. 清理 namespace
# DR 演練(安全,隔離 namespace)
make chaos-dr-rebuild
# 清理殘留(如果演練中途失敗)
make chaos-dr-cleanup
# 生產環境還原(危險,需三重確認)
make prod-restore
PVC 不能跨 namespace 共享。DR namespace 需要存取正式環境的 NFS 備份,用 NFS volume 直接掛載:
volumes:
- name: backup-nfs
nfs:
server: <nas-ip>
path: /volume1/backup/postgres
readOnly: true
如果叢集有混合架構(amd64 + arm64),App image 通常只有 amd64 版本,需要加 nodeSelector 避免被排程到 arm64 節點:
nodeSelector:
kubernetes.io/arch: amd64
否則會看到 exec format error。
cleanup() {
kubectl delete ns "$DR_NS" --ignore-not-found --wait=false
}
trap cleanup EXIT
即使 Ctrl+C 中斷、腳本 error、或任何異常退出,都會執行清理。避免殘留的 namespace 和 NodePort 佔用資源。
if [ -t 0 ]; then
# Terminal — 等使用者確認
info "按 Enter 確認後將清理 DR 環境..."
read -r
else
# Pipe/CI — 自動繼續
sleep 5
fi
讓腳本在 CI/CD 環境也能自動執行。
DR 演練是安全的(隔離 namespace),但實際的生產還原需要額外的防呆:
# 偵測 PG 是否存活
PG_PRIMARY=$(kubectl get pods -l "cnpg.io/cluster=postgres-cluster,role=primary" ...)
if [ PG is Ready ]; then
SCENARIO="B" # 回滾
else
SCENARIO="A" # 從零重建
fi
echo "請輸入 namespace 名稱確認:"
read CONFIRM_NS # 必須完全匹配
echo "請輸入 DESTROY 確認:"
read CONFIRM # 必須輸入 DESTROY
# 還原前先把當前資料 dump 出來
pg_dump ... | gzip > /tmp/pgdump-safety-$(date +%s).sql.gz
echo "如果出問題,用這個備份恢復"
CNPG 的 streaming replication 連線無法中斷,DROP DATABASE 會永遠失敗。改用:
DROP SCHEMA public CASCADE;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO app;
效果相同(清空所有表),但不需要斷開連線。
| 指標 | 值 |
|---|---|
| 總 RTO | 16 秒(含建 namespace、PG Pod、還原、App 驗證、清理) |
| 表數量 | 153 張全部還原 |
| 關鍵表驗證 | 6/6 行數完全吻合 |
| App 啟動 | HTTP 3000 正常回應 |
| 備份大小 | 8.2 MB(pg_dump + gzip) |
| 還原耗時 | 2 秒 |
DROP DATABASE — replication 連線會阻止 DROP。改用 DROP SCHEMA public CASCADE 達到同樣效果。