Files
awoooi/docs/runbooks/PUBLIC-MAINTENANCE-FALLBACK-RUNBOOK.md
Your Name a94ddd566e
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 58s
CD Pipeline / build-and-deploy (push) Successful in 4m27s
CD Pipeline / post-deploy-checks (push) Successful in 1m44s
fix(reboot): harden public maintenance fallback
2026-07-03 04:28:11 +08:00

101 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.conf``k8s/nginx/awoooi-prod.conf` 已固定 `proxy_intercept_errors on``error_page 502 503 504 =503 /__awoooi-maintenance.html``X-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 == 1``awoooi_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-maintenance``Retry-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。
固定驗證命令:
```bash
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_fallback`L0 Nginx intercept 未生效,下一步只能做 staging vhost / `nginx -t` / header verifier不得用 production app restart 當測試。
- `public_route_unreachable_without_external_l1_fallback`edge 或外部路徑整段不可達,下一步是 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 verifier`scripts/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-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 時生效。