903 lines
71 KiB
Markdown
903 lines
71 KiB
Markdown
# AWOOOI 重開機恢復 SOP
|
||
|
||
> **版本**: v5.31
|
||
> **最後更新**: 2026-07-11 (台北時間)
|
||
> **更新者**: Codex
|
||
> **觸發事件**: 2026-07-11 真實全主機 reboot drill、late recovery 與 Agent99/VMware/runtime 閉環
|
||
|
||
---
|
||
|
||
## 目錄
|
||
|
||
1. [架構概覽與依賴圖](#架構概覽與依賴圖)
|
||
2. [自動化腳本狀態](#自動化腳本狀態)
|
||
3. [正常重啟流程 (計劃性維護)](#正常重啟流程)
|
||
4. [異常重啟流程 (緊急恢復)](#異常重啟流程)
|
||
5. [各主機詳細啟動序列](#各主機詳細啟動序列)
|
||
6. [常見故障排查手冊](#常見故障排查手冊)
|
||
7. [E2E 驗證腳本](#e2e-驗證腳本)
|
||
|
||
---
|
||
|
||
## 架構概覽與依賴圖
|
||
|
||
### 2026-07-11 真實 reboot drill:SLA breach 與 late recovery 真相
|
||
|
||
本輪不是模擬。重啟前 Agent99 runtime `7cf83f7f59a5d72ce6c303f59c144bd5bf8566df` 已 `14/14` matched、核心 task `9/9`,production-principal `Recover` baseline 已通過 full-SOP coordinator,且正確標示 `scope=service_recovery`、`rebootSloClaimed=false`。七主機與六核心路由在 observer 開始時均可達。
|
||
|
||
真實重啟後,`2026-07-11T08:00:07Z` 五台 VMware guests 與六核心路由離線,T+10 `08:10:07Z` 時服務仍未恢復,因此本輪 SLA 明確失敗。後續交叉比對 Windows boot time 證明 99 實際於 `16:08:03+08:00` 開機;舊 observer 將 host probe 從 `nc` 改成 `curl telnet://` 後,TCP 已連線仍等待 SSH payload 到 timeout,導致大量 host-down false negative。這些 host samples 不再作為「99 沒有回來」的證據,route-down samples 與 T+10 breach 仍有效;修正版 observer 已改用 Python `socket.create_connection`。
|
||
|
||
真正延遲鏈是:Agent99 Startup-Recovery 於 `16:08:57` 啟動,卻被原本 `PT10M` task limit 終止;boot event 又在 terminal recovery 前被消耗。VMware autostart task 直到 `16:26:48` 才執行,188 PostgreSQL 到 `16:40:05` 才 ready,120/121 K3s 在此之前持續等待 `192.168.0.188:5432`。120 與 121 又分別在 `16:49:32`、`16:56:00` 收到 Windows 99 VMware VIX `SoftReboot`;這不是 guest crash,也不是 unattended-upgrades。VMware process audit 未啟用,不能歸屬 actor;任何後續 reset/stop/suspend 都必須有同一 trace、before evidence、verifier 與 Telegram/KM receipt。
|
||
|
||
服務於 `16:59` 後恢復,SignOz upstream bridge 受控修復後 public route 也回 200。`17:35` production-principal Agent99 `Recover` 用 `10.3` 秒完成 service-recovery,七主機、VMware、Harbor、K3s、service/data/freshness/backup/188 hygiene/full-SOP coordinator 全通過,且 `rebootSloClaimed=false`;`17:37` 全部 23 個 public surfaces 為預期 2xx/3xx。機器可讀完整 receipt 在 `docs/operations/agent99-full-host-reboot-drill-20260711.snapshot.json`。
|
||
|
||
因此正式 SOP 固定為三層,不能再把 99 本機排程當成完整自動化:
|
||
|
||
1. **L0 外部層(99/LAN 之外)**:每 15 秒 public synthetic,連續兩輪 outage quorum 後建立 durable incident;執行繁中 Telegram down、外部 maintenance origin、具 verifier 的 99 out-of-band power。WOL 只可作第一個低風險嘗試,不是唯一電源路徑。
|
||
2. **L1 Agent99/VMware 層**:99 回線後由 L0 投遞同一 incident;Agent99 驗證 Windows boot、runtime manifest、tasks、VMware service、VMX、lock,依序恢復並驗證 110/188/112/120/121。111 是獨立實體 Mac。
|
||
3. **L2 110 full-SOP 層**:110 回線後建立 fresh scorecard,驗證七主機、service/data/freshness/backup、188 hygiene、edge/maintenance、VMware/update policy、版本與 callback。只有 blocker=0 且 `can_claim_slo=true` 才可撤維護頁與結案。
|
||
|
||
L0 source contract 是 `scripts/reboot-recovery/external-l0-recovery-supervisor.py`、`awoooi-external-l0-recovery-supervisor.service/.timer` 與 `ops/reboot-recovery/external-l0-recovery-supervisor.example.json`。Callback 只能位於 `/usr/local/libexec/awoooi-l0` allowlist,使用 direct exec 與 `--event-file`,禁止 arbitrary shell;Telegram/token、maintenance provider 與電源憑證留在外部 host-local wrapper,不寫入 repo/evidence。未部署外部 runtime、OOB power 與 maintenance callbacks前,正式狀態是 `slo_breached_recovered_late`:目前服務正常,不代表下一次全斷可在 10 分鐘內自動恢復。
|
||
|
||
### 2026-07-11 P0 Cold-start 實機收旂
|
||
|
||
最新完成 artifact `/home/wooo/reboot-recovery/reboot-auto-recovery-slo-20260711-172623` 回報 `GREEN_WITH_EVIDENCE_WARNINGS`、readiness `87%`、七主機 reachable、service/data/backup/post-start/188 hygiene 與 Windows99 VMware `5/5` 皆綠。唯三 blockers 是 `all_required_hosts_not_in_10_minute_reboot_window`、`fresh_all_host_reboot_event_missing`、`host_boot_observation_older_than_target_window`;它們保留本輪逾時真相,只能由下一次真實全主機 reboot 的同一 10 分鐘 window 驗收,不能靠事後健康讀回清除。
|
||
|
||
- 99 VMware required guest set 是 `110/188/120/121/112`。111 是 M1 Pro MacBook Local Ollama,已用 110 public key + forced command 回傳 Darwin boot/uptime/Ollama status,不授權 shell。舊版把 111 當 Windows99 VMX 的敘述依 live inventory 標記為 superseded。
|
||
- 110 runner fail-closed authority 只能使用 `flock` + `timeout` + `--ensure`,不得每分鐘無條件 mask + daemon-reload。完成條件是 apply/reload count 為 0 且 PID 1 CPU 收旂。
|
||
- edge fallback 只走 `/usr/local/sbin/awoooi-edge-maintenance check|apply|rollback-latest`;canonical/live SHA parity、backup、`nginx -t`、reload、public route verifier 缺一不可完成。`/api/sentry-tunnel` 必須在 generic `/api/` 前且 proxy 到 web。
|
||
- 111 host probe 只讀 `boot_id=darwin_<epoch>` / uptime / LaunchAgent / local Ollama;`UNKNOWN_UPTIME_HOSTS=[]` 才可進 reboot-event detector。
|
||
- SLO timer 使用 `OnBootSec=2min` + `OnUnitInactiveSec=5min`;禁止恢復 `OnUnitActiveSec=2min`,否則 2-3 分鐘 oneshot 會連續排程並製造 CPU / 告警噪音。
|
||
- 188 production PG 由 `k3s-postgres-recovery` host-network container 管理,host `start.conf=manual`。`awoooi-host188-hygiene reconcile` 只安裝 startup source/unit、daemon-reload、reset stale failed state,禁止產品 restart。startup 內 containerd/Docker metadata 刪除、kine write、ClawBot rebuild、188 legacy SignOz 均預設 fail-closed/opt-in。
|
||
- 2026-07-10 Stock gap 來自 TWSE 颱風休市,不是 ingestion 中斷。交易日曆修正後 freshness/ingestion 皆 green;不得對休市日無限重跑 ETL。
|
||
|
||
下一次 reboot drill 必須在 T+0 由 boot-id 變更自動觸發 Agent99/110 SLO lane,T+10 前產生同一 run 的 VM power、host uptime、services/routes、Stock freshness、backup、Telegram lifecycle 與 KM writeback receipt。若任一主機不在 window,不得宣稱 10 分鐘 SLA 完成。
|
||
|
||
### 2026-07-11 Agent99 full-SOP coordinator
|
||
|
||
舊版 Agent99 `Recover` 只驗證 `vm_start / host_reachability / harbor_registry / k3s_workloads / public_routes`,live evidence 曾在 10.4 秒寫入 `withinSlo=true`。這只能證明既有服務 baseline,不能證明全主機 reboot SLO;v5.28 起標記為 superseded,`full_sop_scorecard` 是必要 phase。
|
||
|
||
`recoveryCoordinator` 固定從 99 以專用 Agent99 SSH identity 讀 110 最新 `/home/wooo/reboot-recovery/reboot-auto-recovery-slo-*` 的 bounded summary,不讀 raw log、`.env`、secret、session 或 backup contents。必要輸入是 `summary.txt`、`host-probe.txt`、`public-maintenance-edge-fallback.txt`、`windows99-vmware-verify.txt` 與 `scorecard.json` 的 allowlisted fields。
|
||
|
||
一般告警觸發的服務恢復,至少要通過 7 host reachable、`SERVICE_GREEN=1`、`PRODUCT_DATA_GREEN=1`、Stock freshness `ok`、`BACKUP_CORE_GREEN=1`、188 service/hygiene green、edge fallback ready、VMware ready、Windows Update policy ready、source controls 與 public fallback ready。此時 scope 只能是 `service_recovery`,Telegram 必須明示「沒有 fresh reboot event,不宣稱全主機重啟 SLA」。
|
||
|
||
若 99 boot age 小於 10 分鐘或 boot identity 改變,scope 強制為 `full_host_reboot`,Agent99 最多等待 420 秒讓 110 的 `OnBootSec=2min` SLO lane 完成。除上述 baseline 外,還必須同時符合:artifact timestamp 新於本次 recovery start、`fresh_reboot_window_observed=true`、active blocker `0`、`can_claim_all_services_recovered_within_target=true`。任一缺口都要 fail-closed,`Get-AgentOutcomeContract` 的 `full_sop_coordinator_verified` 不得通過。
|
||
|
||
部署前 gate 是:PowerShell parse、Agent99 contract、synthetic fresh/blocked fixture、runtime staging hash;部署後 gate 是:`scripts/reboot-recovery/agent99-live-preflight.ps1` 回 `9/9` tasks、relay listener、queue zero、required evidence fresh、runtime manifest matched,再由 production S4U principal 觸發 `Recover`。Administrator 直接跑 launcher 不能取代 production-principal verifier。
|
||
|
||
2026-07-11 20:09 final runtime receipt:Agent99 已原子 promote 為 `d0e364bfa42947190cf1e88d1d85623e23f84858`,deploy evidence `C:\Wooo\Agent99\evidence\agent99-deploy-20260711-200939.json`、rollback backup `C:\Wooo\Agent99\deploy\backup-20260711-200939`;runtime manifest `14/14`、mismatch `0`。production-principal evidence `C:\Wooo\Agent99\evidence\agent99-Recover-20260711-200320.json` 顯示七個 phases 全部 `ok`,新增必要 phase `host112_guest_readiness`,before/after verifier 均為 ready、`applyAttempted=false`,transport 為 direct serialized,recovery completed、coordinator verified,elapsed `44.6s`,且正確維持 `scope=service_recovery`、`rebootSloClaimed=false`。SRE routing self-test `17/17`、Telegram ingest self-test `16/16`,後者不寫 alert、不送 Telegram。這是 service recovery closure,不覆蓋原始 T+10 breach。
|
||
|
||
Windows 99 VMware task 已經由 `scripts/reboot-recovery/windows99-vmware-autostart-controlled-apply.ps1` 正式受控套用:DryRun、Apply、Verify 同一 receipt;task XML、舊 startup script、Windows Update policy 與 service start modes 都有 rollback。Evidence `C:\Wooo\Agent99\evidence\windows99-vmware-autostart-apply-20260711-173505.json` 回報 pre/post VMX process 都是 `5`、running guest rows `5`、process-aware duplicate-start guard true、沒有 host reboot、沒有 VM power change、沒有 secret read。這才是 Agent99 與重啟 SOP 的 runtime 整合證據;仍不取代下一次 600 秒 E2E drill。
|
||
|
||
### 112 Kali 真正開機完成 Gate
|
||
|
||
112 一直存在於 Windows99 required VM inventory、host probe 與 reboot event required host set;本次事故暴露的缺陷是舊流程只用 VMX process、Ping、SSH port 或 node-exporter 判定「已開機」,沒有驗證 Kali 的圖形登入與安全服務。這會讓 112 實際以 `multi-user.target` 無 GUI 啟動、LightDM 未執行且 Wazuh Indexer 開機逾時時仍被錯誤判成綠燈。另一個獨立現象是 SYSTEM principal 以 `vmrun ... nogui` 啟動的 VMX 不會出現在 Administrator 的 VMware Workstation session,GUI 可能顯示 `Powered off`;在 VMX process 與 guest boot identity 已存在時,禁止因此再次按 Power on,避免 exclusive lock 與重複啟動。
|
||
|
||
112 的固定完成鏈改為:`VMX process -> guest boot_id/uptime -> SSH forced-command readback -> systemd running -> graphical.target -> LightDM/display-manager -> Xorg -> open-vm-tools -> Wazuh Indexer/Manager/Dashboard/Filebeat -> recovery timer -> Console-ready`。`scripts/reboot-recovery/host112-guest-readiness.sh` 提供 no-secret `--check` 與 allowlisted `--apply`;apply 只可補起 graphical target、LightDM、VMware Tools,以及在開機 210 秒後、600 秒 cooldown 內重試一次 Wazuh Indexer。`awoooi-host112-guest-recovery.timer` 於開機 20 秒後開始、每 60 秒檢查;不得 reboot host、reset/power cycle VM、修改 firewall、讀取 secret 或執行資料庫寫入。
|
||
|
||
110 到 112 使用來源限制、`restrict` 與 forced command 的既有公鑰,只能執行 `/usr/local/sbin/awoooi-host112-guest-readiness --check`,不能取得 shell 或 forwarding。Host probe 必須輸出 `console_ready=1`、`services_ready=1`、`recovery_timer_ready=1`、`guest_ready=1`;任何一項缺失都建立具名 `host112_*` blocker,阻止 full-host recovery 與 maintenance withdrawal。Prometheus 固定輸出 `awoooi_host112_guest_readback_present`、`awoooi_host112_console_ready`、`awoooi_host112_services_ready`、`awoooi_host112_recovery_timer_ready`、`awoooi_host112_guest_ready`,Agent99 收到狀態變更後執行受控修復並以同一 incident 回報 verifier 結果。
|
||
|
||
2026-07-11 runtime closure:installer runs `host112-guest-recovery-20260711T193137+0800-35698` 與 `host112-guest-recovery-20260711T195449+0800-43505` 已完成備份、受限公鑰、exact-command sudo、timer 與 durable rollback installer,未 reboot、未改 VM power、未讀 secret。110 artifact `/home/wooo/reboot-recovery/reboot-auto-recovery-slo-20260711-200338` 讀回 112 `systemd=running`、Console/Wazuh/timer/guest `1/1/1/1`、五項 Prometheus gauges `5/5`、`host112_* blocker=0`。兩條 112 告警規則 health `2/2 ok` 且 healthy state 為 inactive;source/canonical/active/runtime rule SHA 均為 `86fd0e56cb7633da2a5a09960b8bf2dd61ee917c55072b5a16ba68577e8379cc`。Windows99 verifier 同輪讀到 `VM_PROCESS alias=112 process_id=22288 session_id=0 owner=NT_AUTHORITY\SYSTEM ui_truth=runtime_running_gui_session_isolated`,因此 operator power truth 固定信任 VMX process + guest readiness,不信任跨 principal 的互動式 VMware UI。Agent99 `Recover` 也已把 112 納入必要 phase 並完成 production-principal verifier;完整 receipt 見 `docs/operations/host112-guest-recovery-20260711.snapshot.json`。Agent99 tests `49 passed`、reboot-recovery tests `142 passed`。目前 scorecard 僅保留 `all_required_hosts_not_in_10_minute_reboot_window`、`fresh_all_host_reboot_event_missing`、`host_boot_observation_older_than_target_window`;它們只能由下一次真實全主機 600 秒 drill 關閉。
|
||
|
||
2026-07-11 20:25 Agent99 false-green closure:Windows runtime 已原子部署 `06509e071c8b7c538feb112e4e4774ee22abf7d1`,deploy receipt `agent99-deploy-20260711-202021.json`、rollback `backup-20260711-202021`、manifest `14/14` matched。新版 preflight 為 tasks `9/9`、required evidence fresh/usable `7/7`、parse/content failure `0`。production-principal run `agent99-recovery-20260711-202159-3404b5b3` 在 42.9 秒完成 5/5 VM、5/5 host、5/5 public 與七個 phase,TG `sent=1`、`visualSent=1`,實際圖卡 `agent99-card-recovery_slo_result-20260711-202159.png` 為 1200×760 phase timeline;下一輪同狀態以 `dedupe_window` 抑制且留下 receipt。這關閉 Agent99 可見性假綠,不改寫原始全主機 T+10 breach。
|
||
|
||
### 2026-07-10 P0 Agent99 Transport / Verified Remediation 覆蓋
|
||
|
||
99 的 Agent99 是全主機恢復控制節點。Windows 開機後由既有 Agent99 scheduled tasks 啟動;`Status` 讀到 boot identity 變更時,只能用 single-flight queue 觸發一次 `Recover`,並以 10 分鐘 cooldown 避免併發恢復。每次執行必須寫入 `bootState`、`recoveryTrigger`、`recoveryCoordinator` 與 `recoverySlo` evidence。只有實際全主機重啟事件、七主機 reachability、必要服務、public routes、資料 freshness、備份、edge fallback、VMware/Windows policy 與 188 hygiene 全部在同一個 10 分鐘 window 通過,才可宣稱 reboot recovery SLO 達標。
|
||
|
||
Agent99 SSH transport 使用 99 本機專用 Ed25519 identity `C:\Wooo\Agent99\keys\agent99_ed25519`;private key 不得輸出、複製到 evidence、Telegram 或 repo。只允許將 public key 以 `from="192.168.0.99",restrict` 授權到 110 `wooo` 與 112 `kali`。固定 route map 為 110 / 112 direct,120 / 121 / 188 優先經 110 jump;禁止以共用密碼或任意 interactive shell 取代。Runtime 必須由 `agent99-deploy.ps1` 經 parse、contract、synthetic、staging、backup、atomic promote 與 rollback gate 部署,`runtime-manifest.json.sourceRevision` 必須等於 Gitea commit SHA。
|
||
|
||
所有 SSH command 都必須 bounded timeout。Timeout 時先用 `taskkill /T` 終止 process tree,再確認 process 是否仍存活;`Perf` / `LoadShed` controlled apply 執行前會清理只符合以下全部條件的 stale client:Agent99 allowlisted target、`BatchMode=yes`、沒有 `-N/-L/-R/-D` tunnel、超過 15 分鐘且 parent 已消失,或超過 60 分鐘 hard limit。guard 不記錄 command line,不碰互動式 SSH 或 tunnel,結果寫入 `sshProcessGuard` evidence。
|
||
|
||
Agent99 的 Heartbeat、Performance、手動 Status 或告警修復可能由不同 Windows process 同時啟動。所有 `Invoke-SshText` 必須先取得 `C:\Wooo\Agent99\state\ssh-transport.lock` 的 exclusive file handle,跨 scheduled task process 與 RDP operator session 一次只允許一條 Agent99 SSH command;程序結束時由 OS 自動釋放 handle。預設最多等待 90 秒,逾時回 `ssh_transport_lock_timeout`,不得再開新 client。若同輪五主機 ping 全通,但 Harbor、AWOOOI 與所有 performance readback 同時空白,優先判定為 transport contention,必須等序列化後重驗,不得宣稱所有服務同時故障。
|
||
|
||
99 的正式 Agent99 scheduled tasks 目前使用本機 `WOOO` 帳號的 S4U principal;專用 private key ACL 不授權 RDP 的 `Administrator`。因此 operator 直接執行 `agent99-run.ps1` 若出現 `Load key ... Permission denied`,只能判定為 execution-identity mismatch,不得判定五台主機或服務故障,也不得為了測試放寬 private-key ACL。正式 verifier 必須用 `schtasks /run /tn Wooo-Agent99-Heartbeat` 與 `Wooo-Agent99-Performance-Guard` 走既有 principal;evidence 必須包含 `transportSerialized` 與 `transportLockWaitMs`。長駐 `Wooo-Agent99-SRE-Alert-Relay` 若 task 為 `Running/ok`,不得因 start evidence 超過 1440 分鐘就製造 freshness warning;應記錄 `running_task_supersedes_stale_start_evidence`。
|
||
|
||
效能修復不得以 command exit code 取代 post-verifier。只要 action 配置 verifier,成功判定完全由 verifier 決定;verifier 失敗必須記為 failed,僅建立 10 分鐘 failure backoff,不得建立 success cooldown。policy 變更必須提升 `policyVersion`,由 deployer migration 更新 99 live config,避免舊 command 或 marker 繼續造成 false green。
|
||
|
||
本輪 live receipt:`agent99-load-shed-marker-correction.json` 已封存 188 舊 false-success marker,`agent99-stale-ssh-cleanup-v2.json` 已受控終止 7 個逾時 orphan SSH client;`agent99-Perf-20260710-224454.json` 顯示 110、112、120、121、188 全部 readback OK,route 分別為 direct、direct、via110、via110、via110。110 load/core `0.60`、disk `84%`;188 load/core `0.18`、disk 由 `90%` 降至 `88%`。這證明 transport、效能讀回與 verifier 修復 lane 已恢復,不等於已完成下一次實際全主機 cold-start 的 10 分鐘 SLA 驗收。
|
||
|
||
2026-07-11 production-principal concurrency receipt:同時觸發 Heartbeat 與 Performance Guard 後,`agent99-Status-20260711-004148.json` 顯示五台 `ping=true`、AWOOOI `coreHealthy=true` / `redisUp=true`、Harbor `registryHealthy=true` / `deployReady=true`;`agent99-Perf-20260711-004149.json` 顯示五台 `severity=ok`,110 / 112 / 120 / 121 / 188 load/core 為 `0.54 / 0.01 / 0.06 / 0.01 / 0.11`,disk 為 `84 / 53 / 49 / 27 / 89%`。同輪 public routes 全部 `200`。這是排程身份與並行 readback closure,仍不得取代下一次真實全主機重啟的 10 分鐘 SLA drill。
|
||
|
||
### 2026-07-10 P0 備份/Gitea 自動驗證覆蓋
|
||
|
||
每次 host reboot 後,備份 lane 必須依序執行 `/backup/scripts/backup-status.sh --no-notify`、`/backup/scripts/verify-gitea-backup.sh --no-notify`,再由 110 的每日排程執行 `/backup/scripts/gitea-bundle-backup-sync-188.sh`。最後讀回 110/188 `backup_health.prom`、Gitea restore status、bundle sync status、188 sample restore metric 與 Prometheus Backup/Gitea alerts;不得只看檔案 mtime、HTTP 200 或命令 exit code。
|
||
|
||
本輪 live 驗證已證明:110 `13/13`、188 `4/4`、core blocker `0`;Gitea full dump 為 `6928031514` bytes,CRC、DB/config、15/15 regular repo inventory digest 一致;12/12 product private bundles 已從 110 local container 產生並完整同步 188,remote SHA-256 `12/12`、sample clone/fsck 成功;Velero missed weekly dry-run 已由 `startingDeadlineSeconds=604800` 自動 backfill 成功。20:17 drift guard 自動修復 read-only bind-mount inode drift 後,source/canonical/active/runtime rule SHA 一致,145 rules 已載入,Backup/Gitea 非 inactive alerts 為空;20:20 正式 cron 回 `repaired=0` 且 container ID/StartedAt 未變,穩態無重複 recreate。
|
||
|
||
Prometheus rules 部署不得用會置換 bind source inode 的 `install` / atomic rename 直接覆蓋 `/home/wooo/monitoring/alerts.yml`。正式 guard 會先 promtool canonical,再以保留 host path 的方式更新 active;若 container runtime SHA 仍不同,才用 `/home/wooo/monitoring/docker-compose.yml` 單獨 `--no-deps --force-recreate prometheus` 重新掛載,TSDB volume 保留。post-verifier 必須同時通過 `145` rules health、runtime SHA、required rules 與 active alert query;失敗則還原舊 active file 並重建 Prometheus,不得只看 reload HTTP 200。
|
||
|
||
失敗處置固定為 fail closed:full dump 或 bundle incomplete 不得更新 complete symlink;remote sync 不使用 delete;restore drill 只能在隔離暫存目錄;不得讀 raw backup contents、secret 或 private repo 名稱,不得 restore over production、prune 或刪遠端備份。Telegram 只在狀態改變或失敗時送一張繁中摘要卡,成功心跳維持每日一次。
|
||
|
||
### 2026-07-09 P0 AI Log Triage / Controlled Auto-Recovery 覆蓋
|
||
|
||
主機重啟後不得再靠人工逐台看 log 才判斷異常。固定第一步改為執行 `scripts/reboot-recovery/ai-log-triage-auto-recovery.sh --apply`;若只允許讀取,則用 `--check`,但 scorecard / 告警仍必須吃同一份 artifact。此 runner 會 bounded 收集 110 Docker / containerd / Harbor / Nginx、120 / 121 K3s events、188 edge Nginx / Docker / systemd、99 VMware no-secret readback 與 public route HTTP status,然後輸出 `findings.tsv`、`actions.tsv`、`summary.txt`。
|
||
|
||
本輪 2026-07-09 16:23 orchestrator check evidence `/tmp/awoooi-full-host-reboot-orchestrator-codex/20260709T082305Z` 顯示 `RECOVERY_BLOCKED_P0`、`FINDING_COUNT=3`、`FINDING_P0_COUNT=2`,P0 依序為 `STOCK_UPSTREAM_NOT_READY host=110_188`、`EDGE_502_UPSTREAM_REFUSED host=188_edge`,另有 `HOST188_POSTGRES_SYSTEMD_FAILED` P2 hygiene。public route readback 為 AWOOOI health `200`、AWOOOI web `307`、Stock freshness `200`、Gitea `200`、registry `/v2/` `401`、Harbor `200`;因此不得沿用稍早 Harbor 5000 refused / K3s ImagePullBackOff 舊 artifact 當作目前 truth。若後續 registry 再度不 ready,K3s pod delete 只能被自動化標為 `skipped_registry_not_ready`,不得宣稱 502 已修復。
|
||
|
||
`awoooi-reboot-auto-recovery-slo.service` 必須設定 `AI_LOG_TRIAGE_AUTO_RECOVERY_MODE=apply`,並由 `reboot-auto-recovery-slo-exporter.sh` 在 host probe / readiness summary 前先跑 AI log triage。允許的 controlled apply 僅限:110 Docker inactive 時 start、Harbor watchdog `--repair-once` / harbor-log-first recovery、registry ready 後只刪 `ImagePullBackOff` / `ErrImagePull` pods 讓 Kubernetes 重建、188 nginx/docker inactive 時 start。仍禁止 reboot、node drain、firewall cutover、DB restore/drop/truncate、Docker prune、secret/session/.env/raw-volume reads。
|
||
|
||
### 2026-07-03 18:19 P0-4 VMX Backup / Source Search 覆蓋
|
||
|
||
最新 via-110 no-secret locator 已成功收斂為 `candidate_source_count=0`、`source_locator_status=blocked_missing_expected_vmx_source_no_candidate_found`,早先 `candidate_source_count=6` / generic Ubuntu candidate 僅保留為歷史 receipt,不再作為目前 relink 候選。`docs/operations/windows99-vmx-source-locator-receipt.snapshot.json` 已更新為 17:57 no-candidate truth。
|
||
|
||
新增 `/api/v1/agents/windows99-vmx-source-backup-search-package` 與 `scripts/reboot-recovery/collect-windows99-vmx-source-backup-search.sh`。此 package / collector 固定只做 filename / path-fingerprint 搜尋,不讀 VMX 內容、不輸出 raw path、不讀 secret、不寫遠端、不改 Windows、不啟停 VM、不重啟任何服務或主機。18:19 receipt:120 / 121 / local 均未找到 verified 111 source,99 alias 無 Linux backup host mapping,110 / 188 / 112 SSH BatchMode 不可用;`candidate_source_count=0`、`blocked_host_count=4`、`apply_allowed_by_this_package=false`。
|
||
|
||
P0-4 下一步固定為離線備份 inventory 或授權 console source path,取得 verified source path 後才可建立 scoped relink dry-run + rollback + post-verifier;不得猜 generic Ubuntu candidate、不得直接 relink、不得 restore、不得 power on VM、不得讀 Windows 密碼或 VMX 內容。
|
||
|
||
### 2026-07-03 17:34 P0-4 Verify Timeout 覆蓋
|
||
|
||
110→99 SSH public-key channel 可用,via-110 locator 已成功收集且未讀 secret / 未寫 Windows / 未改 VM power:`expected_vmx_path_present_count=0`、`discovery_root_present_count=2`、`candidate_source_count=0`、`source_locator_status=blocked_missing_expected_vmx_source_no_candidate_found`。同輪 full VMware Verify 在 90 秒曾 timeout,但 180 秒成功讀回 `MISSING_VMX_ALIASES=111`、`VMWARE_AUTOSTART_CONFIG_READY=0`、`VMWARE_AUTOSTART_VERIFY_READY=0`。
|
||
|
||
SOP v5.19 起,Windows99 remote verify 預設 timeout 使用 `180` 秒;仍屬 bounded no-secret verifier,超過 180 秒 fail-closed。不得把 90 秒 timeout 當成 SSH channel 故障,不得因此偏離到 VM power / Windows service restart / host reboot;P0-4 下一步仍是取得 111 既有 VMX source / verified backup path。
|
||
|
||
### 2026-07-03 17:18 P0-4 VMX Repair CLI 覆蓋
|
||
|
||
Gitea CD `#4617` 已完成 source `40713b2f8` 投產,deploy marker `b85d574`,production deploy readback matched=true,job succeeded。production reboot SLO scorecard 已讀回 Windows99 default target `111 / 188 / 120 / 121 / 112`,其中 `missing_vmx_aliases=["111"]`、`powered_off_aliases=["112","120","121","188","111"]`,舊 `110` 不得再當 Windows99 guest alias。
|
||
|
||
`build-windows99-vmx-source-repair-package.py` 是 no-secret / no-DB / check-mode 工具,必須能在工作站缺 `DATABASE_URL` 時輸出 `windows99-vmx-source-repair-111` package;這只表示 repair check-mode package ready,不表示 111 VMX 已恢復。via-110 locator 若回 `blocked_ssh_publickey_auth_missing` 或 `ssh_auth_probe_exit_status=124`,固定視為 management-channel blocker。下一步是取得 111 既有 VMX source / verified backup / 授權 console candidate identity,建立 scoped relink dry-run、rollback 與 post-verifier;不得猜 generic Ubuntu candidate、不得直接 relink、不得 restore、不得 power on VM、不得讀 Windows 密碼。
|
||
|
||
### 2026-07-03 15:57 P0-4 Windows99 Task Run-State 覆蓋
|
||
|
||
Windows99 no-secret via-110 collector 已讀回 `AWOOOI-Start-VMware-VMs` scheduled task:`present=1`、`enabled=1`、`state=Ready`、`trigger_count=1`、`action_count=1`,VMware service 與 Windows Update no-auto-reboot policy 也為 ready;但新增 task detail 顯示 `last_run_time=1999-11-30T00:00:00`、`last_task_result=267011`,等價於 `0x41303`,表示 task 尚未跑過 / 未在本輪 boot 觸發。2026-07-03 16:33 進一步確認舊 verifier default 把 `110` 誤列為 Windows99 guest;最新 source-of-truth 改為 `111 / 188 / 120 / 121 / 112`,以對齊本輪 host probe `111 unreachable` 與 99 VMware guest 需求。因此目前 P0-4 真 blocker 是 `windows99_vmware_guest_power_not_ready` 加 `windows99_vmware_autostart_task_never_ran`,且 deployment 後必須用新版 collector 重新讀回 111。
|
||
|
||
SOP v5.17 起,`windows99-vmware-autostart.ps1 -Mode Verify` 必須輸出 `VMWARE_AUTOSTART_TASK_DETAIL` 與 `VMWARE_AUTOSTART_TASK_ACTION`;110 textfile exporter 必須輸出 `awoooi_windows99_vmware_autostart_task_start_script_present`、`awoooi_windows99_vmware_autostart_task_last_result`、`awoooi_windows99_vmware_autostart_task_never_ran`;production scorecard / API 必須把 `windows99_vmware_autostart_task_never_ran` 放進 active blockers 與 action matrix。固定下一步是執行已建立的 Windows99 VMware autostart check-mode package,然後 rerun no-secret collector 與 reboot SLO scorecard;不得直接 power on VM、重啟 Windows host / VMware service、修改 scheduled task、修改 registry、讀 Windows 密碼或讀 secret。
|
||
|
||
### 2026-07-03 15:35 P0-2 API Readback 覆蓋
|
||
|
||
110 textfile / Prometheus 已有 `awoooi_public_maintenance_edge_fallback_ready{scope="public_routes"} 0` 與 active blocker `edge_public_maintenance_live_config_drift` 時,production API 必須把此 blocker 合併進 `active_blockers`。route probe 200 / route fallback ready 不能替代 edge Nginx fallback ready;只有 `edge_fallback_runtime_readback_present=true` 且 `edge_fallback_ready=true` 才能清掉 edge drift。
|
||
|
||
本輪 source fix 將 public maintenance blockers 拆成 route-level 與 edge-level 兩組:route readback present / route 0 raw 5xx 只清 route blockers;edge blocker 必須由 edge readback green 才能清。這是 readback overlay 修正,不授權讀密碼、Nginx reload、app restart、Docker / host restart、VM power change 或 DB / firewall 操作。
|
||
|
||
### 2026-07-03 15:13 P0-2 Runtime 覆蓋
|
||
|
||
110 SLO controlled install 已完成,`awoooi-reboot-auto-recovery-slo.timer` 為 `enabled/active`,service `Result=success`、`ExecMainStatus=0`。本輪只 stage SLO verifier source、啟動 verifier oneshot 與寫 textfile metric;未重啟 Docker / Nginx / K3s / DB / firewall,未讀 secret / token / `.env` / raw sessions / SQLite / auth。
|
||
|
||
installer v5.15 規定 payload 必須包含 `ops/nginx`。若缺少 `ops/nginx/awoooi.wooo.work.conf`,110 runtime edge check 會在本地 source gate 先失敗成 `local_nginx_conf_missing`,導致無法讀到 188 live edge truth;這是 verifier packaging bug,不是 Nginx runtime 狀態。完成條件是 110 artifact 內 `public-maintenance-edge-fallback.txt` 進入遠端 readback,且 blocker 必須是 live edge drift。
|
||
|
||
2026-07-03 15:13 live artifact `/home/wooo/reboot-recovery/reboot-auto-recovery-slo-20260703-151219` 已讀到 188 edge:`REMOTE_CONF_READABLE=1`、`LIVE_CONF_HAS_INTERCEPT=0`、`LIVE_CONF_HAS_ERROR_PAGE_503=0`、`LIVE_CONF_HAS_FALLBACK_HEADER=0`、`LIVE_CONF_HAS_INTERNAL_LOCATION=0`、`LIVE_CONF_HAS_API_VIP=1`、`LIVE_CONF_HAS_WEB_VIP=1`、`MAINTENANCE_FILE_PRESENT=0`、`SUDO_N_READY=0`、`EDGE_FALLBACK_READY=0`、`BLOCKER edge_public_maintenance_live_config_drift`。110 textfile 必須輸出 `awoooi_public_maintenance_edge_fallback_ready{scope="public_routes"} 0` 與 active blocker `edge_public_maintenance_live_config_drift`;目前 blocker count 為 `8`。
|
||
|
||
此狀態仍不授權 `--apply`。P0-2 完成需要 passwordless sudo / privileged edge apply channel、live conf backup、maintenance file install、`nginx -t`、reload 與 public route header verifier 全綠,或完成外部 L1 cloud/CDN fallback decision record。`SUDO_N_READY=0` 時不得讀密碼、不得 reload Nginx、不得重啟 app / Docker / host。
|
||
|
||
### 2026-07-03 14:40 P0-2 Public Maintenance 現況覆蓋
|
||
|
||
最新 main `3ebeeeb4b` 的 Gitea CD `#4592` 已推 deploy marker `ee0da93`,production deploy readback matched、job succeeded;deploy window 期間 AWOOOI API health 與 SLO route 曾回 raw `502 Bad Gateway`,沒有 `X-AWOOOI-Fallback`、`Retry-After` 或維護頁 body。後續 public route probe 自癒回 `200`,但 raw 502 exposure 仍必須列為 P0 public UX blocker。
|
||
|
||
live 188 edge check 的目前事實是:`LIVE_CONF_HAS_INTERCEPT=0`、`LIVE_CONF_HAS_ERROR_PAGE_503=0`、`LIVE_CONF_HAS_FALLBACK_HEADER=0`、`LIVE_CONF_HAS_INTERNAL_LOCATION=0`、`MAINTENANCE_FILE_PRESENT=0`、`SUDO_N_READY=0`、`EDGE_FALLBACK_READY=0`,blocker 固定為 `edge_public_maintenance_live_config_drift`。此 blocker 不授權 app restart、Docker restart、host reboot 或讀密碼;只能先 `public-maintenance-edge-fallback-apply.sh --check`,具備 passwordless sudo 後才可進受控 `--apply` + backup + `nginx -t` + reload + public header verifier,或走 L1 external static/CDN fallback decision record。
|
||
|
||
v5.14 起,110 SLO exporter 必須把 edge check 合併到 public maintenance readback,輸出 `awoooi_public_maintenance_edge_fallback_ready{scope="public_routes"}`;production scorecard / API 必須在 routes 當下 200 但 edge fallback 未套用時,仍顯示 `public_maintenance_fallback.ready=false` 與 active blocker `edge_public_maintenance_live_config_drift`。Alertmanager 規則 `PublicMaintenanceEdgeFallbackConfigDrift` 必須對此 metric 告警,避免下一次 deploy / reboot raw 502 才由使用者發現。
|
||
|
||
### 2026-07-03 14:26 P0 現況覆蓋
|
||
|
||
Gitea CD `#4589` 已完成 source `33c391808` 部署,deploy marker `9f49651 chore(cd): deploy 33c3918 [skip ci]` 已 fast-forward 回 `gitea-ssh/main`;public queue readback 顯示 `current_main_cd_run_status=Success`、`current_main_cd_deploy_evidence_succeeded_from_log=true`、`latest_visible_cd_production_deploy_readback_matched_from_log=true`、`latest_visible_cd_job_succeeded_from_log=true`、`no_matching_online_runner_visible=false`,且沒有 host pressure refusal。110 `awoooi-reboot-auto-recovery-slo.service` 最新 run `20260703-142122` 已 `Result=success`、`ExecMainStatus=0`,textfile metric 顯示 `awoooi_reboot_auto_recovery_slo_blocker_count=7`、`awoooi_windows99_vmware_autostart_config_ready=1`、`awoooi_windows99_vmware_windows_update_no_auto_reboot_ready=1`、`awoooi_windows99_vmware_missing_vmx_alias` 無 alias gauge,powered-off aliases 為 `110 / 112 / 120 / 121 / 188`。
|
||
|
||
Production `/api/v1/agents/reboot-auto-recovery-slo-scorecard` 已與 runtime metric 對齊:`status=blocked_reboot_auto_recovery_slo_not_ready`、`active_blocker_count=7`、`runtime_metric_active_blocker_count=7`、`runtime_metric_runtime_readback_added_blocker_count=0`、`readiness_percent=67`、`primary_blocker=reboot_event_required_host_unreachable`、`can_claim_all_services_recovered_within_target=false`。`windows99_vmware_autostart_config_not_ready` 與 `windows99_update_no_auto_reboot_policy_not_ready` 已不得再列為目前 blocker;若未來再次出現,優先判定為 metric/API overlay regression,不得轉成 Windows policy apply、service restart、VM power change 或 host reboot。
|
||
|
||
目前仍不得宣稱「全主機重啟後 10 分鐘內全部恢復」。剩餘 7 個 active blockers 固定為:
|
||
|
||
- `all_required_hosts_not_in_10_minute_reboot_window`
|
||
- `fresh_all_host_reboot_event_missing`
|
||
- `host_boot_observation_older_than_target_window`
|
||
- `host_unreachable_after_reboot`
|
||
- `host_uptime_unknown`
|
||
- `reboot_event_required_host_unreachable`
|
||
- `windows99_vmware_guest_power_not_ready`
|
||
|
||
固定下一步:維持 110 timer / textfile / production scorecard readback,優先補 fresh all-host reboot event / 10-minute window evidence / host reachability,以及 Windows99 powered-off guest 的 check-mode package 與 post-verifier;禁止讀 Windows 密碼、啟動或關閉 VM、host reboot、service restart、Docker / Nginx / K3s / DB / firewall restart、restore、prune、delete。
|
||
|
||
### 2026-07-03 14:10 P0 現況覆蓋
|
||
|
||
目前仍不得宣稱「全主機重啟後 10 分鐘內全部恢復」。Gitea CD `#4587` 已完成 source `30c89a0` 部署並推 deploy marker `4da6dda`;110 上 `awoooi-reboot-auto-recovery-slo.timer` 已 `enabled/active`,最新 textfile runtime metric 為 `active_blocker_count=7`,primary blocker `reboot_event_required_host_unreachable`,`can_claim_all_services_recovered_within_target=false`。7 個 runtime active blockers 固定為:
|
||
|
||
- `all_required_hosts_not_in_10_minute_reboot_window`
|
||
- `fresh_all_host_reboot_event_missing`
|
||
- `host_boot_observation_older_than_target_window`
|
||
- `host_unreachable_after_reboot`
|
||
- `host_uptime_unknown`
|
||
- `reboot_event_required_host_unreachable`
|
||
- `windows99_vmware_guest_power_not_ready`
|
||
|
||
最新 no-write artifact 以 110 runtime textfile、latest SLO artifact、production API readback 與本輪 Windows99 collector readback 為來源:
|
||
|
||
- 99 no-secret VMware verify stdout 已能透過 110 / public-key SSH 收集;latest artifact 顯示 `missing_vmx_aliases=[]`、`VMWARE_AUTOSTART_CONFIG_READY=1`、`WINDOWS_UPDATE_NO_AUTO_REBOOT_READY=1`、`VMWARE_AUTOSTART_VERIFY_READY=0`。
|
||
- 111 / 188 / 120 / 121 / 112 才是 Windows99 VMware default guest set;110 是 P0 host / control-plane truth,不得再被 validator 或 scorecard fallback 當成 99 VMware guest default。
|
||
- 111 / 188 / 120 / 121 / 112 VM power readback 仍需用新版 no-secret collector 確認;`windows99_vmware_guest_power_not_ready` 仍 blocked,且此 SOP 不授權直接 power change。
|
||
- 111 host 仍 unreachable。
|
||
- 112 / 120 / 121 可達,但 uptime 已超過 10 分鐘目標窗口。
|
||
- 188 可達,但 `systemd_state=degraded` 且 `awoooi-startup.service failed`。
|
||
- `reboot_event_required_host_unreachable` 仍為 primary blocker,因此 10 分鐘 SLO 尚未證明。
|
||
|
||
固定下一步:保留 110 timer / textfile,每輪讀最新 `scorecard.json` 與 Prometheus metric;Windows99 lane 只建立 powered-off aliases 的 check-mode / post-verifier package,不能從 scorecard 直接 power on。API overlay 必須以 runtime metric 的 `awoooi_windows99_vmware_autostart_config_ready` 與 `awoooi_windows99_vmware_windows_update_no_auto_reboot_ready` 為準;當這些 metric 為 ready 時,不得用 committed snapshot 的舊 `service_blockers` / `policy_blockers` 反加 `windows99_vmware_autostart_config_not_ready` 或 `windows99_update_no_auto_reboot_policy_not_ready`。同時繼續補 fresh all-host reboot event / approved drill proof;禁止讀 Windows 密碼、啟動 / 關閉 VM、host reboot、service restart、Docker / Nginx / K3s / DB / firewall restart、restore、prune、delete。
|
||
|
||
2026-07-03 13:10 最新 overlay:Gitea CD `#4582` 已推 deploy marker `558b9e9`,source `ba0849f70`,production deploy readback matched=true 且 job_succeeded=true;Gitea UI 若仍顯示 Running,只能視為 run-level lag。production `/api/v1/agents/windows99-vmx-source-locator-readback` 已上卷 redacted locator receipt:`candidate_source_count=6`、`alias_hint_candidate_count=0`、`unassigned_ubuntu_candidate_count=5`、`identity_unassigned_ubuntu_candidate_count=1`、`candidate_confirmation_required=true`,狀態為 `collector_readback_single_identity_candidate_confirmation_required`。新的 `candidate_confirmation_gate` 固定輸出 `candidate_identity_evidence_sufficient=false`、`candidate_relink_dry_run_ready=false`、`apply_allowed_by_candidate_confirmation_gate=false`;缺少 evidence 為 `target_alias_identity_hint`、`verified_backup_or_authorized_console_candidate_identity`、`pre_apply_backup_plan`、`scoped_relink_dry_run`。因此下一步已從「重跑 locator」收斂為:取得授權 console candidate identity evidence 或 verified backup path,重跑 confirmation gate,再產生 scoped relink dry-run + rollback + post-verifier;仍不得猜測 generic Ubuntu candidate、不得 relink、不得 restore、不得 power on VM。
|
||
|
||
2026-07-03 16:33 source-of-truth correction:本輪全主機 reboot 需求與 host probe 均指向 `111` 是 Windows99 guest target,舊的 `110 -> 188 -> 120 -> 121 -> 112` default 會讓 111 unreachable 被錯誤排除。因此 repo source、API scorecard fallback、CLI scorecard fallback、validator default 必須使用 `111 / 188 / 120 / 121 / 112` 作為 Windows99 default VM target;`110` 仍是 reboot SLO required host 與 control-plane host,但不是 99 VMware 預設 VM。禁止用舊 110 default 清掉 111 blocker,禁止為了清 blocker 修改 Windows task、relink、restore、啟動 VM 或重啟 host。
|
||
|
||
2026-07-03 13:50 no-secret verifier overlay 曾以舊 default `110 / 188 / 120 / 121 / 112` 讀回 artifact;2026-07-03 16:33 已判定該 default 與目前事故目標不一致。下一步必須部署新版 SLO exporter/collector 到 110、重跑 verifier service,確認 validator default、Prometheus textfile、production scorecard 全部改讀 `111 / 188 / 120 / 121 / 112`。仍不得啟動 VM、relink、restore、重啟 host 或讀密碼。
|
||
|
||
2026-07-03 16:57 bounded verifier overlay:新版 verifier 預設不再遞迴掃 VM roots,避免 111 VMX 缺失時 timeout;實測 via-110 no-secret collector 可回 `VMX alias=111 present=0`、`VM_POWER alias=111 vmx_present=0 running=0`、`VMWARE_AUTOSTART_CONFIG_READY=0`。SLO exporter fallback 只可使用 alias set 完全符合 `111 / 188 / 120 / 121 / 112` 的 artifact;舊 `110` artifact 必須被跳過,不得回灌成 runtime truth。
|
||
|
||
2026-07-03 14:10 runtime metric overlay:110 最新 SLO textfile 已輸出 `active_blocker_count=7`,且 `awoooi_windows99_vmware_missing_vmx_alias` 沒有 alias gauge、`awoooi_windows99_vmware_powered_off_alias` 只列 `110 / 112 / 120 / 121 / 188`。production API 在 source 修正前仍可能因 stale committed snapshot 反加 Windows99 config / Windows Update policy 假 blocker;新版 API 必須讀 `awoooi_windows99_vmware_autostart_config_ready` gauge,並以 runtime metric overlay 清掉這兩個假 blocker。若 production `/api/v1/agents/reboot-auto-recovery-slo-scorecard` 仍顯示 `runtime_metric_active_blocker_count=7` 但 `active_blocker_count>7` 且多出的 blocker 是 Windows99 config / policy,先部署 / 驗證 API overlay source,不得回頭做 VMX locate、relink、power change 或 Windows policy apply。
|
||
|
||
### 五主機全貌
|
||
|
||
```
|
||
192.168.0.188 (OLLAMA / 主服務主機)
|
||
├── containerd ← 基礎,必須最先啟動
|
||
├── Docker ← 依賴 containerd
|
||
├── PostgreSQL :5432 ← K3s kine 後端,API DB
|
||
├── Redis :6380 ← Working Memory (重啟後清空,需 warm-up)
|
||
├── Ollama :11434 ← LLM 推論引擎
|
||
├── Nginx :80/:443 ← 反向代理
|
||
├── OpenClaw (clawbot) :8088 ← AI 核心 (Docker Compose)
|
||
├── MinIO ← Velero 備份存儲 (Docker Compose)
|
||
└── SignOz ← 可觀測性 (Docker Compose)
|
||
|
||
192.168.0.110 (DevOps 主機)
|
||
├── Docker
|
||
├── Harbor :5000 ← Container Registry (K3s 拉映像用)
|
||
├── Gitea :3001 ← 主要 Git / CI 管理介面
|
||
├── Gitea Act Runner ← CD pipeline 執行者 (docker: gitea-runner)
|
||
├── Langfuse :3100 ← LLMOps 追蹤
|
||
├── Prometheus :9090 ← 指標收集
|
||
├── Alertmanager :9093 ← 告警路由
|
||
├── Grafana :3002 ← 監控儀表板
|
||
├── Sentry :9000 ← Error Tracking (2026-03-24,2026-04-05 加入 startup)
|
||
└── SignOz ← 可觀測性
|
||
|
||
192.168.0.120 (K3s Master - mon)
|
||
├── K3s server (control plane)
|
||
├── kube-proxy ← NodePort 轉發 32334/32335
|
||
└── keepalived ← VIP 192.168.0.125 (secondary)
|
||
|
||
192.168.0.121 (K3s Worker - mon1)
|
||
├── K3s agent (worker)
|
||
└── kube-proxy
|
||
|
||
192.168.0.125 (VIP, keepalived 管理)
|
||
├── → :32334 AWOOOI API
|
||
└── → :32335 AWOOOI Web
|
||
```
|
||
|
||
### 服務依賴關係
|
||
|
||
```
|
||
嚴格啟動順序:
|
||
|
||
【188 層】
|
||
containerd
|
||
└── Docker
|
||
├── PostgreSQL (kine DB, API DB)
|
||
│ └── Redis (Working Memory)
|
||
│ └── Ollama (LLM)
|
||
│ └── Nginx
|
||
│ ├── SignOz
|
||
│ ├── MinIO
|
||
│ └── OpenClaw (依賴 aiops-network)
|
||
|
||
【110 層】
|
||
Docker
|
||
└── harbor-log (等 healthy)
|
||
└── Harbor 全組件 (:5000)
|
||
└── Gitea (:3001)
|
||
├── Langfuse
|
||
├── Monitoring (Prometheus + Alertmanager + Grafana)
|
||
│ [Alertmanager → AWOOOI API 告警鏈路]
|
||
├── SignOz
|
||
└── Gitea Act Runner (等 Gitea 就緒後才啟動)
|
||
|
||
【120/121 層】
|
||
K3s (依賴 PostgreSQL@188)
|
||
└── kube-proxy (NodePort 規則)
|
||
└── Pods: API / Web / Worker
|
||
└── keepalived (VIP)
|
||
|
||
【告警鏈路】
|
||
Prometheus → Alertmanager(110)
|
||
→ AWOOOI API(VIP 192.168.0.125:32334/api/v1/webhooks/alertmanager) ← 直接,不走 OpenClaw
|
||
→ TelegramGateway → Telegram
|
||
|
||
【CD 鏈路】
|
||
Git push → Gitea(110:3001)
|
||
→ Act Runner (gitea-runner container)
|
||
→ Build Docker image → Harbor(:5000) → kubectl → K3s pods
|
||
```
|
||
|
||
### 110 Controlled CD Lane 與 Fail-Closed Enforcer
|
||
|
||
110 runner 壓力事故後,`awoooi-cd-lane.service`、legacy runner、generic label 與重型 runner 仍必須維持 fail-closed。唯一可被保留的 110 CD entrypoint 是專用 `awoooi-cd-lane-drain.service` 的非 secret staging 狀態,且必須同時符合下列條件:
|
||
|
||
- `config.yaml` 只允許 `capacity <= 1`、`awoooi-host:host`、`awoooi-ubuntu:docker://192.168.0.110:5000/awoooi/ci-runner:act-22.04`。
|
||
- `awoooi_cd_lane_controlled` 必須是 executable ELF,不可為 fail-closed shell stub。
|
||
- unit 必須含 `ConditionPathExists=/home/wooo/awoooi-cd-lane-drain/data/.runner`、CPU / Memory / Tasks accounting 與 limit、`NoNewPrivileges=true`。
|
||
- service staging 階段必須 inactive、`MainPID=0`、未 enabled / 未 masked;runner token registration 與 `.runner` 內容仍不可讀、不可列印。
|
||
|
||
讀回方式固定先跑 `scripts/reboot-recovery/enforce-110-runner-failclosed.sh --check`,看 `CONTROLLED_DRAIN_STAGING_ALLOWED`;再跑 `ops/runner/check-awoooi-110-controlled-cd-lane-readiness.sh`,看 `CONFIG_READY`、`BINARY_READY`、`REGISTRATION_READY`、`SERVICE_READY`。若只剩 registration / service inactive blocker,代表非 secret guardrail 已收斂,下一步才是 token-safe registration path;不得用手工反覆補 config / binary 取代 source enforcer 修正。
|
||
|
||
### 關鍵依賴說明
|
||
|
||
| 服務 | 關鍵依賴 | 若依賴失敗 |
|
||
|------|---------|-----------|
|
||
| K3s | PostgreSQL@188 ready | K3s 無法啟動,所有 pod 無法調度 |
|
||
| OpenClaw | Docker + aiops-network | Telegram AI 對話失效 |
|
||
| Alertmanager → API | NetworkPolicy 允許 110/32 | 告警全面沉默 |
|
||
| CD pipeline | Gitea Act Runner online | 所有部署失效 |
|
||
| Harbor | harbor-log healthy | 所有其他 Harbor 容器 Exited 128 |
|
||
| K8s pods | Harbor (映像倉庫) | ImagePullBackOff |
|
||
|
||
---
|
||
|
||
## 自動化腳本狀態
|
||
|
||
### 已部署 (2026-04-05)
|
||
|
||
| 主機 | 腳本 | 部署位置 | systemd service | 狀態 | 類型 |
|
||
|------|------|---------|----------------|------|------|
|
||
| **188** | `awoooi-startup.sh` | `/usr/local/bin/` | `awoooi-startup.service` | ✅ enabled | 重開機 oneshot |
|
||
| **110** | `awoooi-startup-110.sh` | `/usr/local/bin/` | `awoooi-startup-110.service` | ✅ enabled | 重開機 oneshot |
|
||
| **110** | `harbor-watchdog.sh` | `/usr/local/bin/` | `harbor-watchdog.service` | ✅ active | **常駐 watchdog** |
|
||
| **120** | K3s 原生 | 系統內建 | `k3s.service` | ✅ enabled | 系統服務 |
|
||
| **121** | K3s 原生 | 系統內建 | `k3s-agent.service` | ✅ enabled | 系統服務 |
|
||
|
||
**本地原始碼**: `scripts/reboot-recovery/`
|
||
|
||
### harbor-watchdog(2026-04-05 新增)
|
||
|
||
**問題背景**: Harbor 的 Exited (128) Race Condition
|
||
- `restart: always` 在 harbor-log 未就緒時不斷重試,最終進入 backoff 放棄
|
||
- 即使 harbor-log 後來 healthy,其他容器不會自動重試
|
||
- 只靠 startup 腳本無法處理**運行中崩潰**的情況
|
||
|
||
**設計**:
|
||
- `Type=simple` 常駐進程(不是 oneshot),systemd 永久監控
|
||
- 每 60 秒輪詢 `http://127.0.0.1:5000/v2/`(401 = healthy)
|
||
- 偵測到不健康 → 等 5 秒再確認(避免誤報)→ 執行完整時序修復
|
||
- 修復邏輯:清除 Exited 容器 → 只啟動 harbor-log → 等 healthy → 啟動全部
|
||
|
||
**查看狀態**:
|
||
```bash
|
||
journalctl -u harbor-watchdog.service -f # 即時 log
|
||
systemctl status harbor-watchdog.service # 服務狀態
|
||
cat /var/log/harbor-watchdog.log # 持久化 log
|
||
```
|
||
|
||
### 各腳本覆蓋清單
|
||
|
||
**188 (7 步驟)**:
|
||
- containerd BoltDB 損壞偵測與修復
|
||
- Docker BoltDB 損壞偵測與修復
|
||
- PostgreSQL 啟動偵測;checkpoint / WAL 類資料層錯誤一律 fail-closed,禁止自動 `pg_resetwal`
|
||
- Redis 啟動
|
||
- Ollama 啟動
|
||
- Nginx 啟動
|
||
- SignOz + MinIO + aiops-network + OpenClaw
|
||
|
||
**110 (7 步驟 + harbor-watchdog 常駐)**:
|
||
- Docker BoltDB 損壞偵測與修復
|
||
- 孤兒容器清除 (Exited 128)
|
||
- Harbor(harbor-log first 策略:Phase 1 清除 → Phase 2 只啟 harbor-log → Phase 3 等 healthy → Phase 4 啟全部)
|
||
- Gitea + Langfuse + Monitoring (含 Alertmanager 健康驗證)
|
||
- SignOz
|
||
- **Gitea Act Runner** (自動清除過期 .runner 配置)
|
||
- **Sentry** (/opt/sentry,含 PostgreSQL WAL + Redis RDB 損壞自動修復)
|
||
- **harbor-watchdog.service** (常駐,每 60s 自動修復)
|
||
|
||
---
|
||
|
||
## 正常重啟流程
|
||
|
||
> 適用於:計劃性維護、安全更新、OS 升級等**有預期**的重啟。
|
||
|
||
### 重啟前準備 (T-30 分鐘)
|
||
|
||
```bash
|
||
# 1. 確認沒有 INVESTIGATING/MITIGATING 的 incident
|
||
curl -s http://192.168.0.121:32334/api/v1/incidents | python3 -c 'import sys,json; d=json.load(sys.stdin); print("Active incidents:", d.get("count",0))'
|
||
|
||
# 2. 確認 K3s 健康
|
||
ssh wooo@192.168.0.120 "kubectl get nodes && kubectl get pods -n awoooi-prod"
|
||
|
||
# 3. 備份確認 (MinIO Velero)
|
||
ssh wooo@192.168.0.120 "kubectl get backup -n velero 2>/dev/null | head -5"
|
||
```
|
||
|
||
### 建議重啟順序
|
||
|
||
```
|
||
第一步: 188 + 110 同時重啟
|
||
(兩者獨立,可並行)
|
||
|
||
等待 5 分鐘確認基礎服務就緒:
|
||
- PostgreSQL accepting connections
|
||
- Harbor returning HTTP 200
|
||
- Gitea accessible
|
||
|
||
第二步: 120 + 121 同時重啟
|
||
(K3s cluster)
|
||
|
||
等待 10 分鐘:
|
||
- Nodes Ready
|
||
- Pods Running
|
||
```
|
||
|
||
> **重要**: 120/121 必須在 188 PostgreSQL 完全就緒後才重啟,
|
||
> 否則 K3s kine 連不到 DB 無法啟動。
|
||
|
||
### 重啟後驗證 (T+15 分鐘)
|
||
|
||
執行底部 E2E 驗證腳本。所有項目必須 ✅。
|
||
|
||
---
|
||
|
||
## 異常重啟流程
|
||
|
||
> 適用於:電源中斷、OOM、系統崩潰、強制重啟等**非預期**的重啟。
|
||
|
||
### 第一步:快速診斷 (T+2 分鐘)
|
||
|
||
```bash
|
||
# 188 狀態
|
||
ssh ollama@192.168.0.188 "
|
||
systemctl is-active containerd docker postgresql@14-main redis-server ollama nginx
|
||
docker ps --format '{{.Names}}: {{.Status}}' | grep -E 'clawbot|minio|signoz'
|
||
"
|
||
|
||
# 110 狀態
|
||
ssh wooo@192.168.0.110 "
|
||
docker ps --format '{{.Names}}: {{.Status}}' | grep -E 'harbor-log|gitea$|alertmanager|gitea-runner'
|
||
"
|
||
|
||
# K3s 狀態
|
||
ssh wooo@192.168.0.120 "
|
||
kubectl get nodes 2>/dev/null || echo 'K3s not ready'
|
||
kubectl get pods -n awoooi-prod 2>/dev/null | grep -v Running | head -10
|
||
"
|
||
|
||
# API 健康
|
||
curl -s --max-time 5 http://192.168.0.121:32334/api/v1/health 2>/dev/null | python3 -c 'import sys,json; d=json.load(sys.stdin); print("API:", d["status"])' 2>/dev/null || echo 'API unreachable'
|
||
```
|
||
|
||
### 第二步:按優先級恢復
|
||
|
||
**P0 (T+0~5 分鐘): 基礎設施**
|
||
```bash
|
||
# 自動化腳本通常已處理,但若沒有自動啟動:
|
||
ssh ollama@192.168.0.188 "sudo /usr/local/bin/awoooi-startup.sh"
|
||
ssh wooo@192.168.0.110 "echo '0936223270' | sudo -S /usr/local/bin/awoooi-startup-110.sh"
|
||
```
|
||
|
||
**P1 (T+5~15 分鐘): K3s 叢集**
|
||
```bash
|
||
# 等 PostgreSQL@188 就緒後
|
||
ssh ollama@192.168.0.188 "pg_isready -h localhost -p 5432"
|
||
|
||
# K3s 若未自動恢復
|
||
ssh wooo@192.168.0.120 "sudo systemctl restart k3s"
|
||
ssh wooo@192.168.0.121 "sudo systemctl restart k3s-agent"
|
||
|
||
# 等待 Nodes Ready
|
||
ssh wooo@192.168.0.120 "kubectl wait --for=condition=Ready nodes --all --timeout=120s"
|
||
```
|
||
|
||
**P2 (T+15~30 分鐘): 業務驗證**
|
||
```bash
|
||
# Pods 全部 Running?
|
||
ssh wooo@192.168.0.120 "kubectl get pods -n awoooi-prod"
|
||
|
||
# API 健康 (含所有 components)?
|
||
curl -s http://192.168.0.121:32334/api/v1/health | python3 -m json.tool
|
||
|
||
# 告警鏈路 E2E 測試
|
||
curl -X POST http://192.168.0.121:32334/api/v1/webhooks/alertmanager \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"receiver":"test","status":"firing","alerts":[{"status":"firing","labels":{"alertname":"RebootRecoveryTest","severity":"info"},"annotations":{"summary":"重開機恢復測試,請忽略"},"startsAt":"2026-04-05T00:00:00Z","endsAt":"0001-01-01T00:00:00Z","generatorURL":""}],"groupLabels":{},"commonLabels":{},"commonAnnotations":{},"externalURL":"","version":"4","groupKey":"test"}'
|
||
# 預期: {"success":true,...} 且 Telegram 收到測試告警
|
||
```
|
||
|
||
---
|
||
|
||
## 各主機詳細啟動序列
|
||
|
||
### 192.168.0.188 — 主服務主機
|
||
|
||
| 步驟 | 服務 | 驗證方式 | 常見失敗原因 |
|
||
|------|------|---------|------------|
|
||
| 1 | containerd | `systemctl is-active containerd` | BoltDB meta.db 損壞 |
|
||
| 2 | Docker | `systemctl is-active docker` | BoltDB local-kv.db 損壞 |
|
||
| 3 | PostgreSQL | `pg_isready -h localhost -p 5432` | WAL checkpoint 損壞 |
|
||
| 3a | kine VACUUM | `psql -d k3s_datastore -c "VACUUM ANALYZE kine;"` | 慢查詢導致 K3s 超時 |
|
||
| 4 | Redis | `redis-cli -p 6380 ping` | bind 配置錯誤 (必須 0.0.0.0:6380) |
|
||
| 5 | Ollama | `systemctl is-active ollama` | GPU 記憶體不足 |
|
||
| 6 | Nginx | `systemctl is-active nginx` | port 衝突 |
|
||
| 7a | aiops-network | `docker network ls \| grep aiops-network` | 重啟後 external network 消失 |
|
||
| 7b | OpenClaw | `curl http://localhost:8088/health` | pip 依賴損壞需 rebuild |
|
||
| 7c | MinIO | `docker ps \| grep minio` | 無狀態服務,重啟即可 |
|
||
| 7d | SignOz | `curl http://localhost:3301/-/healthy` | ClickHouse 起慢 |
|
||
|
||
**BoltDB 損壞修復**:
|
||
```bash
|
||
# containerd meta.db
|
||
BOLT=/var/lib/containerd/io.containerd.metadata.v1.bolt/meta.db
|
||
cp $BOLT ${BOLT}.bak.$(date +%s) && rm $BOLT && systemctl restart containerd
|
||
|
||
# Docker network BoltDB
|
||
BOLT=/var/lib/docker/network/files/local-kv.db
|
||
cp $BOLT ${BOLT}.bak.$(date +%s) && rm $BOLT && systemctl restart docker
|
||
```
|
||
|
||
**PostgreSQL checkpoint / WAL 類錯誤處理**:
|
||
|
||
> 2026-06-26 校正:這不是一般重啟 SOP 的自動修復步驟。`pg_resetwal` 可能造成不可逆資料風險,只能在 DB owner、備份/restore evidence、maintenance window、rollback owner 與 post-check plan 都具備時,作為 break-glass 人工程序。AI、startup 腳本或值班腳本不得自動執行。
|
||
|
||
```bash
|
||
# 只讀確認,不代表可執行修復
|
||
journalctl -u postgresql@14-main -n 30 | grep "could not locate a valid checkpoint"
|
||
pg_lsclusters
|
||
systemctl status postgresql@14-main --no-pager
|
||
```
|
||
|
||
若確認需要進入 break-glass,先完成:
|
||
|
||
```text
|
||
1. DB owner approval
|
||
2. 最新本機與 offsite backup evidence
|
||
3. restore / rollback plan
|
||
4. maintenance window
|
||
5. post-check:pg_lsclusters、pg_isready、應用 DB smoke、backup-status、cold-start wrapper
|
||
```
|
||
|
||
### 192.168.0.110 — DevOps 主機
|
||
|
||
| 步驟 | 服務 | 驗證方式 | 常見失敗原因 |
|
||
|------|------|---------|------------|
|
||
| 1 | Docker | `systemctl is-active docker` | BoltDB 損壞 |
|
||
| 2 | 孤兒容器清除 | `docker ps -a \| grep "Exited (128)"` | 舊容器引用已不存在的 network |
|
||
| 3 | harbor-log | `docker inspect --format='{{.State.Health.Status}}' harbor-log` | syslog :1514 未就緒 |
|
||
| 3 | Harbor 全組件 | `curl http://localhost:5000/v2/` | harbor-log 還沒 healthy |
|
||
| 4 | Gitea | `curl http://localhost:3001` | 無特殊問題 |
|
||
| 5 | Langfuse | `curl http://localhost:3100` | DB 未就緒 |
|
||
| 6 | Alertmanager | `curl http://localhost:9093/-/healthy` | docker compose 未啟動 |
|
||
| 6a | 告警鏈路驗證 | 發送測試 webhook | webhook URL 錯誤 / NetworkPolicy |
|
||
| 7 | SignOz | `curl http://localhost:3301/-/healthy` | ClickHouse 起慢 |
|
||
| 8 | Gitea Runner | `docker logs gitea-runner \| grep SUCCESS` | .runner 配置過期 |
|
||
| 9 | Sentry | `curl -o /dev/null -w '%{http_code}' http://localhost:9000/` | PostgreSQL WAL/Redis RDB 損壞 (見下方) |
|
||
|
||
**110 control-path / Harbor receipt 判讀 (2026-07-02)**:
|
||
|
||
重啟後若公開 Gitea queue 仍顯示舊的 Harbor repair failure、`harbor_110_remote_ssh_publickey_auth_stalled`、remote-control unavailable 或 jobs stale,不得直接把它當成 current blocker。必須先用同一輪 read-only receipt 合併以下證據:
|
||
|
||
```bash
|
||
scripts/reboot-recovery/diagnose-110-ssh-publickey-auth.sh
|
||
ssh wooo@192.168.0.110 'sudo -n /usr/local/bin/recover-110-control-path-and-harbor-local.sh --check'
|
||
python3 ops/runner/read-public-gitea-actions-queue.py --json
|
||
curl -k -s -o /dev/null -w '%{http_code}' https://registry.wooo.work/v2/
|
||
curl -s -o /dev/null -w '%{http_code}' http://192.168.0.110:5000/v2/
|
||
```
|
||
|
||
若 `SSH_COMMAND_PATH ... classification=command_path_ready` 成立,且 public/internal registry `/v2/` 回 `401`,則舊 Harbor repair SSH failure 只能列為 historical queue metadata;active blocker 應轉向實際的 controlled CD lane readiness、CD jobs SHA/readback mismatch 或 deploy marker。2026-07-02 的機器可讀證據是 `docs/operations/harbor-110-control-path-recovery-readback-2026-07-02.snapshot.json`:Gitea latest visible CD `#4335` 為 `Success`,但 active blockers 仍包含 110 controlled CD lane config / binary / registration / service guardrail 與 Gitea CD jobs head-SHA / stale mismatch。
|
||
|
||
combined local-console output 只能依 marker 判讀:`AWOOOI_110_CONTROLLED_CD_LANE_READY` 只屬於 110 controlled lane;沒有 `AWOOOI_NON110_RUNNER_READY` 時,不得從同一份 `BLOCKER` 行推導 non110 runner blocker。
|
||
|
||
**Gitea repository / bundle backup 判讀 (2026-07-02)**:
|
||
|
||
Gitea UI/API 可見性、SSH refs、bundle backup、sample restore dry-run 是四層不同證據,不得混成「repo 不見」或「backup 完成」一句話。
|
||
|
||
固定順序:
|
||
|
||
```bash
|
||
curl -s https://awoooi.wooo.work/api/v1/agents/gitea-repo-bundle-backup-readback | python3 -m json.tool
|
||
curl -s http://192.168.0.188:9100/metrics | grep '^awoooi_gitea_bundle_'
|
||
scripts/backup/gitea-bundle-sample-restore-dry-run.sh --repo wooo/awoooi --write-textfile
|
||
```
|
||
|
||
判讀規則:
|
||
|
||
- `expected_repo_missing_count=0`、`failed_repo_count=0`、`checksum_missing_count=0` 代表 bundle repo / checksum 層沒有缺,不代表 freshness 或 restore drill 已完成。
|
||
- `awoooi_gitea_bundle_fresh=0` 是 freshness blocker;下一步是補跑 `gitea-repo-bundle-backup.sh` 與同步到 188,不得刪 repo、改 visibility、或 restore 到 production。
|
||
- `awoooi_gitea_bundle_sample_restore_dry_run_ok` absent / `0` 是 restore confidence blocker;只允許在暫存目錄 clone bundle + `git fsck --connectivity-only`,不得把 bundle restore 回 Gitea production。
|
||
- public repo search 只代表 public visibility;private/internal repo 要看 authenticated inventory、SSH heads 或 bundle metrics,不能用 404 直接推論 history 消失。
|
||
|
||
**Backup alert receipt 判讀 (2026-07-03)**:
|
||
|
||
備份監控不只看 freshness / failed / age / restore drill,還必須能證明告警進入 Telegram / AwoooP receipt 鏈。重啟後若使用者問「備份到底有沒有告警」,固定順序如下:
|
||
|
||
```bash
|
||
python3 scripts/ops/backup-alert-label-contract-check.py
|
||
curl -G -s http://192.168.0.110:9090/api/v1/query --data-urlencode 'query=awoooi_backup_alert_receipt_stage_fresh{host=~"110|188"}' | python3 -m json.tool
|
||
scripts/ops/backup-alert-live-visibility-check.py --prometheus-url http://192.168.0.110:9090 --alertmanager-url http://192.168.0.110:9093 --host 110
|
||
scripts/ops/backup-alert-live-visibility-check.py --prometheus-url http://192.168.0.110:9090 --alertmanager-url http://192.168.0.110:9093 --host 188
|
||
```
|
||
|
||
判讀規則:
|
||
|
||
- `required_alert` 是原始 backup alert,例如 `BackupJobStale`、`BackupCoverageDomainStale`、`GiteaPrivateBundleBackupIncomplete`;不得用 Prometheus 保留 label `alertname` 承載它。
|
||
- 每個 required alert 都要有 `sent / received / dedup / escalation` 四段 marker;缺段明細由 `awoooi_backup_alert_receipt_stage_fresh == 0` 保留,`BackupAlertReceiptStageMissing` 只允許聚合成每個 `host / receipt_channel` 一條,避免 Telegram/AwoooP 噪音淹沒真問題。
|
||
- marker 只能是 `/backup/alert-receipts/*.last_success` 這類 timestamp / evidence id;不得寫 Telegram token、raw payload、chat id 原文或 secret。
|
||
- 這個 receipt proof 不取代真正 backup / restore proof;Gitea bundle、Gitea full dump、DB/settings/issues/packages/LFS、offsite、restore drill 仍要各自綠。
|
||
|
||
**Harbor Exited 128 修復**:
|
||
```bash
|
||
# 等 harbor-log healthy
|
||
until [ "$(docker inspect --format='{{.State.Health.Status}}' harbor-log)" = "healthy" ]; do
|
||
echo "waiting..."; sleep 5
|
||
done
|
||
|
||
# 清除 Exited 128 容器
|
||
docker rm -f $(docker ps -a --filter status=exited --format "{{.Names}}" | grep -v harbor-log)
|
||
|
||
# 重新啟動
|
||
cd /home/wooo/harbor/harbor && docker compose up -d
|
||
```
|
||
|
||
**Gitea Runner 重新註冊**:
|
||
```bash
|
||
# 清除過期配置 (指向錯誤 hostname 的)
|
||
sudo rm /home/wooo/act-runner/data/.runner
|
||
|
||
# 獲取新的 registration token
|
||
curl -s -u 'wooo:TOKEN' 'http://192.168.0.110:3001/api/v1/repos/wooo/awoooi/actions/runners/registration-token'
|
||
|
||
# 更新 docker-compose.yml 的 GITEA_RUNNER_REGISTRATION_TOKEN
|
||
# 然後啟動
|
||
cd /home/wooo/act-runner && docker compose up -d
|
||
```
|
||
|
||
### 192.168.0.120/121 — K3s 叢集
|
||
|
||
| 步驟 | 項目 | 驗證方式 |
|
||
|------|------|---------|
|
||
| 前提 | PostgreSQL@188 ready | `pg_isready -h 192.168.0.188 -p 5432` |
|
||
| 1 | K3s service | `systemctl is-active k3s` (120) |
|
||
| 2 | K3s agent | `systemctl is-active k3s-agent` (121) |
|
||
| 3 | Nodes Ready | `kubectl get nodes` |
|
||
| 4 | Pods Running | `kubectl get pods -n awoooi-prod` |
|
||
| 5 | keepalived VIP | `ip addr show \| grep 192.168.0.125` (120) |
|
||
| 6 | NodePort | `nc -zv 192.168.0.121 32334` |
|
||
|
||
---
|
||
|
||
## 常見故障排查手冊
|
||
|
||
### 1. 告警沉默 (Telegram 沒收到告警)
|
||
|
||
```
|
||
診斷樹:
|
||
Alertmanager healthy? (http://192.168.0.110:9093/-/healthy)
|
||
├── NO → docker compose up -d (在 110 /home/wooo/monitoring/)
|
||
└── YES
|
||
↓
|
||
Webhook URL 正確?
|
||
grep 'url:' /home/wooo/monitoring/alertmanager.yml
|
||
必須是: http://192.168.0.125:32334/api/v1/webhooks/alertmanager
|
||
├── NO → bash scripts/ops/deploy-alertmanager-config.sh
|
||
└── YES
|
||
↓
|
||
從 110 curl POST webhook 成功?
|
||
curl -X POST http://192.168.0.125:32334/api/v1/webhooks/alertmanager ...
|
||
├── timeout → NetworkPolicy 未允許 110
|
||
│ kubectl apply -f k8s/awoooi-prod/02-network-policy.yaml
|
||
└── {"success":true} → 檢查 Telegram Bot Token
|
||
```
|
||
|
||
Alertmanager config drift 修復:
|
||
|
||
```bash
|
||
# 從專案根目錄執行;會從 K8s Secret 注入 Telegram bot token 與 SRE_GROUP_CHAT_ID,
|
||
# 先用 amtool 驗證,再備份 110 live config、原 inode 覆寫、修正 0644 權限並 HUP reload。
|
||
bash scripts/ops/deploy-alertmanager-config.sh
|
||
```
|
||
|
||
**補充診斷 — 特定服務異常但無告警(Alertmanager 正常)**:
|
||
```bash
|
||
# 確認 Prometheus 規則數量和關鍵規則是否存在
|
||
ssh wooo@192.168.0.110 "curl -s http://localhost:9090/api/v1/rules | python3 -c \"
|
||
import sys,json
|
||
r=json.load(sys.stdin)
|
||
names=[x['name'] for g in r['data']['groups'] for x in g['rules']]
|
||
total=len(names)
|
||
key=['SentryDown','HarborDown','GiteaDown','OpenClawDown','AlertmanagerDown','AlertChainUnhealthy']
|
||
for k in key:
|
||
print(f'{'OK' if k in names else 'MISSING'}: {k}')
|
||
print(f'Total rules: {total}')
|
||
\""
|
||
|
||
# 若規則不存在或 total < 25 → 規則未部署
|
||
# 修復: 從專案根目錄執行
|
||
bash scripts/ops/deploy-alerts.sh
|
||
|
||
# 若規則存在但 inactive → 確認 blackbox probe target
|
||
curl http://192.168.0.110:9090/api/v1/query?query=probe_success
|
||
```
|
||
|
||
**根本教訓 (2026-04-05)**:
|
||
- 舊架構: Alertmanager → OpenClaw 8088 (中間層,重開機後 OpenClaw 不健康就沉默)
|
||
- 新架構: Alertmanager → AWOOOI API 直通 (更穩定,單一故障點少)
|
||
- NetworkPolicy 必須允許 110/32 進入 pod
|
||
|
||
### 2. Gitea CD 無法部署
|
||
|
||
```
|
||
診斷樹:
|
||
Gitea runner 數量?
|
||
curl -u 'wooo:TOKEN' 'http://192.168.0.110:3001/api/v1/repos/wooo/awoooi/actions/runners'
|
||
├── total_count: 0 → Runner 離線
|
||
│ docker ps | grep gitea-runner
|
||
│ ├── 不存在 → cd /home/wooo/act-runner && docker compose up -d
|
||
│ └── 存在但 Restarting → docker logs gitea-runner
|
||
│ "lookup gitea" → 清除 data/.runner → docker compose up -d
|
||
└── total_count: 1 → Runner 在線但 CD 失敗
|
||
查看 Gitea Actions 日誌: http://192.168.0.110:3001/wooo/awoooi/actions
|
||
```
|
||
|
||
### 3. 網站數據顯示 0 / incidents 消失
|
||
|
||
```
|
||
原因: Redis Working Memory 重啟後清空
|
||
新版 API (f4f454f+) 啟動時自動 warm-up
|
||
舊版 API 沒有此邏輯
|
||
|
||
診斷:
|
||
curl http://192.168.0.121:32334/api/v1/incidents
|
||
→ count: 0 但 Telegram 有歷史告警?
|
||
→ 確認 API pod image 版本
|
||
|
||
修復 (若 image 版本正確):
|
||
kubectl rollout restart deployment/awoooi-api -n awoooi-prod
|
||
|
||
修復 (若 image 版本過舊):
|
||
等待 CD 完成部署最新版本
|
||
```
|
||
|
||
### 4. NodePort 32334 從外部無法訪問
|
||
|
||
```
|
||
診斷:
|
||
nc -zv 192.168.0.121 32334 ← 這個通嗎?
|
||
├── 通 → 用 192.168.0.121:32334 直連
|
||
└── 不通
|
||
kubectl get pods -n awoooi-prod
|
||
├── 沒有 Running pods → K3s pods 問題
|
||
└── 有 Running pods
|
||
iptables -t nat -L KUBE-NODEPORTS -n | grep 32334
|
||
├── 沒有規則 → K3s kube-proxy 問題,重啟 k3s
|
||
└── 有規則 → NetworkPolicy 或防火牆
|
||
```
|
||
|
||
### 5. Sentry 重開機後損壞修復 (2026-04-05 事故記錄)
|
||
|
||
**症狀**: sentry-self-hosted-postgres-1 / redis-1 / clickhouse-1 持續 Restarting
|
||
|
||
**原因**: 重開機時各 DB 未正常關閉,導致資料損壞:
|
||
- PostgreSQL: WAL 損壞 (`could not locate a valid checkpoint record`)
|
||
- Redis: dump.rdb 損壞 (`Wrong signature trying to load DB from file`)
|
||
- ClickHouse: system table store parts 損壞 (`broken and needs manual correction`)
|
||
|
||
**Break-glass 修復範例,不屬於一般重啟 SOP**:
|
||
|
||
先確認 Sentry DB volume 是受影響權威資料面,並具備 owner approval、volume backup / rollback ref、maintenance window 與 post-check plan。不得由 startup script、AI agent 或值班自動流程直接執行。
|
||
|
||
```bash
|
||
# Step 1: 修復 PostgreSQL WAL
|
||
docker run --rm -u 999 -v sentry-postgres:/var/lib/postgresql/data \
|
||
postgres:14 pg_resetwal -f /var/lib/postgresql/data
|
||
echo "PostgreSQL WAL reset 完成"
|
||
|
||
# Step 2: 修復 Redis (session cache,可丟失)
|
||
docker run --rm --user root -v sentry-redis:/data alpine \
|
||
sh -c 'rm -f /data/dump.rdb && echo cleared'
|
||
echo "Redis dump.rdb 已清除"
|
||
|
||
# Step 3: 找出損壞的 ClickHouse store 目錄
|
||
docker logs sentry-self-hosted-clickhouse-1 2>&1 \
|
||
| grep -oE 'store/[a-f0-9]{3}/[a-f0-9-]+' \
|
||
| sort -u
|
||
|
||
# Step 4: 依據上方輸出刪除損壞目錄 (以 UUID 為準,以下為範例)
|
||
for uuid in "469/469957c9-..." "57d/57d1d567-..."; do
|
||
docker run --rm --user root -v sentry-clickhouse:/data alpine \
|
||
sh -c "rm -rf /data/store/$uuid && echo OK:$uuid"
|
||
done
|
||
|
||
# Step 5: 重啟 Sentry
|
||
cd /opt/sentry && docker compose up -d
|
||
|
||
# Step 6: 等待 2-3 分鐘後驗證
|
||
sleep 120
|
||
curl -o /dev/null -w "%{http_code}" http://192.168.0.110:9000/
|
||
# 預期: 200 / 302 / 400 (需登入)
|
||
```
|
||
|
||
**2026-06-26 修正方向**: 舊 `startup-110.sh` 曾把 PostgreSQL WAL 和 Redis RDB 損壞列入自動修復,這類資料層破壞性動作後續必須遷移到 owner-gated break-glass,不得再擴大成預設自動化。ClickHouse parts 損壞同樣需人工識別 UUID、備份與 post-check。
|
||
|
||
---
|
||
|
||
### 6. Harbor ImagePullBackOff
|
||
|
||
```
|
||
診斷:
|
||
kubectl describe pod <pod> -n awoooi-prod | grep -A 5 Events
|
||
→ "ImagePullBackOff" or "ErrImagePull"
|
||
|
||
確認 Harbor 健康:
|
||
curl http://192.168.0.110:5000/v2/ → 應該回 {}
|
||
|
||
若 Harbor 不健康:
|
||
- 等 harbor-log healthy 後重啟: 見 Harbor Exited 128 修復
|
||
- 確認 Docker daemon 在 120/121 有設定 insecure registry:
|
||
cat /etc/docker/daemon.json | grep insecure
|
||
```
|
||
|
||
---
|
||
|
||
## E2E 驗證腳本
|
||
|
||
執行此腳本確認重開機後全系統正常。
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# 重開機後 E2E 驗證
|
||
# 使用: bash docs/runbooks/scripts/e2e-verify.sh
|
||
# 執行位置: 任意可 SSH 的機器
|
||
|
||
API="http://192.168.0.121:32334"
|
||
GREEN='\033[0;32m'; RED='\033[0;31m'; NC='\033[0m'
|
||
PASS=0; FAIL=0
|
||
|
||
check() {
|
||
local name="$1"; local cmd="$2"; local expect="$3"
|
||
result=$(eval "$cmd" 2>/dev/null)
|
||
if echo "$result" | grep -q "$expect"; then
|
||
echo -e "${GREEN}✅ $name${NC}"
|
||
((PASS++))
|
||
else
|
||
echo -e "${RED}❌ $name${NC} (got: ${result:0:80})"
|
||
((FAIL++))
|
||
fi
|
||
}
|
||
|
||
echo "=== AWOOOI 重開機 E2E 驗證 $(date '+%Y-%m-%d %H:%M:%S') ==="
|
||
|
||
# 188 服務
|
||
check "188 PostgreSQL" "ssh ollama@192.168.0.188 'pg_isready -h localhost -p 5432'" "accepting"
|
||
check "188 Redis" "ssh ollama@192.168.0.188 'redis-cli -p 6380 ping'" "PONG"
|
||
check "188 OpenClaw" "ssh ollama@192.168.0.188 'curl -s http://localhost:8088/health'" "healthy"
|
||
|
||
# 110 服務
|
||
check "110 Harbor" "curl -s -o /dev/null -w '%{http_code}' http://192.168.0.110:5000/v2/" "200"
|
||
check "110 Gitea" "curl -s -o /dev/null -w '%{http_code}' http://192.168.0.110:3001" "200"
|
||
check "110 Alertmanager" "curl -s http://192.168.0.110:9093/-/healthy" "OK"
|
||
check "110 Gitea Runner" "curl -s -u 'wooo:TOKEN' 'http://192.168.0.110:3001/api/v1/repos/wooo/awoooi/actions/runners' | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get(\"total_count\",0))'" "1"
|
||
check "110 Sentry" "curl -s -o /dev/null -w '%{http_code}' --max-time 10 http://192.168.0.110:9000/" "[23][0-9][0-9]\|400"
|
||
check "Prometheus rules ≥25" "ssh wooo@192.168.0.110 'curl -s http://localhost:9090/api/v1/rules' | python3 -c \"import sys,json; r=json.load(sys.stdin); print(sum(len(g['rules']) for g in r['data']['groups']))\"" "[2-9][5-9]\|[3-9][0-9]"
|
||
|
||
# K3s
|
||
check "K3s Nodes Ready" "ssh wooo@192.168.0.120 'kubectl get nodes --no-headers' | awk '{print \$2}'" "Ready"
|
||
check "K3s Pods Running" "ssh wooo@192.168.0.120 'kubectl get pods -n awoooi-prod --no-headers' | grep -c Running" "3"
|
||
|
||
# AWOOOI API
|
||
check "API Health" "curl -s $API/api/v1/health" '"status":"healthy"'
|
||
check "API openclaw" "curl -s $API/api/v1/health" '"openclaw":{"status":"up"'
|
||
|
||
# 告警鏈路 E2E
|
||
PAYLOAD='{"receiver":"e2e-test","status":"firing","alerts":[{"status":"firing","labels":{"alertname":"RebootE2ETest","severity":"info"},"annotations":{"summary":"重開機 E2E 驗證,請忽略"},"startsAt":"2026-04-05T00:00:00Z","endsAt":"0001-01-01T00:00:00Z","generatorURL":""}],"groupLabels":{},"commonLabels":{},"commonAnnotations":{},"externalURL":"","version":"4","groupKey":"e2e-test"}'
|
||
check "告警鏈路 E2E" "curl -s -X POST $API/api/v1/webhooks/alertmanager -H 'Content-Type: application/json' -d '$PAYLOAD'" '"success":true'
|
||
|
||
echo ""
|
||
echo "=== 結果: ${PASS} 通過, ${FAIL} 失敗 ==="
|
||
[ $FAIL -eq 0 ] && echo -e "${GREEN}🎉 全部通過!系統正常${NC}" || echo -e "${RED}⚠️ 有 ${FAIL} 項失敗,請排查${NC}"
|
||
```
|
||
|
||
---
|
||
|
||
## 版本歷史
|
||
|
||
| 版本 | 日期 | 說明 |
|
||
|------|------|------|
|
||
| v5.30 | 2026-07-11 | 交叉驗證 99 實際 boot time,修正 observer false negative;部署 durable reboot state、PT20M task、principal-aware VM verifier/parser、VMware controlled apply,並以 production-principal Recover 與 23/23 public matrix 證明 late recovery |
|
||
| v5.29 | 2026-07-11 | 初始 observer 將 99 判為未回電;此判讀已由 v5.30 的 Windows boot/readback 與 socket probe evidence supersede,T+10 breach 本身仍有效 |
|
||
| v1.0 | 2026-04-04 | 初版:containerd + Docker + PostgreSQL WAL 修復流程 |
|
||
| v2.0 | 2026-04-05 上午 | 188 啟動腳本 + 110 啟動腳本 + Harbor race condition 修復 |
|
||
| v3.0 | 2026-04-05 下午 | 完整架構重盤 + Gitea Runner 自動化 + 告警鏈路根因修復 + NetworkPolicy 修正 |
|
||
| v4.0 | 2026-04-05 下午 | Prometheus 規則統一部署 (28條) + Sentry startup (Step 7) + Sentry損壞修復SOP + 規則未部署診斷樹 + E2E腳本加入Sentry/Prometheus驗證 |
|
||
| v5.0 | 2026-04-05 下午 | Harbor Exited(128) Race Condition 根治:startup-110.sh 改用 harbor-log first 策略 + 新增 harbor-watchdog.service 常駐自愈(curl -f bug 修正)|
|