vatt'ghern jaskier's ballads
本文 3 個互動圖表在手機上以重點摘要呈現,互動版請以桌面瀏覽器開啟。

你給了 AI agent 一個 shell,它在 plan 裡寫下 rm -rf /, 然後真的執行了。問題不在於它會不會這麼做——非確定性的模型遲早會—— 而在於那一刻爆炸半徑有多大:是刪掉一個 throwaway sandbox, 還是刪掉你的 home directory。

替 AI agent 挑 sandbox——container、gVisor、microVM 的隔離強度與啟動成本怎麼換

Agent 跑的程式碼不是你寫的。 這句話聽起來理所當然,但它顛覆了過去十年容器化的隱含假設—— 我們把程式碼裝進容器,是為了「打包與分發的便利」與「資源的公平配額」, 而不是為了防範容器裡的程式碼本身就是敵意的。 一個由 LLM 即時生成、可能受 prompt injection 操控、可能在 hallucination 裡寫出破壞性指令的 process,它對隔離邊界的要求,跟你自己寫的 microservice 完全不同等級。 Docker 在這篇對照文裡把問題收斂成一條軸線:隔離強度(你信任邊界到什麼程度) 對上啟動成本(你要等多久、付多少記憶體),中間還夾著一個常被忽略的 syscall 相容性問題。

放在天平兩端的是三類方案。 最輕的一端是 native container——runc 用 Linux namespace (PID、network、mount、user)加 cgroups 把 process 圈起來, 但所有容器共用同一個 host kernel。 這意味著一個 kernel 漏洞、一個被 misconfigure 成 --privileged 的容器,隔離就破了。 中間一檔是 gVisor 的 runsc:它在 user-space 起一個叫 Sentry 的「應用程式 kernel」,攔截 agent 發出的每一個 syscall,自己實作網路、 檔案系統、記憶體管理,只有在絕對必要時(例如 disk I/O)才向 host kernel 發一個被嚴格過濾的 call。 最重的一端是 microVM——Firecracker、Kata Containers,以及 Docker 自家的 Docker Sandboxes——每個 sandbox 有自己的 guest kernel,跑在 KVM 之上, 隔離強度等同傳統 VM,但靠砍掉 BIOS/UEFI 開機流程、砍掉 USB 與 PCI bus 的裝置模擬,把啟動時間從「分鐘」壓到「秒」。

這不是三選一的純技術品味問題。 chroot 只隔離檔案系統、systemd-nspawn 加上了 process 與 network namespace、 native container 是 nspawn 的工業化版本、gVisor 換掉了 syscall 介面、 microVM 換掉了整個 kernel——這是一條從「弱隔離、近乎零成本」到 「強隔離、付出開機與記憶體代價」的連續光譜, 而 agent 工作負載把選擇權的天平推向了重的那端, 但推多遠取決於你的 agent 到底在做什麼。 先把五個方案攤在一張圖上,看清楚它們在「隔離 vs 啟動成本」平面上各自的座標。

下面這張散點圖把每個方案放進二維平面:橫軸是啟動延遲(log scale, 從 chroot 的「即時」到傳統 VM 的「30 到 60 秒」),縱軸是隔離強度 (從只隔離檔案系統到專屬 kernel 的硬體級隔離),圓的大小代表每個 agent 的記憶體足跡。 切換上方的「以哪個維度上色」可以把同一組座標換個角度看—— 依跨平台支援上色時,會立刻看出整片光譜裡只有 native container 與 Docker Sandboxes 能同時跑在 macOS 與 Windows 上。

switch the colour axis to re-read 5 approaches · 5 points × 3 colour modes

橫軸啟動延遲為 log scale(資料來源:Docker 對照文,chroot「即時」、container「毫秒」、microVM「秒級」、Lima VM「30 到 60 秒」)。 圓面積對應每 agent 記憶體(chroot ~1MB、container/nspawn ~10MB、傳統 VM ~4GB)。 右上角是理想區(強隔離、快啟動),目前只有 microVM 接近它。

橫軸啟動延遲為 log scale(資料來源:Docker 對照文,chroot「即時」、container「毫秒」、…

container 啟動快但隔離弱;gVisor 攔截所有 syscall 換到更強隔離;microVM 是唯一接近「強隔離×快啟動」理想區的選項。

這張圖把整篇對照的張力濃縮成一個視覺事實:右上角(強隔離且快啟動) 是空的。 chroot 與 native container 擠在左下到左中——啟動極快但隔離弱; gVisor 往上爬了兩格(攔截 syscall 換來明顯更強的隔離)但啟動延遲也跟著往右; microVM 是唯一接近右上的點,代價是它的圓最大(記憶體足跡最重)。 沒有任何一個方案同時佔據左上角,這就是「隔離強度與啟動成本必須交換」 這句話的幾何形狀。 接下來四個 H2 各拆一條軸:隔離邊界擋住什麼、啟動延遲與記憶體怎麼算、 syscall 相容性的隱性稅、最後才是怎麼依場景選。

隔離邊界:共用 kernel、user-space kernel、專屬 kernel 各擋住什麼

隔離強度不是一個刻度,而是一個「攻擊面」的問題: agent 裡的惡意程式碼要逃出 sandbox,需要穿過幾道牆、那些牆是用什麼做的。 三類方案在這件事上的差別,本質是「syscall 從 agent 走到實體硬體時, 中間經過誰的手」。

native container 的答案是:直接走到 host kernel。 namespace 與 cgroups 是 kernel 內的隔離原語,它們重新命名了 PID、 重新映射了 network interface、限制了能看到的 mount point, 但執行 syscall 的還是同一個 host kernel。 這帶來兩個具體弱點。 第一,chroot 等級的逃逸——一個有 root 權限的 process 可以 chroot 出去, 這是 1979 年就知道的老問題,namespace 收緊了它但沒消除它。 第二,也是 agent 場景最致命的:Docker-in-Docker。 agent 常需要自己跑 docker builddocker compose, 傳統作法是把 host 的 docker socket 掛進容器,或用 --privileged 模式跑一個內層 daemon——而 --privileged 按 Docker 自己的說法 「給了容器 process 提升的權限,大幅削弱隔離」。 一個能跑 privileged container 的 agent,基本上就等於能控制 host。

gVisor 的答案是:先走到 Sentry,再由 Sentry 決定要不要轉給 host。 Sentry 是一個用 Go 寫的、跑在 user-space 的「應用程式 kernel」, 它攔截 agent 發出的每一個 syscall——不是過濾,是攔截後自己處理。 Sentry 內部實作了自己的 Linux 網路 stack、自己的檔案系統語意、 自己的記憶體管理。 agent 呼叫 open()read()socket() 時,這些 syscall 根本不會抵達 host kernel; 只有當 Sentry 真的需要碰實體資源(例如把資料寫到底層 disk)時, 它才會向 host 發一個「極度受限、重度過濾的安全 call」。 換句話說,host kernel 暴露給 agent 的 syscall 攻擊面, 從整個 Linux ABI(300 多個 syscall)縮小到 Sentry 真正會用的那一小撮。 這是質的差別:即使 host kernel 有某個冷門 syscall 的漏洞, agent 也碰不到那個 syscall。

microVM 的答案最乾脆:你連 host kernel 都碰不到, 因為你有自己的 kernel。 每個 microVM 跑在 KVM 之上,有一個獨立的 guest kernel; agent 的 syscall 全部由 guest kernel 處理, guest 與 host 之間只透過 hypervisor 的 virtio 介面通訊。 要從 agent 逃到 host,攻擊者得先攻破 guest kernel, 再攻破 hypervisor 的 VMM——這是雲端 multi-tenant 隔離賴以為生的邊界, AWS Lambda 與 Fargate 底下跑的就是 Firecracker。 Docker Sandboxes 在這個基礎上又加了第三層: 每個 sandbox 有自己的 Docker Engine daemon。 agent 在裡面跑 docker pull 時, 指令打到的是 sandbox 內部的 engine,看不到 host 上的任何 image、 container、或其他 sandbox——這正面解掉了前面 native container 那個 Docker-in-Docker 必須 --privileged 的死結。

三條 syscall 路徑放在一起比,差別就具體了。 切換下方的分頁,每一頁畫出 agent 的一個 syscall 從發出到觸及實體硬體 所經過的層,以及攻擊者要逃出 sandbox 必須攻破哪幾道。

switch tabs to compare 3 syscall paths · 3 isolation models

agent process(不可信) syscall 直達 共用 host kernel —— namespace + cgroups 只在這層內做圈圍 實體硬體(與 host 及所有其他容器共享) 攻擊面 = 整個 Linux syscall ABI(300+)

逃逸只需攻破一道(host kernel)。一個 kernel CVE 或一個 --privileged 誤設,就能從 agent 拿到 host。攻擊面是整個 Linux ABI。

agent process(不可信) 每個 syscall 被攔截 Sentry(user-space 應用 kernel) 自實作 net / fs / mm,多數 syscall 在此終結 只有必要時(disk I/O) host kernel —— 只收到極少數、重度過濾的安全 call 攻擊面 = Sentry 真正轉發的那一小撮

逃逸需先攻破 Sentry,再攻破 host kernel 暴露的那一小撮 syscall—— 兩道牆,且第二道牆的開口被縮到極小。代價是每個 syscall 多繞一層 user-space。

agent process(不可信) 專屬 guest kernel(每 sandbox 一份) virtio 介面 hypervisor / VMM(KVM)—— Firecracker、Kata host kernel(agent 完全碰不到) 攻擊面 = guest→hypervisor 逃逸(雲端等級難度)

逃逸需依序攻破 guest kernel 與 hypervisor——這是 AWS Lambda / Fargate 的 multi-tenant 邊界。代價是每個 sandbox 扛一份完整 kernel 的開機與記憶體。

同一個 agent syscall 在三種模型下的旅程:箭頭越多、越往下繞,逃逸要攻破的牆越多。

把這三張路徑圖疊在腦中,「隔離強度」就不再是形容詞而是可數的: container 一道牆、gVisor 兩道牆且第二道牆開口極小、microVM 兩道雲端級的牆。 對 agent 工作負載來說,這個差別直接對應「一次 prompt injection 能造成多大破壞」。 如果 agent 只能在自己的 sandbox 裡 rm -rf, 那是一個可拋棄的環境被毀; 如果它能逃到 host,那是你整台開發機。

這條光譜有它的歷史層理,值得快速回看一遍,因為每一層都是在補前一層的洞。 chroot 是 1979 年 Version 7 Unix 的產物,它只做一件事:把 process 看到的 filesystem root 換掉。 它連 process 視圖都不隔離——在 chroot 裡跑 ls /proc, 你仍然看得到 host 上的所有 process。 systemd-nspawn 補上了 PID namespace 與 network namespace, 於是 process list 被限制在容器內,這是質的進步, 但它仍共用 host kernel,且 Linux-only、開發者社群採用度低。 native container(runc)是 nspawn 那套 namespace + cgroups 的工業化版本, 加上 image 分發、overlay filesystem、orchestration 生態—— 但隔離模型本質沒變,還是共用 kernel。 gVisor 是第一個真正換掉介面的:它不再信任 host kernel 來執行 agent 的 syscall。 microVM 則乾脆連 kernel 都換成專屬的。 每一層解決的是前一層「最致命的那個洞」,而 agent 場景把這個洞放大到不能忽視。

Docker Sandboxes 在 microVM 基礎上的三層架構值得單獨拆解, 因為它正面回應了 agent 場景的三個具體威脅。 第一層是 hypervisor 隔離:每個 sandbox 有自己的 Linux kernel, 一個 sandbox 內的 kernel 被攻破,影響不會擴散到 host 或其他 sandbox。 第二層是網路隔離:每個 sandbox 有獨立的 network stack, 多個 sandbox 之間、以及 sandbox 與 host 之間預設不能互通, 並可用 network policy 強制流量規則——這擋住的是「agent A 橫向移動到 agent B」。 第三層是 Docker Engine 隔離,也是它和其他 microVM 方案的差異化點: 每個 sandbox 有自己的 Docker Engine daemon。 agent 在裡面跑 docker pulldocker compose 時, 指令只對內部 engine 生效,看不到 host 上或其他 sandbox 裡的任何 Docker 服務。 這三層疊起來,把「agent 跑 Docker 工具」這件原本必須 --privileged 的危險操作,變成了在硬體邊界內安全完成的常規動作。

啟動延遲:毫秒、秒、分鐘的數量級鴻溝

agent 工作負載有個和傳統服務很不一樣的特性:sandbox 是用過即丟的。 每一次 agent 要跑一段不可信程式碼,理想上就開一個全新的 sandbox, 跑完即銷毀,避免狀態污染與橫向移動。 這讓「啟動延遲」從一個一次性的部署成本,變成一個會乘上 agent 互動次數的高頻成本。 一個 agent 一天可能要開幾百次 sandbox,每次多等一秒就是好幾分鐘的累積延遲。

Docker 對照文給的數量級是這樣的。 chroot 近乎即時——它只是換個 filesystem root,沒有任何重量級初始化。 native container 是毫秒級——runc 建立 namespace、套用 cgroups、掛 overlay filesystem,這些都是 kernel 內的快速操作。 傳統 VM(文中以 Lima 為例)是 30 到 60 秒——要跑完整的 BIOS/UEFI、 bootloader、kernel init、systemd 起服務這一整套開機流程。 microVM 的關鍵創新就是把 VM 這 30 到 60 秒壓到「秒級」: Firecracker 砍掉 BIOS/UEFI(直接從一個最小化的 boot protocol 進 kernel)、 砍掉 USB 與 PCI bus 模擬(只留 virtio 的 net 與 block)、 用一個極簡的 VMM。 Docker Sandboxes 文中標的是「秒級(首次 pull 之後)」—— 也就是 image 拉下來快取住之後,後續每次起 sandbox 是秒級。

gVisor 的啟動延遲沒有單一漂亮數字,因為它介於兩者之間: 起 Sentry 這個 user-space kernel 比起 runc 多了一些初始化, 但遠不到開一個真 VM 的等級。 Docker 的對照表把它標為「varies」。 從實務經驗看,runsc 的冷啟動通常在數百毫秒量級—— 比 runc 慢一個檔次,但仍遠快於 microVM。 這把它放在散點圖上不前不後的位置:用一點點啟動延遲, 換到比 container 強得多的隔離,但又沒有 microVM 那麼重。

microVM 能把 VM 開機壓到秒級,背後是一系列具體的「砍掉」決定, 值得點出來,因為它解釋了為什麼 microVM 不是「比較快的 VM」而是「不同的東西」。 Firecracker 是 Amazon 在 2018 年開源的 VMM, 它用 Rust 寫成、刻意維持極小的程式碼量以縮小自身的攻擊面, 並且是 AWS Lambda 與 Fargate 底下實際在用的隔離技術。 它做的事包括:跳過 BIOS/UEFI,用一個最小化的 boot protocol 直接把控制權交給 guest kernel;不模擬 USB、不模擬 PCI bus,只提供 virtio 的 net 與 block device; 用一個剝到只剩必要功能的 device model。 這些砍除把「開機要做的事」從幾百項縮到個位數,於是開機從分鐘變成秒、 甚至在某些配置下到百毫秒級。 代價是 guest 裡能用的硬體周邊極少——但對「跑一段 agent 程式碼」這種工作負載, 你本來就不需要 USB 或 GPU 的完整模擬。

啟動延遲只是成本的一半,另一半是記憶體足跡,而記憶體的故事 在「同時跑很多 agent」這個 agent-orchestration 的常態下會放大。 Docker 給的單機數字:chroot 約 1MB、systemd-nspawn 約 10MB、 傳統 VM(Lima)約 4GB 外加 4 CPU。 這些單看不嚇人,但乘上 fleet size 就分道揚鑣: 10 個 nspawn agent 約 100MB,10 個傳統 VM 約 40GB—— 後者直接吃掉一台開發筆電的全部記憶體。 下面的滑桿讓你把 agent 數量從 1 拉到 64, 看四種方案的總記憶體足跡如何發散。

drag to scale the agent fleet from 1 to 64 · total RAM across 4 approaches

10
chroot(1MB/agent) container/nspawn(10MB/agent) microVM(~128MB/agent) 傳統 VM(~4GB/agent)
Y 軸為 log scale(足跡跨四個數量級)。基準資料:Docker 對照文——chroot ~1MB、 nspawn ~10MB、Lima VM ~4GB;microVM 取 Firecracker 級 ~128MB 的代表值。 曲線是 per-agent 足跡乘以 N 的線性外推,未計入 host daemon 固定開銷。

Y 軸為 log scale(足跡跨四個數量級)

10 個並發 agent 時,microVM 記憶體約 1.3 GB,傳統 VM 達 40 GB;microVM 以 VM 級隔離換 container 密度。

把滑桿拉到 10,圖上立刻重現 Docker 給的對照: 傳統 VM 線爬到 40GB——已經是一台高階筆電的記憶體上限; microVM 線停在約 1.3GB——同樣是專屬 kernel 的隔離,記憶體只要 VM 的 三十分之一。 這 30 倍差距正是 microVM 之所以能讓「每個 agent 一個獨立 kernel」 從不切實際變成可行的關鍵——VM 級的隔離強度配上接近 container 的部署密度。 再把滑桿拉到 64,傳統 VM 線衝破 256GB(你需要一台 server), 而 microVM 還在 8GB 左右、container 線根本貼著底部。 這就是為什麼「強隔離一定很貴」的舊直覺,被 microVM 改寫了一半: 隔離強度可以保留,貴的那部分(整個 VM 的記憶體)可以削掉一個數量級。

syscall 相容性:user-space kernel 的隱性稅

前面兩條軸(隔離、成本)是大家會主動去比的, 但 gVisor 還有第三條軸,常常在 PoC 階段沒被注意、上 production 才咬人: syscall 相容性。 因為 Sentry 是「重新實作」了一個 Linux kernel, 它能否正確處理 agent 程式碼用到的每一個 syscall、每一個 ioctl、 每一個 /proc/sys 的語意, 取決於 gVisor 團隊有沒有把那條路徑實作到位。

Linux 的 syscall ABI 有 300 多個 syscall,加上海量的 ioctl 命令、 fcntl flag、socket option、以及程式間接依賴的 /proc 偽檔案語意。 Sentry 覆蓋了絕大多數常見路徑,但長尾永遠存在。 具體會踩到的場景包括:某些需要特定 ioctl 的硬體加速 (GPU passthrough、特定的網路 offload)、 冷門的 /proc 條目、 依賴精確 mmap 旗標行為的記憶體映射技巧、 以及對 kernel 版本特性敏感的程式(gVisor 報告的 kernel 版本不一定 對得上 agent 程式碼期待的那個)。 這些在跑 lspython script.py 時都沒事, 但 agent 一旦要跑某個編譯需要特殊 syscall 的工具鏈,或某個探測 /proc/self/... 的 runtime,就可能撞上「在普通容器裡能跑、 在 gVisor 裡報 ENOSYS」的差異。

相容性問題還有另一面,是性質而非覆蓋率的:syscall overhead。 每一個 syscall 都要被 Sentry 攔截、在 user-space 處理、可能再轉發—— 這比 native container 那種「syscall 直達 host kernel」多了好幾層。 對 syscall 密集的工作負載(大量小檔案 I/O、頻繁的 epoll、 高頻 network syscall),這個 overhead 是可量測的延遲增加。 gVisor 的平台選擇會放大或縮小這個成本:用 ptrace 平台時每次攔截要走 一次 context switch,開銷較大;用 KVM 平台時攔截走硬體輔助, 開銷小得多但需要巢狀虛擬化支援。 這是「攔截每個 syscall 來換隔離」這件事必然附帶的稅。

然後是更現實的一條:平台與生態。 gVisor 只支援 Linux——沒有 macOS、沒有 Windows。 對一個團隊在 Mac 與 Windows 筆電上開發 agent 的場景, 這直接把 gVisor 從本機開發的選項裡劃掉。 社群採用度也相對有限, 意味著當你撞上某個 Sentry 沒實作好的冷門 syscall 時, 能找到的前人經驗與 workaround 比 runc 那種主流路徑少得多。 相對地,native container 與 microVM-based 的 Docker Sandboxes 都標榜跨平台(macOS、Windows、Linux), 這在「開發機是 Mac、production 是 Linux」的常態下是個不能忽視的因素。

值得補一句的是,syscall overhead 與相容性這兩個成本,本質上是同一個設計選擇 的兩面:因為 Sentry 選擇「自己重新實作 kernel 語意」而不是「過濾後轉發給 host kernel」,它才能把 host 攻擊面縮到極小(這是隔離強度的來源), 但也因此每個 syscall 都要在 user-space 走一遍 Sentry 的實作(這是 overhead 的 來源),而且 Sentry 的實作有沒有涵蓋你用到的那條冷門路徑(這是相容性的來源)。 換句話說,gVisor 的強隔離與它的兩個隱性稅不是各自獨立的優缺點, 而是同一個架構決定的必然配套——你不可能只要前者不要後者。 這跟 microVM 形成有趣的對比:microVM 用「完整 guest kernel」換到了 完整的 syscall 相容性(agent 看到的就是一個真 Linux kernel,沒有重新實作的長尾), 代價改付在記憶體與啟動上。兩條路是把成本記在不同的帳上。

這條軸的結論不是「gVisor 不好」——它在 Google 內部與 GKE Sandbox 跑了多年,是成熟技術——而是「它的成本不在啟動延遲或記憶體, 而在相容性與平台」。 一個只跑標準 Linux 工具、且部署在 Linux 上的 agent, gVisor 的隔離/成本比非常漂亮; 一個要跑各式各樣不可預測工具、又要在 Mac 上開發的 agent, 這條隱性稅可能讓 gVisor 變得不划算。

把五個方案的所有維度攤在一張表上,是時候做最後的橫向比對。 點任一欄位 header 可以依該維度重排—— 依「跨平台」排會把能上 Mac 的方案頂到一起, 依「隔離」排會把 microVM 與 gVisor 頂到一起, 方便你按自己最在意的約束做歸類。

click any column header to sort · 6 columns × 5 rows

五種 sandbox 方案的六維對照(資料來源:Docker 對照文;點擊 header 可重排)
方案 kernel 模型 隔離強度(1–5) 啟動量級 每 agent RAM 跨平台
chroot 共用 host kernel 1(僅檔案系統) 即時 ~1MB Linux only
systemd-nspawn 共用 host kernel 2(+ proc/net) <1s ~10MB Linux only
container (runc) 共用 host kernel 2(DinD 需 privileged) 毫秒 ~10MB+ macOS / Windows / Linux
gVisor (runsc) user-space kernel(Sentry) 4(syscall 攔截) 百毫秒級(varies) 中等 Linux only
microVM(Docker Sandboxes) 專屬 guest kernel(KVM) 5(hypervisor 邊界) 秒級(首次 pull 後) ~128MB macOS / Windows / Linux
沒有任一行全綠:container 在啟動與記憶體欄全綠,但隔離欄全紅; microVM 隔離與跨平台全綠,但記憶體與啟動是中等代價。 這張表的「沒有完美行」正是選型必須依場景的根本原因。

沒有任一行全綠:container 在啟動與記憶體欄全綠,但隔離欄全紅; microVM 隔離與跨平台全綠,但記憶體與…

五種方案無一全綠:container 隔離強度僅 2;microVM 隔離達 5 且跨平台,代價是每 agent 128 MB 與秒級啟動。

這張表把全篇的張力固定下來:五行裡沒有一行是全綠的。 container 系(chroot、nspawn、runc)在啟動與記憶體欄漂亮, 但隔離欄全是紅字——共用 kernel 這件事是它們的天花板。 gVisor 把隔離欄拉到 4,代價是相容性欄(這張表沒列, 但前一節講透了)與 Linux-only 的平台限制。 microVM 把隔離與跨平台都拉到綠,代價是啟動與記憶體的中等成本。 選型就是在這張表上,依你最不能妥協的那一欄,往下找第一個能接受的方案。

怎麼選:依爆炸半徑與工具可預測性分流

把所有軸收攏成一條可自我歸類的規則,關鍵不是「哪個最強」, 而是兩個正交的問題:這個 agent 的爆炸半徑有多大、它要跑的工具有多可預測。

第一象限——爆炸半徑大、跑不可信或不可預測程式碼:選 microVM。 這是 agent 場景的預設。 如果 agent 會執行任意 model 生成的程式碼、會自己 docker build、 會碰到網路、而一次逃逸的代價是你的開發機或你的 CI runner, 那 microVM 的硬體級隔離是唯一能讓你晚上睡著的選項。 Docker Sandboxes 在這裡的額外賣點是 per-sandbox Docker Engine—— 它讓 agent 能正常用 Docker 工具鏈,卻不需要 --privileged, 解掉了 native container 在 DinD 上的死結。 記憶體成本(每 agent ~128MB)對「同時跑十幾個 agent」是完全付得起的, 啟動的秒級延遲在快取 image 之後也可接受。

第二象限——爆炸半徑大、但工具可預測且只跑 Linux:gVisor 是甜蜜點。 如果你的 agent 只跑標準 Linux 工具(沒有奇怪的 ioctl、不依賴冷門 /proc 語意),且整套部署都在 Linux server 上(沒有 Mac 開發機 的需求),那 gVisor 用「比 container 重一點點的啟動」換到「接近 VM 的隔離」, 這個交換比非常划算。 GKE Sandbox 多年的 production 紀錄是它的背書。 前提是你願意承擔 syscall 相容性的長尾風險,並在 CI 裡實測過你的工具鏈 在 runsc 下不會撞 ENOSYS

第三象限——爆炸半徑小、程式碼半可信:native container 夠用。 如果跑的不是任意 model 生成的程式碼,而是你信任度較高的內部 agent、 且最壞情況的破壞被其他層(唯讀掛載、network policy、沒有 host socket) 擋住了,那 container 的毫秒啟動與 1MB 級記憶體沒有理由放棄。 強隔離是有成本的;當爆炸半徑本來就小時,付那個成本是 over-engineering。 關鍵紀律是:絕不為了讓 agent 跑 Docker 而開 --privileged—— 一旦你發現自己需要 privileged,那就是訊號,該往 microVM 升級了。

第四象限——只需檔案系統視圖、零安全訴求:chroot/nspawn。 這幾乎不該出現在 agent 場景,因為 agent 的核心問題就是安全; 但如果你只是要給 agent 一個乾淨的 filesystem 視圖、 完全不指望它擋住惡意行為(安全靠別處保證), 那 chroot 的近乎零成本是合理的。 把它放在這裡是為了完整——它是光譜的一端,提醒你「隔離強度」 這條軸真的可以一路滑到「幾乎沒有」。

串起來的決策樹很短:先問爆炸半徑——大就排除 container 系; 再問跨平台——要 Mac/Windows 就排除 gVisor; 最後問工具可預測性——不可預測就傾向 microVM 的完整 kernel。 對絕大多數「在本機開發、會跑不可信程式碼」的 agent, 這三個問題會把你導向同一個答案。

The call:對會執行 model 生成程式碼、又要在 Mac/Windows 上開發的 agent——也就是今天大多數 agent——選 microVM(Docker Sandboxes 這類),用每 agent ~128MB 與秒級啟動換來 hypervisor 級隔離; 只有當你能保證工具鏈純 Linux 且可預測時,gVisor 才是更省的甜蜜點; native container 留給爆炸半徑本來就小、且你已用別層防線兜底的情況。