☰
谷歌开源 AX:任务状态为什么不能放 Kubernetes CRD——用 Redis Streams 编排十亿级 agent 的架构拆解
2026/9/26 2:22:54 网站建设 项目流程

谷歌开源 AX:任务状态为什么不能放 Kubernetes CRD——用 Redis Streams 编排十亿级 agent 的架构拆解

一句话概括:AX 是谷歌开源的 agent 编排运行时。它值得看的地方不是"又一个 agent 框架",而是它为了扛住海量短生命周期任务,主动放弃了 Kubernetes CRD 作为任务状态存储——理由写在官方设计文档里,很直白。

所有事实来自 Google 官方仓库github.com/google/ax、其DESIGN.md/docs/与官网agentexecutor.io,抓取时间 2026-09-25。文末列了全部来源。


一、先对齐时间线:它不是今天才出生的

先把"新"这个字说准,避免误判成熟度:

时间事件
2026-03-30仓库google/ax创建
2026-08-20已有doctor诊断子命令、sidecar PID 跟踪等提交
2026-09-20一次改变项目定位的重构(commitdc4f36c)
2026-09-23补上对外路线图(PR #381)
2026-09-25单日 5 个提交;当天登上 GitHub 日榜第 4

仓库当前状态(2026-09-25 抓取):Apache-2.0,主语言Go,11,133 star、536 fork、41 个 open issue,最近一次 push 是当天 13:25 UTC。

真正让它值得写的是 9 月 20 日那次重构。commit message 原文写得很清楚:

AX is undergoing a significant redesign, moving away from a single CLI with an embedded Python harness toward a general-purpose orchestration layer for agentic tasks.

拆成三个二进制:ax-server暴露 gRPC API 接收 Task manifest,ax-controller作为水平扩展的 reconciler消费Redis Streams,ax-task-runner在沙箱化 worker 内执行任务。任务状态现在存在 Redis 而不是 Kubernetes CRD,这样系统能处理数百万个短生命周期任务而不压垮 etcd。

旧的 Python harness、ATE client、SQL 事件日志和 skill 示例全部删除。

翻成一句话:它原本是个带内嵌 Python harness 的 CLI,现在是一套通用编排层。这个转向本身就是本文的主题。


二、它的立论:agent 是一种新的工作负载

README 里那句话是整个项目的地基:

Agents are a new kind of workload. They are neither stateless microservices nor run-to-completion batch jobs.
(Agent 是一种新的工作负载。它既不是无状态微服务,也不是跑完即走的批处理作业。)

官网把这条论证展开得更完整:

Agent 型负载是有状态、突发、长时间运行的 actor:它高强度计算一分钟,然后等待模型响应、工具响应或人工审批。为无状态微服务或可预测批处理作业设计的传统编排器,在维持空闲沙箱运行这件事上会变得极其昂贵,同时又缺乏对亚秒级 suspend/resume 的原生支持。

这段话解释了两个设计选择的动机:

  1. 为什么要有suspend/resume——agent 大部分时间在等,等待期间不该占着 CPU 和内存。
  2. 为什么不能复用现成的编排器——Kubernetes 擅长"让 N 个副本一直活着",而 agent 需要的是"活着、随时能冻住、随时能解冻、且解冻后内存和文件系统都还在"。

值得注意的是这句自我定位:AX 官方明说它不是 agent SDK,而是运行 agent 的系统。这和 LangChain 一类框架不在一个层面。


三、四个(或三个?)原语

这部分有个值得注意的细节:官方三处表述目前没对齐。

  • 仓库 README 写的是 “three small primitives”:Task/Workspace/Model
  • 官网首页写的是 “four small primitives”,多一个Gateway(网络策略)
  • docs/concepts.md目前详述的是三个:Task/Workspace/Model

我按最新最全的官网口径列四个,并标注文档现状:

原语解决什么文档状态
Task在隔离沙箱里跑不受信任的 agent 代码,带 CPU / 内存限额concepts.md 有
Workspace预先把 Git 仓库、MCP server、skill 包接好,让每个 agent 一启动就是热的concepts.md 有
Gateway网络策略:把出站流量锁到显式 allowlist 的主机和端口上,并给入站请求注入凭据concepts.md 暂无对应章节
Model平台自己用哪个 LLM、参数是什么、API key 从哪个 Kubernetes Secret 取concepts.md 有

Model的定义值得单独说一句,官方原话是 “AModelis not a model.”——它不是模型,而是一份具名的模型配置。把它做成一种资源的意义在于:轮换密钥、钉住新模型版本、收紧某个参数,都变成一次ax apply,而不是去翻遍所有 task 定义。AX 自己的组件也读它,比如根据goal规划 workspace 的时候。

Workspace里最有意思的是goal字段。它允许你用一句自然语言描述"环境该长什么样",首次启动时 runner 会把这句话交给一个 agent 去把环境装完:

apiVersion:ax.io/v1alpha1kind:Taskmetadata:name:data-analysisspec:workspaces:-name:python-envgoal:"Set up a Python 3 development environment"

四、核心架构:为什么状态不放 CRD

这是全文最硬的部分。DESIGN.md开头直接给了理由:

把数百万个短生命周期任务存成 Kubernetes CRD 会把 etcd 推到它的舒适区之外(个位数 GB 的存储上限、写入速率瓶颈、控制面性能退化)。AX 把状态放在 Redis,并用 Redis Streams 作为 API server 与水平扩展的 controller 池之间的工作队列。

这是对 Kubernetes 能力边界一次相当坦率的承认:etcd 适合存"集群的期望状态",不适合存"每分钟产生几十万条的作业记录"。CRD 不是用来当高吞吐作业队列的。

数据流是这样的:

ax apply -f task.yaml │ ▼ ax-server ← 无状态 gRPC API(:8080)+ /healthz │ 存状态 & 发布事件 │ ▼ Redis ← Task Hashes + Event Streams + PubSub │ XREADGROUP(Streams) │ ▼ ax-controller ← 水平扩展的 reconciler 池 │ gRPC(Control API) │ ▼ Agent Substrate ← atespace 供给 / actor 创建与激活 / worker 分配

四个二进制各司其职:

二进制职责
ax开发者 CLI。应用 manifest、查看与 watch 资源、向集群打隧道
ax-server无状态gRPC API,监听 8080。校验 manifest、持久化到 Redis、发布事件
ax-controllerreconciler worker。消费 Redis stream,在 Agent Substrate 上供给 atespace 与 actor,把任务推向期望状态。加副本即扩容
ax-task-runner每个任务容器内的入口程序。引导 workspace、提供 metadata 服务、运行 agent 命令

注意ax-server是无状态的,状态全在 Redis——所以 API 层可以直接水平扩。控制面 API 是 gRPC 服务ax.v1alpha1.AX,健康检查则是普通 HTTPGET /healthz返回 200。

一个有意思的细节:Task的WatchTask是服务端流式 RPC,会实时推送 status 与 condition 的迁移,而不是让你轮询。


五、沙箱内部:PID 1 是谁

docs/sandbox.md写得很具体,而且官网演示里有一段真实终端输出可以互相印证:

$ ax ssh test -- ps -o pid,cmd PID CMD 1 /usr/local/bin/ax-task-runner 12 go build ./...

每个任务容器都以ax-task-runner作为 PID 1 启动。它启动后做四件事:

  1. 加载Task和所有绑定的Workspacespec
  2. 在80 端口起一个 metadata / guest 管理守护进程
  3. 首次运行时按绑定顺序准备每个 workspace:克隆 Git 仓库、设置 skill 路径;如果绑定带goal,就把这个 goal 交给一个 Antigravity agent 去完成环境安装。这个 agent 需要容器里有GEMINI_API_KEY,默认给 10 分钟,可用AX_BOOTSTRAP_TIMEOUT改。在包括 agent 运行在内的所有 workspace 完成之前,任务一直报 not-ready。
  4. 把spec.command作为子进程拉起(工作目录是第一个 workspace),并把AX_METADATA_URL和spec.env注入环境变量,然后监督它

两个容易忽略的工程设计:

  • runner 作为 PID 1 始终不退出,即使命令已经结束。所以命令跑完之后 metadata server 仍在应答,ax ssh仍然能进得去;
  • 沙箱停止或挂起时,runner 给命令的进程组发SIGTERM,等 10 秒,然后才强杀。这比"直接 SIGKILL"给了 agent 收尾的机会。

Metadata server 同时说 HTTP/1.1 和h2c,端点如下:

端点方法返回说明
/healthzGETtext/plain存活。永远 200
/readyzGETtext/plain就绪。workspace 初始化期间返回503,克隆、MCP 配置、skill 都就位后返回 200
/metadata/v1alpha1/ax/taskGETapplication/yamlTask 启动配置(不含status 和挂起标志)
/metadata/v1alpha1/ax/workspacesGETapplication/yaml所有绑定的 Workspace,按绑定顺序的多文档流

spec.debug: true时,同一个端口还会通过 gRPC 提供 Agent Substrate 的 guest services(进程服务 + 文件系统服务,前者正是ax ssh的底座)。它们默认关闭,因为允许在沙箱内任意执行进程和读写文件——ax ssh会拒绝连接没有开启它们的任务。这个默认值是合理的:把"能进容器执行任意命令"变成一个必须显式打开的开关。


六、网络:Task 没有 Service,也没有 Ingress

这一条对习惯了 Kubernetes 的人会有点反直觉。docs/networking.md明确写:

Task 不会得到属于自己的 Kubernetes Service 或 Ingress。

所有发往 task 的请求都走 Agent Substrate 的atenet router(ate-system命名空间下的atenet-routerService)。路由器只读一个 header:

ate-target-actor: <atespace>/<task>

它的行为是:解析出这个 actor 跑在哪台 worker 上,如果它处于挂起状态就先唤醒,然后把请求代理过去。Host和:authority留给你自己的应用,只有这个 header 负责选目标。

从集群内访问:

curl-H"ate-target-actor: default/task123"\http://atenet-router.ate-system.svc.cluster.local/metadata/v1alpha1/ax/task

从本机访问,先端口转发再照常说:

kubectl-nate-system port-forward svc/atenet-router8001:80curl-H"ate-target-actor: default/task123"http://localhost:8001/readyz

gRPC 则把同一个 header 放进 outgoing metadata:

ctx=metadata.AppendToOutgoingContext(ctx,"ate-target-actor","default/task123")resp,err:=client.SomeMethod(ctx,req)

"路由器发现目标是挂起的就先唤醒再代理"这一点是整套设计的关键。它意味着一个被 checkpoint 到磁盘的 agent 仍然持有一个稳定的寻址标识,外部无需知道它当前是否在内存里。没有这个能力,suspend就只能省自己的钱、断自己的路。


七、底座:Agent Substrate 凭什么能扛

AX 自己不做沙箱,它跑在Agent Substrate(github.com/agent-substrate/substrate)上面。这个底座的官方指标相当激进:

指标官方口径
密度运行数百万沙箱,密度比标准容器运行时高10 倍
恢复延迟恢复操作亚 500 毫秒
吞吐每秒 500 次以上suspend/resume 激活
隔离原生零信任内核与网络隔离;支持microVM 与 gVisor
复用演示中把一个集群的约 250 个有状态 actor 复用到仅 8 个物理 pod 上(30 倍以上超配)

它的核心思路一句话讲完:把一大组 actor 映射到一小撮就绪 worker 上,赌的是"agent 类应用大部分时间在发呆"。官方把这套能力叫 Actor Teleport——挂起时把易失内存和文件系统状态一起做全量快照,唤醒时在 worker 池里任意一台机器上恢复。

Substrate 自己并不排斥 Kubernetes。它用 K8s 做基础设施供给和 worker 生命周期管理(worker 就是 Pod),在 Pod 和 Pod 自动扩缩容之上再叠一层 agent 专用的调度,为的是更低延迟。这个分层是合理的:K8s 管"机器",Substrate 管"actor",AX 管"任务语义"。

顺带一提,Substrate 的生态里还有个 CNCF Sandbox 项目kagent也在用它跑沙箱化的有状态 agent 负载——说明这不是一个只服务自家产品的内部组件。


八、跑起来:前置条件 → 步骤 → 验证

8.1 前置条件(不满足会直接失败)

需要说明
已装 Agent Substrate 的 Kubernetes 集群这是硬前置。Substrate 落在ate-system命名空间,Control API 暴露在api.ate-system.svc.cluster.local:443,AX 默认去这里找它
Go 与kubectl装 CLI 用
ko与一个集群能拉取的镜像仓库构建并部署控制面
Redis由make deploy部署,状态与工作队列都在它上面

先确认 Substrate 起来了,再往下走:

kubectl get svc api-nate-system

8.2 三步跑通第一个任务

# 1) 装 CLI(会装到 $(go env GOPATH)/bin,确认它在 PATH 里)goinstallgithub.com/google/ax/cmd/ax@latest# 2) 部署控制面(自动部署 Redis,然后用 ko 构建并部署控制面镜像)makedeployAX_IMAGE_REPO=<你的仓库># 3) 跑第一个任务(一个文件里同时有 Task + Workspace + Model)ax apply-fexamples/task.yaml ax get tasks# NAME ATESPACE PHASE ACTOR WORKER-IP AGE# task123 default Running task123 10.20.3.67 1m

ax的 CLI 形状是刻意照着kubectl做的:apply/get/describe/watch/delete,外加几个 agent 特有的动词:

axwatchtask task123# 实时流式看 phase 与 condition 迁移axsshtask123 --ls-la/workspace# 进沙箱翻一翻axsuspendtask task123# 打检查点并暂停ax resume task task123# 从断点继续

ax还会跟随你当前的 Kubernetes context——切集群后它会自动在后台解析并隧道到那个集群的控制面:

kubectx staging-cluster&&ax get tasks kubectx prod-cluster&&ax get tasks ax--context=dev-cluster get tasks# 或者不改 context 直接指定

8.3 怎么验证"挂起真的保住了状态"

这一步别只看命令返回 0。官网演示给了一条可复现的验证路径——在挂起前写一个文件,恢复后读它:

axsshtest--touchnotes.txt# 挂起前落一个文件axsuspendtasktestax resume tasktestaxsshtest--lsnotes.txt# 期望输出:notes.txt

官方演示的实际输出就是notes.txt。这说明恢复回来的不只是进程,而是文件系统状态。要验证内存状态,官方提供了 Counter Demo:一个有状态 Go HTTP server,反复 suspend/resume 后计数不回零。

8.4 在任务里读自己的配置

容器内不需要任何 SDK 就能自省配置。下面这段轮询/readyz直到 workspace 就绪,用的全是标准库:

importosimporttimeimporturllib.errorimporturllib.request BASE=os.environ["AX_METADATA_URL"].rstrip("/")defwait_ready(timeout=600.0,interval=2.0):"""轮询 /readyz,直到 workspace 初始化完成(503 -> 200)。"""deadline=time.monotonic()+timeoutwhiletime.monotonic()<deadline:try:withurllib.request.urlopen(f"{BASE}/readyz",timeout=5)asresp:ifresp.status==200:returnTrueexcepturllib.error.HTTPErrorasexc:ifexc.code!=503:raisetime.sleep(interval)returnFalseif__name__=="__main__":print("workspace ready:",wait_ready())

九、五个容易踩的坑

#坑正确做法
1没装 Agent Substrate 直接部署 AX前置缺失不会立刻报"缺依赖",官方在 PR #399 里专门修过这个体验:改用例前 demo.sh 会做 preflight 检查 CLI、kubectl 和 Substrate Control API Service。自己部署时请先跑kubectl get svc api -n ate-system
2ax ssh连不上guest services 默认关闭。必须在 Task 里设spec.debug: true,否则ax ssh会拒绝连接而不是报权限错误
3在Model里设temperature官方在 2026-09-25 的 PR #397 里把示例中的temperature全部删掉,理由是 “We don’t recommend this parameter.”。照抄老示例会带上一个官方不推荐的参数
4bootstrap 超时带goal的 workspace 会交给 agent 装环境,默认10 分钟。装大工具链会超时,用AX_BOOTSTRAP_TIMEOUT(Go duration 格式)调大
5ax delete看起来"卡住"这是设计如此:删除会把任务推进Terminating,controller 拆完沙箱后才移除记录,ax delete会一直阻塞到这一步完成

关于任务状态机,官方建议只等我Ready这一个 condition:

Condition何时为 True
WorkspaceReady所有 workspace 都完成初始化。此后一直为 True
Ready任务在跑且WorkspaceReady为 True。这个才是该等的

挂起会把Ready置为 False、reason 为TaskSuspended;恢复再置回。


十、现状与风险(必须说清楚)

官方自己在 README 顶部挂了警告:

我们仍在积极调整核心概念、协议与规范。在稳定版发布之前,很可能会引入重大破坏性变更。

加上底座 Substrate 也标着 pre-1.0、明确不保证向后兼容,所以:

  • 不适合现在押上生产。它的价值目前在于读它的设计,而不是把它当基础设施。
  • 它不是托管服务。要自建 K8s 集群、自己装 Substrate、自己维护 Redis 和镜像仓库——"零运维"不在承诺里。
  • Redis 是控制面的有状态依赖。这是换取 etcd 之外高吞吐的代价,也意味着备份和可用性要自己设计。
  • "十亿级"是设计目标而非实测数据。官方措辞是 “allowing you to scale to billions of concurrent agent sessions per cluster”,我找到的公开演示规模是 250 actor / 8 pod。两者不矛盾,但不要混为一谈。

十一、路线图里最值得盯的四件事

官方路线图(docs/roadmap.md)里有几项一旦落地会明显改变能力边界:

  1. Task 之间可以分叉——把一个运行中或已挂起的 Task(含检查点内存与 workspace 文件系统状态)分叉成多个并行任务,用来并发探索投机执行路径。这是 suspend/resume 的自然延伸,也是"有状态"真正变成优势的地方。
  2. 空闲检测与自动挂起——持续监控 actor 活动(进程执行、I/O、网络流量、活跃 gRPC/SSH 会话),自动对空闲任务触发检查点,回收 worker 的 CPU 与内存来提高密度。
  3. 给每个 Task 发 SPIFFE 身份——签发 X.509-SVID,让任务、MCP server 与内部服务之间走零信任 mTLS。
  4. runner 层自动采集 OTel 指标、分布式 trace 与结构化 agent 轨迹(prompt、模型响应、工具调用、进程执行、生命周期迁移),且不需要用户容器做任何埋点。

第 4 条对做 agent 评测和强化学习的人价值最大:轨迹采集从"每个框架各写一遍"变成运行时自带。


十二、一句话总结

AX 最有信息量的地方不是它提供了Task/Workspace/Gateway/Model这几个声明式原语——那是自然的设计选择。最有信息量的是它为了这些原语,选择不把状态放进 Kubernetes CRD,并在设计文档里直说了 etcd 的存储上限与写入瓶颈。一家把 Kubernetes 做成行业标准的公司,在自己的新编排器上给 etcd 划了一条边界,这条边界比任何功能列表都更值得记住。


参考来源

来源URL核验状态
Google 官方仓库 AXhttps://github.com/google/ax✅ 正文与元数据均已抓取
AX READMEhttps://raw.githubusercontent.com/google/ax/main/README.md✅ 正文完整抓取
AX 设计文档 DESIGN.mdhttps://raw.githubusercontent.com/google/ax/main/DESIGN.md✅ 正文完整抓取(含 etcd 论证与架构图)
AX 核心概念 docs/concepts.mdhttps://raw.githubusercontent.com/google/ax/main/docs/concepts.md✅ 正文完整抓取
AX 沙箱 docs/sandbox.mdhttps://raw.githubusercontent.com/google/ax/main/docs/sandbox.md✅ 正文完整抓取
AX 网络 docs/networking.mdhttps://raw.githubusercontent.com/google/ax/main/docs/networking.md✅ 正文完整抓取
AX 路线图 docs/roadmap.mdhttps://raw.githubusercontent.com/google/ax/main/docs/roadmap.md✅ 正文完整抓取
AX 官网https://agentexecutor.io/✅ 正文完整抓取(含演示终端输出与四原语表述)
Agent Substrate 仓库https://github.com/agent-substrate/substrate✅ README 完整抓取(含密度/延迟指标与 demo 说明)
GitHub API 仓库元数据https://api.github.com/repos/google/ax✅ 2026-09-25 抓取
重构 commitdc4f36c说明https://github.com/google/ax/commit/dc4f36cdba647f110b34fd69adf31eb2bec37ca3✅ commit message 完整抓取
2026-09-25 GitHub 日榜https://raw.githubusercontent.com/jobcher/new-blog/refs/heads/main/content/new/daily/github_trending_2026-09-25.md✅ 第三方日报,AX 列第 4
谷歌 Cloud 官方博客(Agent Executor)https://cloud.google.com/blog/products/ai-machine-learning/agent-executor-googles-distributed-agent-runtime⚠️未抓取成功(连接失败)。该链接由 Substrate README 引用,本文未使用其中任何内容

标签:开源, Kubernetes, AI Agent, Go, 云原生

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询