你可能已經會用 Kubernetes 跑一個 AI Agent:一個 Pod、一份容器、一個工作目錄。但當 100 個 Agent 大部分時間都在等人、等模型或等工具時,難道真的要留 100 個 Pod 在原地發呆?這正是 Agent Substrate 教學 要解開的問題。
這篇專為第一次接觸 Agent 基礎設施、但已經知道 Docker 與 Kubernetes 基本概念的讀者而寫。我們會用官方 kind 路徑建立 counter lab,親手觀察 Actor/Worker 分工、記憶與檔案如何跨 suspend/resume 保存,再用「Actor 多於 Worker」的實驗看排隊、延遲與隔離。這是一份可重跑的 lab,不是一張宣傳成績單;數字要由你的機器、版本與設定自己量。
先說結論:一個 Actor 不是一個 Pod
一句話記住:Actor 是「可休眠、可搬家的工作身份」;Worker 是「已暖機、暫時借住的沙盒空房」。
- Actor 保存邏輯身份、生命週期與最近 snapshot,不必永遠綁在同一個 Pod。
- Worker 是預先啟動的 Kubernetes Pod;同一時間最多承載一個 Actor,Actor 休眠後就能接下一個。
- ActorTemplate 像房型與設備清單,固定映像、sandbox 類型、volume 與 snapshot 規則。
- WorkerPool 決定到底準備幾間空房;Actor 比 Worker 多時,系統靠 suspend、resume 與 request parking 周轉。
- Snapshot 才是搬家箱。
Full可包含程序記憶、rootfs 差異與 DurableDir;Data只保存支援 snapshot 的資料 volume。
所以它不是另一套教模型思考的 Agent framework,也不是把 Kubernetes 換掉。它把「高頻、短暫的 Actor 狀態切換」移到專門控制面,仍讓 Kubernetes 負責低頻的 Worker Pod 與基礎設施。想先補上 Agent 本身的迴圈,可先讀 AI Agent Harness 是什麼;想比較上層控制面,則搭配 Google AX 教學。

Agent Substrate 教學:先拆開 5 個零件
① Actor:「住客」而不是房間
Actor 是一個 ActorTemplate 的實例,以 (atespace, name) 識別。它可在 SUSPENDED、RESUMING、RUNNING、SUSPENDING 等狀態間移動;實際所在的 Worker 會改變,身份則不變。白話說,你追蹤的是「這位住客」,不是某一間房號。
② Worker 與 WorkerPool:「暖機空房」與房間總數
WorkerPool 是 Kubernetes CRD,會被 controller 轉成一組預先啟動的 Worker Pod。Worker 是控制面資料庫裡對應某個 Pod 的動態紀錄,狀態是 FREE 或 ASSIGNED。不要輸入 kubectl get actor;Actor、Worker 與 ActorTemplate 不在 Kubernetes etcd,要用 kubectl ate get ... 查詢。官方 CLI 文件把這個邊界寫得很清楚。
③ ActorTemplate:「可重建的房型」
ActorTemplate 定義容器、volume、snapshot policy、sandbox class 與 Worker 選擇規則。建立時,系統會先產生 golden snapshot,之後的新 Actor 可從這份共同起點快速啟動。Template 被視為不可變;要升版就建立新 template,而不是偷偷修改舊房型。
④ Snapshot:「記憶與檔案要裝進哪種搬家箱」
Full snapshot 的語意最接近「把現場一起搬走」:程序記憶、rootfs 差異與 DurableDir 都可納入;Data snapshot 只保留支援 snapshot 的 volume,程序會重新啟動。這兩者不能混成一句「狀態一定都會保留」。本篇 counter demo 使用 Full 的 onCommit,所以 RAM counter 與 file counter 都應延續。
⑤ Router 與 request parking:「櫃台負責叫醒,也負責排隊」
流量帶著 ate-target-actor: <atespace>/<actor> 進入 Router。Actor 若休眠,Router 會要求控制面分配 Worker 並恢復 snapshot;WorkerPool 暫時全滿時,預設 request parking 會在有限 budget 內重試,而不是無限排隊。這個 commit 的預設 park budget 是 5 秒、最多同時停放 1,024 個請求;這是背壓上限,不是「永遠不會回 503」的保證。
開始前:這不是三行指令的輕量工具
截至 2026 年 9 月 23 日,官方最新 GitHub release 是 v0.1.0,但 main 已有大量後續變動。官方 README 同時明示:專案仍在 early development、尚未準備好投入 production,API 可能改動,而且它不是 Google 正式支援產品。因此 lab 要固定 commit,跑完也要刪掉,不要直接接公司 secret 或真實客戶資料。
以下流程固定到 commit 6f45aeb2。你的電腦至少要有可用的 Docker daemon、Go 與 kubectl;官方腳本會透過 Go 管理 kind 等相依工具。microVM 路徑另需要 KVM 或支援 nested virtualization 的環境,官方在本機 microVM 指南中把 8 CPU/16 GiB 視為控制面加一個 microVM Worker 的舒適下限;第一次請先跑預設 gVisor。
git clone https://github.com/agent-substrate/substrate.git cd substrate git checkout 6f45aeb2dd656ec65fcdd9dff360e8397052cf42 docker info go version kubectl version --client
如果 docker info 連不到 daemon,先停在這裡。不要為了保住「實測」標籤,臨時把 host 權限、KVM 或付費雲端資源擴大;先把操作理解透,再選一台真正適合做 disposable cluster 的機器。
Lab 1:用 kind 跑 counter,證明記憶與檔案都續得上
先照官方 development quickstart 建立本機 cluster、安裝控制面與 counter demo。這一步會建 local registry、PostgreSQL、RustFS 與多個系統元件,不是只啟動一個 app container。注意:create-kind-cluster.sh 會先刪除同名 kind cluster;以下另取專用名稱,避免覆蓋預設的 kind。
export KIND_CLUSTER_NAME=substrate-tutorial hack/create-kind-cluster.sh hack/install-ate-kind.sh --deploy-ate-system hack/install-ate-kind.sh --deploy-demo-counter go install ./cmd/kubectl-ate kubectl ate create actor my-counter-1 \ -a ate-demo-counter --template counter kubectl port-forward -n ate-system svc/atenet-router 8000:80
另開終端送兩次請求,先記錄回應中的 RAM counter、file counter 與 worker 資訊,再查 Actor 與 Worker。不要只看 HTTP 200;你要保存「狀態、數值、Worker Pod、開始與結束時間」四種證據。
for i in 1 2; do
curl -sS -X POST \
-H 'ate-target-actor: ate-demo-counter/my-counter-1' \
http://localhost:8000/
done
kubectl ate get actor my-counter-1 -a ate-demo-counter
kubectl ate get workers
接著量 suspend 與下一次 request-triggered resume。macOS 可用 time 看整條命令的牆鐘時間;Linux 想留下格式化秒數可用 /usr/bin/time -f '%e'。這不是純 runtime latency:CLI、API、snapshot upload、Router 與本機排程都可能混在裡面,所以要把測量邊界寫在紀錄旁。
time kubectl ate suspend actor my-counter-1 -a ate-demo-counter kubectl ate get actor my-counter-1 -a ate-demo-counter time curl -sS -X POST \ -H 'ate-target-actor: ate-demo-counter/my-counter-1' \ http://localhost:8000/ kubectl ate get actor my-counter-1 -a ate-demo-counter kubectl ate get workers
驗收不是「counter 有回應」而已:兩個 counter 都應從先前數值繼續,Actor 應從 SUSPENDED 回到 RUNNING,而 Worker Pod 可能與前一次不同。若 RAM counter 歸零但 file counter 延續,先檢查 template 是否真的使用 Full scope;這正是 Full 與 Data 的可觀察差別。
Lab 2:4 個 Actor 壓 2 個 Worker,看見密度的代價
密度不是「同一個 Worker 同時塞進很多 Actor」。目前一個 Worker 同時最多承載一個 Actor;密度來自大量休眠 Actor 共享少量可重複使用的 Worker。官方 parking demo 已把比例做成 4 個 Actor 對 2 個 Worker:
hack/install-ate-kind.sh --deploy-demo-parking
for id in p1 p2 p3 p4; do
kubectl ate create actor "$id" \
--atespace ate-demo-parking --template parking
done
kubectl port-forward -n ate-system svc/atenet-router 8000:80
先請求 p1、p2 把兩個 Worker 佔滿,再用第三個終端請求 p3。它會進入有限時間的 parking;在 5 秒 budget 內 suspend p1,p3 才有機會取得空位。用 curl -w 留下狀態碼與總等待時間:
curl -s -H 'ate-target-actor: ate-demo-parking/p1' http://localhost:8000
curl -s -H 'ate-target-actor: ate-demo-parking/p2' http://localhost:8000
kubectl ate get workers
curl -s -w '\nHTTP %{http_code} / %{time_total}s\n' \
-H 'ate-target-actor: ate-demo-parking/p3' http://localhost:8000
# 在另一個終端、5 秒內執行
kubectl ate suspend actor p1 --atespace ate-demo-parking
你真正要量的是:Worker 數、同時執行 Actor 數、休眠 Actor 數、parking wait 的 median/p95、budget_exhausted 與 parking.rejected。官方提供 atenet.router.parking.active、atenet.router.parking.wait.duration、atenet.router.parking.rejected,也能從 Router 的 /statusz?format=json 看目前 parking 狀態。宣傳中的 10×、30× 或 sub-500ms 只能當專案自述與特定 demo;你的 production 決策必須以自己的 snapshot 大小、儲存距離、Actor 工作集與併發分布重測。
Lab 3:三個故障注入,別只測 happy path
故障 A:Worker Pod 消失
先在 disposable kind cluster 找到承載測試 Actor 的 Worker Pod,再刪除它,持續觀察 Actor 狀態、Worker replacement 與最後一次 snapshot 之後的資料。這是一個破壞性演練,不要在共用或 production cluster 執行。
# 先 suspend,留下已知可恢復的 external snapshot kubectl ate suspend actor my-counter-1 -a ate-demo-counter kubectl ate resume actor my-counter-1 -a ate-demo-counter # 再送一次請求,製造「snapshot 之後」的新狀態 curl -sS -X POST \ -H 'ate-target-actor: ate-demo-counter/my-counter-1' \ http://localhost:8000/ # 從 kubectl ate 的 JSON 找到目前 Worker Pod;確認後才刪除 actor_json=$(kubectl ate get actor my-counter-1 \ -a ate-demo-counter -o json) worker_ns=$(printf '%s' "$actor_json" | \ jq -er '.status.workerAssignment.workerNamespace') worker_pod=$(printf '%s' "$actor_json" | \ jq -er '.status.workerAssignment.workerPod') printf 'delete %s/%s\n' "$worker_ns" "$worker_pod" kubectl -n "$worker_ns" delete pod "$worker_pod" kubectl ate get actor my-counter-1 -a ate-demo-counter kubectl ate revert actor my-counter-1 -a ate-demo-counter kubectl ate resume actor my-counter-1 -a ate-demo-counter
這個 commit 的 E2E 測試預期:刪除正在承載 Actor 的 Worker Pod 後,Actor 會進入 CRASHED;kubectl ate revert actor 可讓它回到 SUSPENDED,再從最後一份 external snapshot 恢復,因此 snapshot 之後的新變更會遺失。要特別誠實的是,同一 commit 的舊 upgrade runbook 仍寫著 CRASHED 沒有 recover verb,文件與實作尚未同步;所以把 revert 視為實驗性救援路徑,而不是穩定復原承諾。驗收要記錄「何時停止服務、何時進入 CRASHED、revert 是否成功、資料回到哪一版」,而不是只等 Pod 重新 Ready。
故障 B:沒有 egress policy 就必須拒絕
官方 egress demo 的設計是 policy default-deny:沒有 EgressPolicy 的 Actor 不會取得 tunnel。先部署 demo,建立 Actor 後在「尚未建立 allow rule」時送一次外連請求,保存拒絕結果;接著建立只允許測試目標的 policy,再做正向與負向探針。最小自動測試如下:
hack/install-ate-kind.sh --deploy-demo-egress ./demos/egress/test-egress.sh --cleanup
不要只測「允許的站能連」。還要測未列入的 host/port、非 Actor 身份的 Pod,以及 suspend/resume 前後政策是否一致。更完整的 Agent 網路邊界可搭配 Tailscale Agent fleet 教學與 Agent runtime controls。
故障 C:Worker 全滿時,排隊不能無上限
在 parking demo 先故意讓所有 Worker 忙碌,再觀察請求超過 park budget 後是否得到可辨識的 503;接著把 --parked-request-max=0 暫時加入 Router args,比較 fail-fast 與有限排隊。這個實驗回答的是「你的 API 要等多久才應該明確失敗」,不是追求 0 個 503。
Agent Substrate 教學:怎麼做一張可信的量測表
每一輪至少固定:commit SHA、Docker/kind/Kubernetes 版本、CPU/RAM、sandbox class、Worker 數、Actor 數、snapshot scope、snapshot bytes、物件儲存位置與併發數。然後把「控制面成功」和「使用者真的收到回應」分開計時。
- 生命週期:suspend、resume、first response 各自記 median 與 p95。
- 狀態正確性:RAM counter、DurableDir file counter、Actor VERSION 是否符合預期。
- 密度:Actors/Workers 比例、Worker busy ratio、snapshot storage bytes。
- 背壓:parking active、wait duration、rejected、budget exhausted。
- 復原:Worker 中斷後的不可用時間、資料回復點與人工介入。
- 隔離:允許與拒絕 egress、跨 Actor 殘留、secret 是否進入 snapshot。

gVisor、microVM、一般容器池怎麼選?
- 一般容器池:最容易上手,適合可信 workload 與低隔離風險;但「一個長駐容器對一個 Agent」會讓大量 idle 實例持續佔資源。
- gVisor WorkerPool:是官方 kind quickstart 的預設路徑,能練習 Full snapshot、Actor 搬移與 request parking;它仍需要你驗證 syscall 相容性、snapshot 大小與實際延遲。
- microVM WorkerPool:隔離邊界與 guest RAM snapshot 更接近 VM,但本機需要 KVM/nested virtualization,設定與資源門檻較高。Apple Silicon 的 nested virtualization 還受硬體世代限制。
選擇順序不是「microVM 一定最好」,而是先回答:你的 code 是否不可信、需要保留多少程序狀態、可接受多少恢復延遲、主機是否支援所需虛擬化。想先理解 microVM 與容器差異,可讀 Docker Sandboxes 與 microVM 教學。
能不能進 production?用 6 道硬閘門回答
- 版本可重現:映像 digest、Substrate commit、SandboxConfig 與 WorkerPool manifest 都能重建。
- 狀態語意清楚:每個目錄、RAM 狀態與 secret 都知道是否會進 Full/Data snapshot。
- 容量會失敗:parking budget、queue 上限與 503 行為已在尖峰併發下驗過。
- 故障有 recovery objective:Worker、node、Router、database、object storage 任一中斷,都有實際 RTO/RPO 紀錄。
- 隔離是負向證據:未授權 egress、跨 Actor 殘留、Kubernetes API、metadata service 與其他 snapshot 都確實被拒絕。
- 升級與清理可演練:Template 不可變、Actor 可刪除、snapshot 有 retention,舊 WorkerPool 能安全退場。
若任何一項只能回答「理論上應該」,目前就把它留在 lab。官方自己的 README 已把 production readiness 判為未完成,threat model 也明列 worker reuse、snapshot 權限、policy 同步、API authorization 與 lateral movement 等風險;把專案名稱前的 Google 版權誤讀成產品支援,是最危險的捷徑。
清理:lab 成功的最後一步
先刪 demo,再刪 kind cluster。不要只關掉終端;確認 container、network、local registry 與 volume 都沒有殘留。
hack/install-ate-kind.sh --delete-demo-egress hack/install-ate-kind.sh --delete-demo-parking hack/install-ate-kind.sh --delete-demo-counter hack/delete-kind-cluster.sh docker ps -a docker volume ls docker network ls
常見問題
1. Agent Substrate 是 Agent framework 嗎?
不是。它管理執行環境、Actor 生命週期、snapshot、Worker 分配與 routing;Agent 如何推理、呼叫模型與工具,仍由你的 harness 或上層 framework 決定。
2. 一個 Worker 可以同時跑很多 Actor 嗎?
同一時刻不行。目前一個 Worker 最多承載一個 Actor;高密度來自時間上的 multiplexing,也就是大量休眠 Actor 輪流使用少量 Worker。
3. Suspend 後 RAM 一定會保留嗎?
不一定。要看 snapshot scope。Full 可保存程序記憶;Data 只保存支援 snapshot 的 volume,程序會依 resume source 冷啟動或從 golden 恢復。
4. 30× oversubscription 代表我的 cluster 也能省 30 倍嗎?
不能這樣推論。那是官方 demo 的 Actor/Pod 比例,不是你的成本保證。snapshot 大小、idle ratio、併發、儲存距離與恢復頻率都會改變答案。
5. 用 kind 跑通就能上 production 嗎?
不能。kind 適合理解拓撲與重現生命週期;production 還要補高可用 database、object storage、權限、升級、節點故障、容量與安全驗證。
6. gVisor 和 microVM 哪個比較安全?
要看威脅模型與設定。兩者提供不同隔離邊界與相容性取捨;不要把 runtime 名稱當成完整安全證明。真正要驗的是 node、network、snapshot、identity、secret 與 worker reuse 的整條邊界。
7. Request parking 會不會讓請求永遠排隊?
不會無限排隊。它有 park budget 與最大容量;超時或 parking lot 已滿時仍會失敗。你要把 503 視為容量訊號,而不是把 queue 調到無限大。
8. 現在最適合的使用方式是什麼?
先做 disposable lab。固定版本,跑 counter、parking、egress 與 worker 故障注入,留下可重跑紀錄;等六道 production 閘門都有證據,再評估小流量 pilot。
給新手的 5 個重點
- Actor 是身份與狀態,Worker 是暫時執行槽;不要把 Actor 等同 Pod。
- Full 與 Data snapshot 決定「醒來時還記得什麼」,必須用 counter 實驗驗收。
- 密度來自休眠輪替,不是把無限 Actor 同時塞進一個 Worker。
- Parking 有時間與容量上限;503 是系統誠實回報飽和的方式。
- 官方目前仍標示未準備好 production;先累積故障、安全與升級證據。
接著閱讀
左右滑動查看更多推薦
結語:先證明「醒來還是同一個它」
Agent Substrate 最值得帶走的,不是 10× 或 30×,而是新的資源心智模型:Actor 是可休眠、可搬家的工作身份;Worker 是暖機好的沙盒空房。當你把兩者拆開,才有機會讓大量 idle Agent 不再各佔一個 Pod。
你的下一步很具體:在 disposable kind cluster 跑完一次 counter suspend/resume,保存前後數值、Actor VERSION、Worker Pod 與時間,再做 4 Actor/2 Worker 的 parking 實驗。想把這套基礎設施思維接回完整 AI 工作流,可到 AlphaLab 課程繼續建立從 Agent Harness 到部署驗收的完整能力。
