FR-066.7 T-7.4 · B 案生死驗證 · 2026-08-23
在 192.168.50.160 開一個獨立 WSL distro,裝 systemd+docker-ce,把 onepack 兩容器跑起來並 enroll 上 188 DEV,然後專攻一題:沒有人登入時,這套東西能不能自己活著。答案是不能——不是設定沒調好,而是 WSL 會在閒置約 60 秒後把 distro 的 userspace 凍結,且 restart: unless-stopped、systemd enable、vmIdleTimeout=-1、distro 內常駐 anchor 進程四種手段全部無效。B 案在生死題上失敗。
🔴 本文件記錄「B 案能不能成立」,不是「客戶要這樣裝」。 探路階段允許權宜手段 (改埠避開既有 agent、SSH 遠端下指令、手寫 .env);權宜處一律標示 ⚠️探路權宜, 與「結論」分開寫。體例沿用
t71-windows-probe.md。對應:design.md D18(B 案否決的理由之一是「常駐機制未驗」——本卡即為補驗)、 D17(Docker Desktop 路線)、
t71-windows-probe.md§8(A 案登出全滅 P0)。
| # | 驗證項 | 實測結論 | 卡點等級 |
|---|---|---|---|
| ① | 佈建 | ✅ 通。獨立 distro guidant-wsl(Ubuntu 24.04)+systemd+docker-ce 29.7.2+compose v5.5.0;onepack 兩容器 healthy、enroll 上線 188 DEV(remote_agents id=8 WIN160-WSL-B)。既有 Docker Desktop 完全未受影響 |
低(純安裝面無擋路) |
| ② | 🔴 無人登入常駐(核心生死題) | ❌ 不通,且比 A 案更難救。(a) SYSTEM 帳戶排程被 WSL 硬拒(WSL_E_LOCAL_SYSTEM_NOT_SUPPORTED);(b) 改 S4U 具名帳戶排程可跑 wsl.exe,但 WSL 在閒置約 60 秒後凍結 distro userspace;(c) restart: unless-stopped/systemd enable/vmIdleTimeout=-1/distro 內 anchor 進程四種手段全失效;(d) 反證測試排除「S4U 設定造成」的替代解釋 |
極高(P0,B 案的死因) |
| ③ | 網路 | ⚠️ 有條件通,且比 A 案退步。WSL2 NAT 只轉 localhost,LAN 介面完全沒有 listener,雲端連不到;需 netsh portproxy 補一層,且 WSL IP 是 DHCP、每次開機要重設。mirrored 模式此機不支援(0x803b0015,還會退化成 networkingMode=None) |
中(可解但增維運面) |
| ④ | 指紋 | ✅ 行為明確。/sys 在 WSL 內同樣唯讀、product_uuid 不存在(同 T-7.1 硬失敗,該掛載行必須拿掉);退化成 machine-id 單源,而該 machine-id 是 distro 自己的(import 時生成),非 Windows 主機任何值。獨立驗算相符 |
低(行為與 A 同構,可預期) |
| ⑤ | 韌性(冷重開機) | ⏸️ 未測——②已判死,此題失去決策意義(詳 §7) | — |
| ⑥ | B 案可行性判定 | 🔴 不可行。見 §8 一句話判定 | — |
一句話判定:B 案不可行——WSL VM 的生命週期由 Windows 側常駐進程決定,而「Windows 側常駐進程」正是無人登入場景所缺的那樣東西,因此 B 並未繞開 A 的死穴,只是把「需要有人登入」換成「需要一個永不結束的 Windows 側進程」,而工作排程器(跑完即結束的模型)給不了它。
| 項目 | 實測 |
|---|---|
| OS | Windows Server 2025 Standard(10.0.26100.4652),機器名 JEDI-WIN2025-01-AD,網域 jedicogy.com |
| WSL | 2.7.12.0,kernel 6.18.33.2;WSLService Automatic/Running |
| 既有 distro | Ubuntu、docker-desktop — 屬 raymond 的 per-user 註冊(見 §1.2) |
| 既有 Docker Desktop | 執行中(7 進程),agent 容器在 8443 |
| 記憶體 | 24GB(實測期間 free 約 16GB) |
| onepack | C:\onepack\guidant-agent-compose-0.2.28,sha256 與 .sha256 檔相符(0b5d47f0…,即 T-7.1 那顆,未重搬) |
以 Administrator 身分執行 wsl -l -v 回沒有任何 distro,但 raymond 有兩個。 查註冊表證實 distro 掛在各使用者 SID 下:
HKEY_USERS\S-1-5-21-…-1101\…\Lxss → docker-desktop, Ubuntu (raymond)
HKEY_USERS\S-1-5-21-…-500\…\Lxss → (空,Administrator)
意義:B 案的 distro 必須註冊在「執行排程工作的那個帳戶」名下;換帳戶跑就看不到它。 這也順帶保證了本次探路的隔離性——新開的 guidant-wsl 在 Administrator 名下, raymond 的 Docker Desktop 兩個 distro 全程未被觸碰。
| 步驟 | 作法 | 耗時 |
|---|---|---|
| 取得 rootfs | cloud-images.ubuntu.com/wsl/releases/24.04/current/ubuntu-noble-wsl-amd64-24.04lts.rootfs.tar.gz(340MB) |
45 秒 |
| import distro | wsl --import guidant-wsl C:\t74\wsl\guidant <rootfs> --version 2 |
34 秒 |
| 開 systemd | 寫 /etc/wsl.conf([boot] systemd=true)→ wsl --terminate → 再進入 |
數秒 |
| 驗 systemd | ps -p 1 -o comm= = systemd;systemctl is-system-running = running |
— |
| 裝 docker-ce | 官方 apt repo(download.docker.com/linux/ubuntu noble stable)→ systemctl enable --now docker |
108 秒 |
| 版本 | docker 29.7.2(client=server)、Docker Compose v5.5.0、enabled/active |
— |
docker load 兩顆 image |
從 /mnt/c/onepack/... 讀 3.32GB+188MB |
約 12 分鐘(比 T-7.1 的 6 分鐘慢一倍,因走 /mnt/c 的 9p 檔案系統) |
compose up -d |
兩容器建立並轉 healthy | 約 45 秒 |
⚠️ 對日後任何 WSL 路線的規格輸入:交付包放在 Windows 檔案系統(/mnt/c)時 docker load 明顯變慢;若真要走 WSL 路線,應先把 tar 複製進 distro 的 ext4(/root 之類)再 load。
160 上已有 Docker Desktop 版 agent 佔用 8443,且其 .env 指向 https://192.168.50.191 (guidant-ai-prd-01,非 T-7.1 記載的 188——該設定在 T-7.1 之後被改過)。 本卡未動那份設定、未動那組容器,改為:
C:\t74\b(.env + docker-compose.yml + certs-in/).env、CA 與註冊 token 取自 188 上 /tmp/t66-final/ 的既有安裝)AGENT_HOSTNAME=WIN160-WSL-B(雲端可一眼分辨是哪一路線)INFO:agent:device_uuid computed: 5bd79796b9b2b1687fff565af29ca46cb1a00862b7f5a7a7d7e8195ef40ed0ed
INFO:agent:agent enrolled: agent_uid=642f815e-a3c8-4ee6-9863-f6c347fe2875 heartbeat=300s
INFO:agent:data-plane mTLS listening on 0.0.0.0:8443 (client certificate required)
INFO:agent:agent auth ready (mode=full, status=active, ...)
[integrity] 完整性驗證通過(114 個檔案,版本 0.2.28,kid 04b1e65f100c6c8f)
雲端 188 DEV(guidant_ai.compliance.remote_agents):
id | name | base_url | fp | status
8 | WIN160-WSL-B | https://192.168.50.160:9443 | 5bd79796b9b2b168 | active
✅ ①結論:docker engine on WSL2 這條路,安裝面完全走得通, 且 agent 對 engine 與對 Docker Desktop 沒有任何行為差異(防竄改驗章、enroll、mTLS 全正常)。
卡片設計的做法是「工作排程器(SYSTEM 帳戶、不論使用者是否登入、開機觸發)」。 實測以 SYSTEM 身分執行 wsl.exe:
Running WSL as local system is not supported.
Error code: Wsl/WSL_E_LOCAL_SYSTEM_NOT_SUPPORTED
wsl -l -v、wsl --status 皆同一錯誤。
這不是權限沒調好或環境沒裝好,是 WSL 明文不支援 LOCAL SYSTEM。 卡片寫的那個做法不存在可行版本,必須換帳戶模型。
改用 Register-ScheduledTask 搭 S4U(Service-for-User,不需存密碼):
$p = New-ScheduledTaskPrincipal -UserId 'JEDICOGY\administrator' -LogonType S4U -RunLevel Highest實測結果(工作內容印出自身身分):
whoami: jedicogy\administrator
sessionid: 0
--- wsl --status --- Default Version: 2 ← 有回應,不再被拒
✅ 這一層通了:具名帳戶+S4U+session 0,可以在無互動 session 的情況下呼叫 wsl.exe。 (附註:schtasks /RU <user> /NP 這種寫法在 Server 2025 上被拒;要用 PowerShell 的 New-ScheduledTaskPrincipal -LogonType S4U。)
排程能跑 wsl.exe 之後,容器也確實被拉起來了。但放著不管,agent 就沒了。
最初我用 wsl -d guidant-wsl -- docker ps 反覆檢查,每次都看到容器 Up, 看起來一切正常。露餡的是 uptime:每次查詢它都從 0 重新算。
wsl.exe只要被呼叫就會把 distro 喚醒(resume/boot)。 因此任何用wsl.exe做的健康檢查,都會把「已經死掉」變成「看起來活著」。
改用不碰 distro 的觀測面(Windows 側 wsl -l --running + 雲端 DB 心跳)之後, 真相才出來。
| 時刻 | 觀測 | 結果 |
|---|---|---|
| 00:01:31 | 啟動 distro,之後只用 wsl -l --running 看 |
guidant-wsl running |
| 00:02:35 | 同上(約 60 秒後) | There are no running distributions. |
distro 在無人使用約 60 秒後自行停止(WSL 預設 vmIdleTimeout 即 60000ms)。 agent 隨之離線——restart: unless-stopped 與 systemctl enable docker 都救不了, 因為死的不是容器、也不是 docker daemon,是承載它們的整個 distro。
進一步解剖(在 anchor 測試失敗後、resume 當下取證):
| 觀測面 | 值 | 意義 |
|---|---|---|
vmmemWSL 進程 |
仍在(start 00:07:15) | VM 外殼還在 → 只看 Windows 進程會誤判成活著 |
wsl -l --running |
no running distributions | distro 不在執行 |
distro 內 uptime -s |
仍是 00:07:16(未變) | Linux kernel 沒有重開機 |
容器 StartedAt |
00:14:33(=我 poke 的瞬間) | 容器是被停掉、又在 resume 時拉起 |
docker.service ActiveEnterTimestamp |
00:14:34 | systemd 的 docker 也是 resume 才 active |
docker inspect … RestartCount |
0 | 不是 crash-restart |
| agent log | 最後一行 00:07:49,之後空白 6.5 分鐘 | 期間完全沒有任何活動 |
dmesg 佐證([438s] = 00:14:34,即 resume 時刻):
WSL (2 - init-systemd(guidant-wsl)) ERROR: WaitForBootProcess:3488:
/sbin/init failed to start within 10000ms
結論:WSL 把 distro 的 userspace 整個凍結/收掉(kernel 與 VM 外殼保留), 裡面沒有任何一層還醒著能自救——docker daemon 自己也被凍結了。 這比單純「關機」更棘手:關機至少可以靠「開機自起」補救,凍結則是連自起的那個東西也被凍住。
| # | 手段 | 實測結果 |
|---|---|---|
| 1 | compose restart: unless-stopped |
❌ 無效——死的不是容器 |
| 2 | systemctl enable docker(開機自起) |
❌ 無效——distro 不「開機」,是被凍結 |
| 3 | %USERPROFILE%\.wslconfig → [wsl2] vmIdleTimeout=-1 |
❌ 無效,實測 2 分鐘內照樣停止 |
| 4 | distro 內常駐 anchor 進程(sleep infinity,模仿「保持忙碌」) |
❌ 無效——anchor 自己也被凍結/收掉(測後 Windows 側已無該 wsl.exe 進程);心跳停在 00:07:45,6 分鐘無更新 |
手段 4 失敗的原因,正是本案最核心的結構問題: 放在 distro 裡面的東西,無法阻止 distro 被凍結——它跟著一起被凍。
手段 4 的 anchor 原本由 S4U 排程啟動,因此需排除一個可能: 會不會是排程工作結束時把 distro 一起收掉了(而非 WSL 本質行為)?
實測:停掉 watchdog,改從 SSH session(非排程)啟動同一支 anchor,只用無干擾觀測面看心跳。
| 時間 | 心跳 age | 判讀 |
|---|---|---|
| 00:21:30(2 分) | 126s | 未定論(< 300s 心跳間隔) |
| 00:23:30(4 分) | 247s | 接近上限 |
| 00:25:31(6 分) | 368s | ❌ 已超過 300s 間隔 → agent 停止回報 |
事後檢查:wsl -l --running = 無執行中發行版;SSH 啟動的那支 anchor 進程同樣消失 (Windows 側只剩 Docker Desktop 於 14:49 啟動的 4 個 wsl.exe)。
✅ 反證成立:換一個啟動者(SSH session 取代 S4U 排程)結果完全相同, 死因是 WSL 的 VM 生命週期本身,不是排程工作的 context。
改成 S4U 排程每分鐘跑一次,主動偵測並復活(偵測 distro/anchor 狀態 → 重拉 → 補 portproxy)。 機制本身正確運作,但 boot.log 揭露了真相——每一個 tick 都報告「已經死了」:
00:16:32 distro running: False anchor alive: False → anchor (re)launched → stack 回來
00:17:33 distro running: False anchor alive: False → anchor (re)launched → stack 回來
即 distro 撐不過每次 tick 之間的不到 60 秒空檔。
⚠️ 這不是「常駐」,是「每分鐘重開機一次」。 後果:
- agent 每分鐘經歷一次容器停/起,心跳不連續
- 執行中的檢測任務會被腰斬(CINC/nmap 掃描動輒數分鐘)
- SQLite 與上傳中的證據檔在反覆硬停下有損毀風險
- 雲端顯示的
active是假象——是被 watchdog 戳出來的,不是它自己活著
同一台機器上,raymond 的 Docker Desktop VM 從 14:49 活到隔日 00:20(約 10 小時)未死, 而 B 的 distro 撐不過 60 秒。差別不在 WSL 設定,而在:
Docker Desktop 有
com.docker.backend——一個 Windows 使用者態的常駐進程(實測 7 個進程), 持續與 VM 通訊,所以 VM 一直「忙著」。B 案沒有這種 Windows 側常駐元件: 工作排程器是「跑完就結束」的模型。
這一點把 A 與 B 的死因統一了:兩者其實是同一個結構事實的兩面—— WSL VM 需要一個 Windows 使用者態的常駐看護,而「Windows 使用者態常駐」與「無人登入」本質衝突。 B 案並沒有繞開 A 的死穴,只是把「需要 Docker Desktop 且需要有人登入」 換成「需要一個永不結束的 Windows 側進程」——而排程器給不了它。
| 觀測 | 結果 |
|---|---|
| WSL distro 內 | docker-proxy LISTEN 0.0.0.0:9443 ✅(但這是 WSL 的網路命名空間內) |
Windows 側 netstat :9443 |
完全沒有 listener ❌ |
Windows 本機 127.0.0.1:9443 |
✅ True(WSL 的 localhost forwarding 自動生效) |
Windows 本機 192.168.50.160:9443 |
❌ False |
| 188 → 160:9443 | ❌ TCP 不通 |
根因:WSL2 NAT 模式只做 localhost 轉送,不會把容器埠曝露到 LAN 介面。 這與 T-7.1 §5.1 的 Docker Desktop 行為(自動綁 0.0.0.0、Windows 側看得到 listener) 明確不同——B 在此退步。
| 方案 | 結果 |
|---|---|
networkingMode=mirrored(理論上最乾淨) |
❌ 此機不支援:CreateInstance/CreateVm/ConfigureNetworking/0x803b0015,且退化成 networkingMode=None(更糟)。已還原 |
netsh interface portproxy(v4tov4) |
✅ 可行 |
portproxy 生效後(另加一條 inbound 防火牆規則 Guidant AI Agent B-case (9443)):
188 → nc -z 192.168.50.160 9443 → OPEN
188 → openssl s_client → CONNECTION ESTABLISHED, TLSv1.3
peer cert CN = 5bd79796…(=agent 指紋)
issuer CN = Guidant Agent CA (onprem-188)
WSL 的 eth0 是 DHCP(本次 172.31.22.105/20),每次 distro 啟動可能換 IP, 而 portproxy 規則寫死了目標 IP → 規則會靜默失效(TCP 連到黑洞,症狀是「雲端連不上但本機看起來正常」)。 因此安裝程式必須每次開機重算並更新規則(本次 watchdog 腳本已內建此邏輯並實測可運作)。
| 項目 | 實測 |
|---|---|
/sys/class/dmi/id/product_uuid |
不存在;且 /sys 是 sysfs,root 也不可寫(mkdir /sys/class/dmi: Operation not permitted) |
| 對 compose 的影響 | 與 T-7.1 §3.1 同樣是啟動硬失敗,該掛載行必須拿掉(160 上這行在 T-7.1 已被註解,本次沿用) |
| agent 行為 | WARNING:agent:device fingerprint source unreadable (/host/product_uuid) → graceful 退化成單源 |
distro /etc/machine-id |
21e1a92684b84f05aceba8f5d1f7276e(import 時生成,非 Windows 主機任何值) |
agent device_uuid |
5bd79796b9b2b1687fff565af29ca46cb1a00862b7f5a7a7d7e8195ef40ed0ed |
| 獨立驗算 | sha256("" + "|" + machine-id) = 5bd79796… ✅ 完全相符(同 T-7.1 §3.3 的退化公式) |
與 A 案對照:兩者都不是 Windows 主機身分—— A 綁在 docker_data.vhdx(machine-id 650cb79d…),B 綁在 guidant-wsl distro 的 vhdx。 漂移條件:wsl --unregister 後重 import 必然換一顆 machine-id,等同 A 的 factory reset。
⚠️
wsl --unregister漂移未實機執行:該操作會摧毀現場,而②已判死, 保留現場的價值高於補一個可從機制上確定的結論(machine-id 由 import 時生成,重 import 必變)。 此項標記為推定、未實測。
| 項目 | 實測 |
|---|---|
| WSL VM 數量 | 兩顆並存:B 的(0.79–0.91GB,start 00:07)+Docker Desktop 的(1.74GB,start 14:49) |
| 意義 | B 不是取代 A 的 VM,而是再開一顆;24GB 機器上兩顆約 2.5GB |
| Docker Desktop 影響 | ✅ 全程無影響(7 進程健在、8443 仍 listening、distro 未被觸碰)——demo 環境安全 |
| # | 權宜手段 | 正式做法應為 |
|---|---|---|
| 1 | 埠改 9443/283xx 避開既有 agent | 正式部署用標準埠;本次僅為與 demo 環境並存 |
| 2 | .env/CA/註冊 token 從 188 既有安裝複製 |
正式走安裝程式互動問答+TOFU 憑證確認 |
| 3 | 全程 SSH 遠端下 PowerShell 指令(經 188 跳板) | 正式不要求客戶開任何終端 |
| 4 | rootfs 直接從 cloud-images.ubuntu.com 下載 | 封閉網路客戶需離線 rootfs(本身即 B 案的另一項成本) |
| 5 | compose 檔沿用 T-7.1 已註解 product_uuid 的版本 | 出 Windows 專用 compose 檔(T-7.1 §3.5 已定調) |
wsl --unregister 指紋漂移——推定必變,未實機執行(見 §4)。🔴 B 案(WSL2+docker engine+工作排程器常駐)不可行。
依據:無人值守常駐是本案的存在理由(B 相對 A 的唯一賣點就是解 A 的登出全滅 P0)。 實測顯示 B 不但沒解掉,還把問題惡化——A 至少在有人登入時能穩定長跑(DD 的 VM 實測 10 小時未死), B 則是閒置 60 秒就凍結,只能靠外部 watchdog 每分鐘復活一次, 代價是心跳不連續、長時間檢測任務會被腰斬。
理論上還剩一招:把「握著 distro 的那隻手」做成 Windows 側常駐服務 (用 WinSW/NSSM 把 wsl.exe -d guidant-wsl -- sleep infinity 包成 Windows Service), 結構上模仿 Docker Desktop 的 com.docker.backend。這有機會成立(未測)。
但即使成立,也不建議:
這與 D18 當初否決 B 的理由(「常駐機制未驗、四層架構查修成本高、指紋仍是 VM 值、對原生版零複用」) 完全一致——本卡把其中「未驗」那一項補成了「已驗且失敗」。
D18 是在「B 案常駐機制未驗」的前提下做的裁示。本卡結論支持並強化該裁示:
(本節僅為完整性;以本卡結論不建議立案。若仍要立,下列為必要而非充分條件。)
| # | 相對 CM-1338/1339 需新增的工作 | 理由 |
|---|---|---|
| 1 | Windows 常駐服務(WinSW/NSSM)包 wsl.exe 保活 — 且需先做可行性實測 | §8.2;沒有它 B 不成立,而它本身未驗 |
| 2 | 帳戶模型改 S4U 具名帳戶(不可用 SYSTEM),含帳戶生命週期(改密碼/停用/網域政策)的處置 | §2.1;SYSTEM 被 WSL 硬拒 |
| 3 | portproxy 生命週期管理(每次開機重算 WSL IP、更新規則、失效偵測) | §3.2 |
| 4 | 離線 rootfs 供裝(封閉網路客戶無法連 cloud-images) | §6 第 4 項 |
| 5 | WSL distro 註冊在正確帳戶的安裝邏輯(per-user 註冊) | §1.2 |
| 6 | Windows 專用 compose 檔(拿掉 product_uuid 掛載) | §4(與 T-7.1 §3.5 同一項) |
| 7 | 常駐性回歸測試機制(且必須用無干擾觀測面) | §2.3.1 觀察者效應——沒有它,回歸測試會一直報「正常」 |
判斷 WSL 上的常駐性,不能用
wsl.exe觀察——它會把 distro 喚醒, 把「已經死了」變成「看起來活著」。
可用的無干擾觀測面:
| 觀測面 | 可信度 |
|---|---|
雲端 DB remote_agents.last_seen_at(心跳,間隔 300s) |
✅ 最可信;但判讀要等足夠久(age < 300s 不代表健康) |
Windows 側 wsl -l --running(只讀清單) |
✅ 實測不會喚醒 distro |
Windows 側 Get-Process vmmemWSL |
⚠️ 會誤導——VM 外殼可留著但 userspace 已凍結 |
distro 內 uptime -s vs 容器 StartedAt 交叉比對 |
🔍 事後解剖有效,但取證動作本身會 resume |
同一個陷阱在本卡出現兩次(先誤判 distro 活著、後誤判 anchor 有效), 兩次都是靠「換一個不碰它的觀測面」才看破。
| 項目 | 位置/名稱 | 建議 |
|---|---|---|
| WSL distro | guidant-wsl(Administrator 名下),C:\t74\wsl\guidant |
裁示後 wsl --unregister guidant-wsl 可完整清除 |
| 安裝目錄 | C:\t74\b(.env 含註冊 token 與 SeaweedFS 憑證,未入版控) |
清除時一併刪 |
| rootfs 與腳本 | C:\t74\wsl\*.tar.gz、C:\t74\*.ps1、C:\t74\boot.log |
可刪 |
| 排程工作 | T74-GuidantWslKeepalive(目前已 Disabled)、t74s4u、t74probe0 |
可刪(探路權宜,非產品的一部分) |
| 防火牆規則 | Guidant AI Agent B-case (9443) |
可刪(A 案的 …data plane (8443) 那條請保留,屬 T-7.1) |
| portproxy | 0.0.0.0:9443 → 172.31.22.105:9443 |
可刪(netsh interface portproxy delete v4tov4 listenport=9443 listenaddress=0.0.0.0) |
.wslconfig |
C:\Users\Administrator\.wslconfig(vmIdleTimeout) |
可刪;raymond 從頭到尾沒有這個檔,未被影響 |
| DEV 雲端資料 | guidant_ai.compliance.remote_agents id=8 WIN160-WSL-B |
建議驗收後清 |
🔴 未動的東西(確認清單):raymond 的 Ubuntu/docker-desktop distro、 Docker Desktop 本體與其容器、C:\onepack\ 內容、8443 那條防火牆規則、 191/STG/POC 任何環境(全程只讀 188 DEV,寫入僅 agent 自行 enroll 產生的 id=8 一筆)。