Files
awoooi/docs/runbooks/PUBLIC-MAINTENANCE-FALLBACK-RUNBOOK.md
Your Name 84f6c6d4e3
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 56s
CD Pipeline / build-and-deploy (push) Successful in 4m16s
CD Pipeline / post-deploy-checks (push) Successful in 1m39s
fix(reboot): add edge maintenance fallback apply gate
2026-07-03 04:38:54 +08:00

7.8 KiB
Raw Blame History

Public Maintenance Fallback Runbook

目標

主機或 VM 重啟時public 網站不得直接露出空白 502。正式做法分兩層

  • L0 local edge110 / 188 / 120 / 121 的 Nginx 仍可回應時,用 ops/maintenance/maintenance.html 攔截 502 / 503 / 504,顯示靜態維護頁。
  • L1 external cloud/CDN如果 99 / VMware / 家用路由 / 對外 IP 整段不可達,由外部雲端主機或 CDN health check 切到同一份靜態維護頁。

必要特性

  • 維護頁不得讀取 .env、cookie、session、DB、K3s、Redis、NFS 或內部 API。
  • 維護頁必須是靜態檔,能被 Nginx、Cloudflare Pages、S3/R2 static hosting、GCS static site 或任一低成本外部 VM 直接服務。
  • L0 與 L1 使用同一份內容,避免使用者在不同故障層看到不同訊息。
  • 502 fallback 只能處理 upstream failure若整個 public edge unreachable必須由 L1 接手。

L0 受控套用

  1. ops/maintenance/maintenance.html 部署到 edge host 的 /var/www/maintenance/maintenance.html
  2. 在每個 public server {} 內 include ops/maintenance/nginx-502-maintenance-snippet.conf 的等價內容AWOOOI source conf ops/nginx/awoooi.wooo.work.confk8s/nginx/awoooi-prod.conf 已固定 proxy_intercept_errors onerror_page 502 503 504 =503 /__awoooi-maintenance.htmlX-AWOOOI-Fallback: local-maintenance。所有會 proxy_pass 到 AWOOOI API / Web 的 location 也必須明確帶同一組 proxy_intercept_errors / error_page,不得只依賴 server 層繼承。
  3. nginx -t 通過後才 reload Nginx。
  4. 用壞 upstream staging vhost 或暫存 upstream 驗證 X-AWOOOI-Fallback: local-maintenance,不得用 production DB / app restart 當測試手段。

L1 外部雲端/CDN 建議

優先順序:

  1. Cloudflare / CDN health check + fallback origin最快切換對使用者體驗最好。
  2. 低成本外部 VM可控性高能同時跑 blackbox probe需要 VM patching 與監控。
  3. Object storage static hosting成本最低適合維護頁DNS/CDN health check 仍要另外配置。

L1 active 條件:

  • probe_success 對主要 public routes 連續 2 分鐘失敗,或
  • awoooi_reboot_event_detected == 1awoooi_reboot_auto_recovery_slo_ready == 0 超過 10 分鐘,或
  • 99 / 110 / 188 / 120 / 121 / 112 任一 P0 host down 且 public route 同時 502 / timeout。

驗證

  • L0對測試 vhost 讀回 503 的維護頁 HTML且 header X-AWOOOI-Fallback=local-maintenanceRetry-After 存在probe 若讀到 502 / 503 / 504 但同時有 fallback header/body必須分類為 maintenance fallback不得算 raw 5xx。
  • L1從外部網路讀回靜態維護頁且不經過 99 / 110 / 188 / 120 / 121 / 112。
  • Recovery主要 upstream 連續 2 分鐘健康後切回正式 originTelegram 發送 recovery 通知,並在 cold-start scorecard 留下 readback。

2026-07-02 scorecard gate

2026-07-02 晚間 Gitea CD #4446 rollout 期間曾讀回 public 502,後續正式 health 回到 200。這證明「source 有維護頁」不足以代表使用者不會看到 raw 502驗收必須要求 runtime readback。

固定驗證命令:

python3 scripts/reboot-recovery/public-maintenance-fallback-probe.py \
  --output /tmp/public-maintenance-fallback.json
python3 scripts/reboot-recovery/reboot-auto-recovery-slo-scorecard.py \
  --public-maintenance-file /tmp/public-maintenance-fallback.json

判斷規則:

  • public_route_raw_5xx_without_maintenance_fallbackL0 Nginx intercept 未生效,下一步只能做 staging vhost / nginx -t / header verifier不得用 production app restart 當測試。
  • public_route_unreachable_without_external_l1_fallbackedge 或外部路徑整段不可達,下一步是 L1 external static origin / CDN health check decision record 與 rollback不直接切 DNS。
  • public_maintenance_fallback_runtime_readback_missing:沒有 runtime probe 證據,不得宣稱 public maintenance fallback 已完成。

2026-07-02 L0 source apply

已將 AWOOOI edge Nginx source 從 legacy /50x.html 改為 L0 maintenance fallback

  • ops/nginx/awoooi.wooo.work.conf
  • k8s/nginx/awoooi-prod.conf
  • ops/maintenance/nginx-502-maintenance-snippet.conf

驗收仍必須分兩層:

  • Source verifierscripts/reboot-recovery/tests/test_reboot_p0_operational_contract.py 必須確認上述 source 不可退回 /50x.html
  • Runtime verifier在 edge host 用 staging vhost / nginx -t / header readback 證明 X-AWOOOI-Fallback=local-maintenance;通過前不得宣稱 production runtime 已接管 raw 502。

2026-07-03 API / SLO cutover raw 502 hardening

Gitea CD #4517 在 deploy marker 切換期間再次讀到 production API / SLO route 短暫 502,且沒有 maintenance body / fallback header。修正口徑

  • error_page 必須使用 =503,讓維護頁帶明確 503 / Retry-After,而不是回傳裸 502
  • AWOOOI API、SSE、WebSocket、front-end 與 static proxy location 必須各自明確宣告 proxy_intercept_errors on 與同一條 error_page
  • public-maintenance-fallback-probe.py 必須把帶 X-AWOOOI-Fallback=local-maintenance 與維護頁 body 的 502 / 503 / 504 判成 fallback serving只有缺 header/body 的 5xx 才是 raw_5xx_without_maintenance_fallback
  • 驗收仍以 marker-time public route watch 為準source verifier 通過不等於 live Nginx runtime 已 reload。

2026-07-03 #4519 live edge drift

#4519 部署 source a94ddd5deploy marker 3aca484 chore(cd): deploy a94ddd5 [skip ci] 已推上 Gitea mainbuild log 也讀到 production deploy readback matched但 marker 後 public probe /tmp/awoooi-public-maintenance-fallback-after-4519-20260703-0433.json 仍回:

  • https://awoooi.wooo.work/api/v1/healthHTTP 502
  • X-AWOOOI-Fallback:空
  • maintenance bodyfalse
  • raw_5xx_without_fallback_count=1

live edge readback

  • 188 readable config/etc/nginx/sites-enabled/awoooi.wooo.work.conf
  • config 仍走 VIP 192.168.0.125:32334/32335
  • config 缺 proxy_intercept_errors / error_page 502 503 504 =503 / fallback header
  • /var/www/maintenance/maintenance.html 缺失
  • ollama@192.168.0.188 無 passwordless sudo不能由一般 SSH channel apply

因此 P0-2 目前 blocker 不是 app deploy而是 live edge source-to-runtime drift。下一步固定

scripts/reboot-recovery/public-maintenance-edge-fallback-apply.sh --check

SUDO_N_READY=1,才能執行:

scripts/reboot-recovery/public-maintenance-edge-fallback-apply.sh --apply
python3.11 scripts/reboot-recovery/public-maintenance-fallback-probe.py --timeout 8 \
  --output /tmp/public-maintenance-fallback-after-edge-apply.json

SUDO_N_READY=0,不得讀密碼、不得重啟 Nginx、不得用 app restart 當測試;需走 console/root-controlled apply 或維護窗口。

2026-07-02 API runtime artifact overlay

/api/v1/agents/reboot-auto-recovery-slo-scorecard 不得只依賴 committed snapshot 判斷 public maintenance fallback它必須能讀取 reboot-auto-recovery-slo-exporter.sh 產出的 runtime artifact。

讀取順序:

  1. AWOOOI_PUBLIC_MAINTENANCE_FALLBACK_READBACK_FILE 指定的 JSON。
  2. /home/wooo/reboot-recovery/reboot-auto-recovery-slo-*/public-maintenance-fallback.json 中最新一份。
  3. 找不到 artifact 時維持 fail-closed public_maintenance_fallback_runtime_readback_missing

驗收:

  • Production API 讀回 readback.public_maintenance_runtime_readback_present=true
  • Production API 讀回 readback.public_maintenance_fallback_ready=true
  • active_blockers 不包含 public_maintenance_fallback_runtime_readback_missing
  • 若 public routes 都正常,這只代表「沒有裸 5xx 且 runtime artifact 已接上 scorecard」仍需 staging vhost / nginx -t / header readback 才能宣稱 L0 intercept 在 upstream failure 時生效。