Share Notes

chundev

View the Project on GitHub latteouka/share-notes

K8s 災難恢復演練設計:從 NFS 備份到隔離 namespace 全重建

日期:2026-03-23 環境:K3s + CNPG + NFS (NAS)


TL;DR

「備份有在跑」不等於「備份能用來還原」。設計了一個自動化 DR 演練腳本:在隔離的 K8s namespace 中從 NFS 上的 pg_dump 還原資料庫 + 部署 App 驗證連線,全程自動化(make chaos-dr-rebuild),16 秒內完成。三個關鍵設計決策:Secrets 不需要備份(Operator 自動重建)、用 NodePort 讓演練中的 App 可從瀏覽器存取、EXIT trap 確保異常中斷也會清理。


背景

備份策略已到位(增量 + 完整 + pg_dump),但從未驗證過「如果所有 PVC 全毀,能不能真的從 NFS 備份重建整個系統」。需要一個自動化的 DR 演練,證明備份不只是「有在跑」而是「真的能用」。


設計原則

隔離 Namespace 演練(不影響生產)

dfaa-dr-test namespace 中執行所有操作:

Secrets 不需要備份

這是最重要的設計決策。盤點後發現:

Secret 來源 重建方式
PG 認證 CNPG Operator 自動產生 建新 cluster 時自動重建
ES 認證 ECK Operator 自動產生 建新 ES 時自動重建
TLS 憑證 Cert-Manager / Operator 自動重建
App 密碼 寫在 Makefile config 裡 從 config 重建

結論:不需要備份 Secrets 到 NFS(反而是安全風險),只要確保能重建。

App 啟動驗證

光驗證 PG 資料完整不夠 — 客戶會問「App 能不能連上」。演練流程需要:

  1. 在 DR namespace 建立 PG Service(讓 App 找到 PG)
  2. 建立最小化 Secrets 和 ConfigMap
  3. 部署 App Pod(同一個 container image)
  4. HTTP health check 確認 App 回應
  5. 透過 NodePort 讓瀏覽器實際存取

演練腳本流程

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

Makefile 整合

# DR 演練(安全,隔離 namespace)
make chaos-dr-rebuild

# 清理殘留(如果演練中途失敗)
make chaos-dr-cleanup

# 生產環境還原(危險,需三重確認)
make prod-restore

關鍵實作細節

NFS 直接掛載(不用 PVC)

PVC 不能跨 namespace 共享。DR namespace 需要存取正式環境的 NFS 備份,用 NFS volume 直接掛載:

volumes:
- name: backup-nfs
  nfs:
    server: <nas-ip>
    path: /volume1/backup/postgres
    readOnly: true

amd64 NodeSelector

如果叢集有混合架構(amd64 + arm64),App image 通常只有 amd64 版本,需要加 nodeSelector 避免被排程到 arm64 節點:

nodeSelector:
  kubernetes.io/arch: amd64

否則會看到 exec format error

EXIT Trap 確保清理

cleanup() {
  kubectl delete ns "$DR_NS" --ignore-not-found --wait=false
}
trap cleanup EXIT

即使 Ctrl+C 中斷、腳本 error、或任何異常退出,都會執行清理。避免殘留的 namespace 和 NodePort 佔用資源。

互動模式 vs 非互動模式

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

場景 B 的三重確認

echo "請輸入 namespace 名稱確認:"
read CONFIRM_NS  # 必須完全匹配

echo "請輸入 DESTROY 確認:"
read CONFIRM      # 必須輸入 DESTROY

場景 B 的安全備份

# 還原前先把當前資料 dump 出來
pg_dump ... | gzip > /tmp/pgdump-safety-$(date +%s).sql.gz
echo "如果出問題,用這個備份恢復"

CNPG 下的 DROP DATABASE 替代方案

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 秒

學到的事


參考資料