Skip to main content

Command Palette

Search for a command to run...

AX:用 Kubernetes 的方式编排 AI Agent

Updated
•5 min read•View as Markdown

最近 Google 在 GitHub 上低调开源了一个新项目 AX(Agent Executor / Agentic Orchestrator),口号:

一个高吞吐、声明式的编排器,用来在集群里运行数十亿个自治 Agent 工作负载。

为什么需要 AX:Agent 是一种新型工作负载

AX 的 README 开篇就点明了主旨:Agent 既不是无状态微服务,也不是跑完即退的批处理任务。它们有几个让传统基础设施头疼的特点:

  • 有状态:Agent 在生命周期内不断积累上下文、文件、中间结果,不能随便重启;

  • 要隔离:Agent 会执行模型生成的不可信代码,必须有沙箱和资源限额;

  • 常发呆:等模型返回、等工具调用、等人工确认,90% 以上的时间是空闲的——但状态必须留着;

  • 会花钱:一个陷入循环的 Agent 可以不停调用模型 API 直到账单爆炸。

Kubernetes 擅长管理无状态服务,也能管理 Job 之类的有状态任务,但是 Agent 工作负载稍有区别。AX 的思路是:把 Agent 任务变成一条可以 apply、可以观察、可以挂起、可以恢复的集群工作负载,就像 kubectl 运行容器化工作负载一样。

为此 AX 站在另一个 Google 开源项目 Agent Substrate 的肩膀上:Substrate 负责底层的沙箱执行与状态快照,AX 负责上层的声明式编排。两者都是 Apache-2.0 协议,都处于 pre-1.0 阶段(API 随时可能大变,官方 README 专门加了粗体警告)。

组件与功能

先看一张整体组件关系图:

AX 层:三个声明式原语 + 一个 kubectl 风格的 CLI

所有资源都用 ax.io/v1alpha1 的 YAML 清单描述,通过 ax apply -f 提交,体验和 Kubernetes 几乎一致。

Task —— 最小隔离执行单元

声明容器镜像、启动命令、CPU/内存请求与上限、环境变量,以及要挂载的 Workspace。每个 Task 都在独立沙箱里运行,是"便宜到可以随时创建、隔离、挂起、丢弃"的基本单元。一个 Agent 可以只用一个 Task,也可以在拆解问题时派生出一棵 Task 树——每个节点获得相同的沙箱和生命周期语义。

Task 的生命周期用 status.phase(Running / Suspended / Failed / Terminating…)和 Conditions(WorkspaceReady、Ready)表达,可以用 ax watch 实时观察。

** Workspace —— 让 Agent "热启动"的环境声明**

Agent 开始干活前的准备工作很繁琐:克隆指定分支的代码仓库、配置允许调用的 MCP 服务器、安装技能包(Skills)。Workspace 把这一切声明化:声明一次,被任意多个 Task 引用,运行器(Runner)会在命令启动前把环境物化到沙箱里。

有趣的是 goal 字段:你可以用自然语言描述想要的环境(例如"确保 Go 工具链从源码构建完成"),首次启动时会由一个内置 Agent 自动完成环境搭建。

Model —— 模型供应商配置

声明用哪家供应商、哪个模型、生成参数,以及存放凭证的 Kubernetes Secret。把模型配置变成集群资源后,换密钥、锁版本、调参数都只需要一次 ax apply,而不是翻遍每个 Agent 的环境变量。AX 自己的组件(比如执行 Workspace goal 的引导 Agent)也读 Model 资源。

ax CLI —— kubectl 风格的操作面

apply / get / describe / watch / delete 之外,还有几个 Agent 特色的动词:

  • ax suspend task <name> —— 对 Actor 做检查点(checkpoint),挂起并释放算力;

  • ax resume task <name> —— 从快照精确恢复到挂起前的状态;

  • ax ssh <name> -- <cmd> —— 直接 shell 进运行中的沙箱"围观"Agent 在干什么(需要 Task 声明 debug: true);

  • ax ctx / ax tunnel —— CLI 通过 gRPC 连接控制面,跟随当前 kube context 自动建隧道。

控制平面组件

部署在 ax-system 命名空间里:

  • ax-server:gRPC API 服务,CLI 的所有请求都打到这里;

  • ax-controller:核心协调器,把 Task 翻译成 Substrate 的 ActorTemplate 和 Actor,驱动挂起/恢复,轮询沙箱的就绪探针;

  • Redis:任务事件流与状态存储;

  • ax-task-runner:每个 Task 容器里的 PID 1。它是控制面和"Agent 实际进程"之间的桥梁——负责物化 Workspace、启动并监管用户命令、暴露 /healthz /readyz 和元数据服务,debug: true 时还提供 guest services(ax ssh 走的就是它)。你可以换成自己的 runner 镜像,只要遵守同样的契约。

Agent Substrate 层:沙箱执行与高密度复用

Substrate 的核心洞察是:Agent 大部分时间在 idle,因此可以把大量"Actor"(有状态的 Agent 进程)多路复用到少量"Worker"(物理 Pod)上。官方演示里,250 个有状态 Actor 只跑在 8 个物理 Pod 上,挂起/恢复做到亚秒级。

主要组件(部署在 ate-system 命名空间,ate 是 Agent Task Execution 的缩写):

  • ate-api-server:控制面 API(gRPC),管理 Actor/Worker 生命周期,状态存在 PostgreSQL;

  • ate-controller:Kubernetes 控制器,调和 WorkerPool 自定义资源(Worker 池的副本数、镜像、资源);

  • atelet:节点级 DaemonSet,监督本节点的 Worker Pod、协调快照与状态迁移;

  • atenet:网络层(基于 Envoy 的 router + egress 代理),负责把流量路由到"此刻正好醒着"的 Actor,必要时先唤醒再转发(request parking);

  • ateom-gvisor / ateom-microvm:跑在 Worker Pod 内部的沙箱助手,分别用 gVisor 和 microVM(Cloud Hypervisor)执行 Actor,负责 runsc 的检查点/恢复;

  • podcert-controller:为 Pod 证书签发提供 polyfill(该能力未来会进入上游 Kubernetes);

  • rustfs:S3 兼容对象存储,保存 Actor 的快照(挂起时内存页和文件系统的镜像)。

沙箱隔离默认用 gVisor(有 KVM 的机器上可用 microVM),Actor 出网流量受 egress 策略管控——用来防止烧钱。

一个 Task 的旅程:从 ax apply 到挂起恢复

把两层串起来,一个 Task 从下发到执行、再到挂起恢复的完整流程如下(对应本文实测的每一步):

几个值得注意的细节:新 Task 创建后默认是 Suspended,要显式 ax resume 才会占用 Worker——这正是“为 idle 而生“的设计;挂起后快照落在对象存储里,恢复时可以调度到任何一个有空闲容量的 Worker 上(Actor Teleport),不必回到原节点。

本机实测:在 macOS 上跑通 Hello World

测试环境

  • MacBook(Apple Silicon / arm64),Docker(OrbStack)运行中

  • Go 1.27.1、kubectl v1.37、ko v0.19.1(brew install ko)

  • 本地 K8s 用 kind(v1.37.0 节点),无需 GCP

ko 是 Google 开源的 Go 容器镜像构建工具:不需要写 Dockerfile,ko build ./cmd/myapp 就能把 Go 二进制打进一个极简基础镜像并推送到 registry;它还认得 YAML 里的 ko://github.com/xxx/cmd/app 镜像引用,ko apply / ko resolve 会自动构建、推送并改写成真实镜像 digest 后再提交给集群。可以理解为"Go 项目的 docker build + push + kubectl apply 合一体",Knative 等 Google 系项目都在用。AX 和 Substrate 的所有组件镜像都是靠它构建部署的。

拉起 kind 集群并安装 Agent Substrate

Substrate 自带了本机开发脚本,一条命令创建 kind 集群 + 本地镜像 registry(端口 5001):

git clone https://github.com/agent-substrate/substrate.git
cd substrate
hack/create-kind-cluster.sh
hack/install-ate-kind.sh --deploy-ate-system

脚本会用 ko 从源码构建 ateapi、atelet、atenet 等组件镜像推入本地 registry,再部署 PostgreSQL、rustfs 和全套控制面。装完验证:

$ kubectl get svc api -n ate-system
NAME   TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
api    ClusterIP   None         <none>        443/TCP   45m

部署 AX 控制平面

git clone https://github.com/google/ax.git
go install github.com/google/ax/cmd/ax@latest   # 安装 CLI
cd ax
make deploy AX_IMAGE_REPO=localhost:5001/ax     # ko 构建并部署 ax-server / ax-controller / Redis

创建 WorkerPool

这是一个 README 没写清楚的步骤:AX 的 Task 最终要调度到 Substrate 的 Worker 上,而新集群里没有任何 WorkerPool,直接跑任务会报 no free workers available。参照 Substrate 的 counter 示例,为 default 命名空间建一个 gVisor WorkerPool:

# workerpool.yaml
apiVersion: ate.dev/v1alpha1
kind: WorkerPool
metadata:
  name: ax-workers
  namespace: default
spec:
  replicas: 1
  workerImage: ko://github.com/agent-substrate/substrate/cmd/ateom-gvisor
  template:
    nodeSelector:
      ate.dev/substrate-version: "1d7ca8c"   # 与安装时节点打的版本标签一致
    resources:
      limits:   { cpu: "2",  memory: 2Gi }
      requests: { cpu: 250m, memory: 2Gi }

用 ko resolve 构建 ateom-gvisor 镜像并一并提交:

cd substrate && KO_DOCKER_REPO=localhost:5001 ko resolve -f ../workerpool.yaml | kubectl apply -f -

自建 task-runner 镜像

官方示例里的任务镜像 gcr.io/ax-substrate/ate-images/ax-task-runner 匿名拉取会被拒绝(403,该仓库未公开),所以本机自建一份。注意两点:kind 节点是 arm64,Makefile 里硬编码了 amd64,需要改;Substrate 要求 Actor 镜像必须用 digest 固定(镜像变了快照就失效):

cd ax
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" \
  -o bin/linux_arm64/ax-task-runner ./cmd/ax-task-runner
sed 's|bin/linux_amd64|bin/linux_arm64|' Dockerfile.task-runner > /tmp/Dockerfile.arm64
docker build --platform linux/arm64 -t localhost:5001/ax-task-runner:latest -f /tmp/Dockerfile.arm64 .
docker push localhost:5001/ax-task-runner:latest

(localhost:5001 会被 Substrate 的镜像缓存自动改写成集群内的 kind-registry:5000,无需手动处理。)

声明并运行 Hello World

最小的 Task——不带 Workspace 和 Model,开 debug 以便 ssh:

# hello-task.yaml
apiVersion: ax.io/v1alpha1
kind: Task
metadata:
  name: hello-ax
  atespace: default
spec:
  image: "localhost:5001/ax-task-runner@sha256:8cf61d06…"   # digest 固定
  env:
    - name: GREETING
      value: "Hello from AX!"
  debug: true
ax apply -f hello-task.yaml
ax resume task hello-ax        # 新 Task 默认是 Suspended,需要显式 resume
ax get tasks
NAME       ATESPACE   PHASE     ACTOR      WORKER-IP     AGE
hello-ax   default    Running   hello-ax   10.244.0.25   1m

ax describe 可以看到 Ready=True (TaskRunning)、WorkspaceReady=True (SetupComplete) 两个条件。

SSH 进沙箱看看

$ ax ssh hello-ax -- sh -c 'echo "$GREETING"; uname -a; ls -la /workspace'
Hello from AX!
Linux actor 4.19.0-gvisor #1 SMP Sun Jan 10 15:06:54 PST 2016 aarch64 GNU/Linux
total 0
drwx------ 1 root root   0 Sep 26 17:04 .
drwx------ 1 root root 100 Sep 26 17:04 ..

注意 uname 的输出:内核是 4.19.0-gvisor——这个 shell 确实跑在 gVisor 沙箱里,而不是普通容器。

挂起 / 恢复

先往工作目录写个文件,然后挂起:

ax ssh hello-ax -- sh -c 'echo "state survives suspend" > /workspace/memo.txt; date >> /workspace/memo.txt'
ax suspend task hello-ax
# PHASE 变为 Suspended,Worker 资源被释放

再恢复,读回文件:

$ ax resume task hello-ax && ax ssh hello-ax -- cat /workspace/memo.txt
state survives suspend
Sat Sep 26 17:11:44 UTC 2026

挂起时 Substrate 对 Actor 做了完整检查点(内存页 + /workspace 文件系统打成快照存入 rustfs),恢复后从断点继续——这正是"Agent 大部分时间 idle"场景的省钱关键:空闲时不占算力,醒来时上下文分毫不差。

M1 踩坑记录

  1. 代理传播问题:宿主机 shell 里的 HTTP_PROXY=127.0.0.1:7890 会被 kind 传进节点、被 buildkit 传进构建容器,而容器里的 127.0.0.1 是它自己——所有拉取全部失败。解决:创建集群和构建时把代理指向宿主机真实地址(如 192.168.x.x:7890),并把 kind-registry 加进 NO_PROXY。

  2. 官方 runner 镜像拉不动:示例中的 gcr.io/ax-substrate/ate-images/ax-task-runner 未公开,必须自建(且注意 arm64)。

  3. 镜像必须 digest 固定:用 :latest 会被 Substrate 拒绝(must be pinned by digest)。

  4. 快照 bucket 缺失:AX 控制器默认把快照写到 gs://snapshot-substrate-test-ax-substrate/,而本地 rustfs 只初始化了 ate-snapshots 桶,导致首次挂起报 NoSuchBucket。在 rustfs 里手动建同名桶即可: aws --endpoint-url http://rustfs.ate-system.svc:9000 s3api create-bucket --bucket snapshot-substrate-test-ax-substrate

  5. resume 竞态:任务刚创建就 resume,控制器可能先处理创建事件把任务又挂回去;再执行一次 ax resume 即可。

总结

AX 的思路很清晰:把沙箱、状态、挂起恢复、网络策略这些工程地基收敛成三个声明式原语和一套 kubectl 风格的工具链,底层由 Agent Substrate 提供高密度复用和亚秒级快照。

它适合的场景是:需要在集群里大规模、可审计、可管控地运行自治 Agent 任务的团队。需要提醒的是,项目目前处于非常早期(pre-1.0,README 明说随时可能有破坏性变更),文档与代码存在脱节(示例镜像私有、WorkerPool 需要自建、默认快照桶名对不上),离生产可用还有距离。但作为观察"Agent 基础设施"演进方向的样本,非常值得动手一试。

参考链接

More from this blog

FDE 是传统运维产品厂商的出路吗?

很多传统运维产品厂商,已经不缺产品了。 从 ITSM、CMDB、监控、自动化,到 IAM、数据中心管理和各类大屏,一个成熟厂商往往都能拿出一张很完整的产品地图。销售材料上看,客户运维部门的日常工作几乎都被覆盖了。 但项目一落地,味道就变了。 产品越多,集成越重;模块越全,边界越模糊;客户越大,定制越深。到了后期,项目成功本身也会变成麻烦:现场改出来的东西回不到主干,配置越来越像黑箱,谁都不敢轻易升

Jun 29, 20263 min read

绵里藏针才是 AIOps 的本质?

Agent 让运维编排变得柔性、可变、甚至自演进;但真正敢进入生产环境的 AIOps,仍然离不开坚实、受控、可审计、可回退的自动化底座。 从 Gartner 提出 AIOps 概念到现在,也大概有十年了。这么多年来,这个领域好像发生了很多变化,又好像没什么“本质”的变化。技术上,我们经历了传统机器学习、深度学习和神经网络、以及大模型和智能体这样“翻天覆地”的变化;业务上,我们面对的是更多品种、更大

May 31, 20263 min read

龙虾恐慌:AIOps 又要改名了?

ChatGPT 开始,把 AI 拉近到普罗大众的面前,让无数人感受到 AI 的亲民魅力。而龙虾,则把大模型驱动的自动化能力,突然间变得水灵灵、活泼泼地走进千家万户。它不只是“风口上的猪”,而是风口本身。热度高到让 Mac mini 一度断货,不知道这在不在库克的预料之内。 每代人都有每代人的鸡蛋,春节期间,我就领了我的鸡蛋。翻出古老的 MacBook Air M1,充值各种大模型。当然了,这个工具

Mar 9, 20261 min read

再见 2025

我猜不少人以为这个号废了吧?并没有,只是今年变化有点大,一直有种抄起键盘,无从说起的感觉,所以一直偷懒到今天,2025 的最后一天。 今年是我的第四个本命年,去年末一期播客里,大内说本命年不是灾年,是变化年,有危也有机。可是讲真啊,只看到危,没看到机。 各种因缘际会,从鹅厂跳槽到前东家,已经接近四年,第一个合同期已经进入尾声。除了前两年还在云原生领域嗷嗷叫,后两年基本都是些鸡零狗碎的东西了,用老东家的术语说是——偏离主航道,可谓是前景暗淡了。 一旦确定要滚蛋,反倒心思轻松起来,每天骑着我的小红车...

Jan 5, 20261 min read
【

【伪】架构师

345 posts