1. 从“ax”这个标题说起:一个被低估的运行时编排命题
第一次看到“ax”这个标题,很多人会一头雾水。它不像“Kubernetes 入门”那样直白,也不像“Agentic RAG 实战”那样自带场景。但把热搜词摊开来看,脉络就清楚了:ax、agentic、orchestration、runtime、Kubernetes这几个词放在一起,指向的是一个非常具体的工程命题——在 Kubernetes 之上,如何为 agentic 工作负载构建一套轻量、可编排、可观测的运行时层。
我先把结论摆在前面:ax 不是一个具体的开源项目名,而更像是一类“agent runtime orchestration”方案的代称。它要解决的问题是:当你的系统里不再只有无状态的 HTTP 服务,而是有一堆需要长时间运行、需要工具调用、需要状态保持、需要多步推理的 agent 时,传统的 Deployment + Service 模型就不够用了。你需要一层专门为 agent 设计的运行时抽象。
为什么这么说?因为热搜词里同时出现了agentic rag、karmada 正式毕业、agentic cloud、container runtime is not running、webview2 runtime、labview runtime engine这些看似不相关的词。它们共同指向一个事实:“runtime”这个词在 2024 到 2025 年已经从“语言运行时”扩展到了“智能体运行时”。以前我们说 runtime,指的是 JVM、CPython、Node.js 这种执行环境;现在说 runtime,更多是指 agent 的执行、调度、工具注入、状态管理这一整套基础设施。
所以这篇博文,我不打算把它写成某个项目的 README 翻译。我要做的是:把“ax”当作一个 agentic orchestration runtime 的设计命题,从架构选型、核心组件、Kubernetes 落地、常见故障排查四个维度,完整拆一遍。如果你正在做多 agent 系统、正在把 agent 往 K8s 上搬、或者正在被container runtime is not running这类报错折磨,这篇内容应该能帮你省下不少试错时间。
适合谁看?三类人:一是正在设计 agent 平台的后端工程师;二是需要把 agent 工作负载部署到 Kubernetes 的 DevOps;三是想理解 agentic orchestration 到底和传统微服务编排差在哪里的技术负责人。不需要你是 K8s 专家,但最好对 Pod、Deployment、CRD 这些概念有基本认知。
2. 为什么 agentic 工作负载需要独立的 runtime 层
2.1 传统微服务编排模型为什么不够用
先把问题说清楚。Kubernetes 最初是为无状态服务设计的,后来通过 StatefulSet 支持有状态服务,通过 Job/CronJob 支持批处理。但这三种模型都有一个共同假设:工作负载的生命周期是相对确定和短暂的。一个 HTTP 请求进来,处理完就结束;一个 Job 跑完就退出。
Agent 不一样。一个 agent 任务可能是这样的:接收用户问题 → 规划步骤 → 调用搜索工具 → 等待结果 → 调用代码执行工具 → 观察输出 → 决定是否继续 → 最终生成回答。这个过程可能持续几秒,也可能持续几分钟甚至几小时。更关键的是,agent 的执行路径是动态的,不是预先定义的。你没法在 YAML 里写清楚它下一步要调用哪个工具。
这就带来几个传统编排搞不定的问题。第一,状态保持。Agent 的中间推理结果、工具调用历史、上下文窗口,都需要在多次调用之间保持。你不能每次都重新开始。第二,工具注入。Agent 需要访问外部工具,这些工具的凭证、限流、超时策略,需要在运行时动态注入。第三,可观测性。传统服务的日志是线性的,agent 的执行是树状的,你需要看到每一步的决策依据。第四,资源隔离。不同 agent 可能跑不同的模型、不同的工具集,需要不同的资源配额。
我见过太多团队一开始想用普通 Deployment 硬扛,结果就是:Pod 里塞一个巨大的 Python 进程,所有 agent 逻辑都在里面,状态存在内存里,一重启全丢。这种方案在 demo 阶段能跑,一上生产就崩。
2.2 ax 类 runtime 的核心抽象应该长什么样
基于上面这些痛点,一个合格的 agentic runtime 至少需要四层抽象。我用一个表格来对比传统模型和 ax 类模型:
| 维度 | 传统 K8s 模型 | ax 类 agentic runtime |
|---|---|---|
| 执行单元 | Pod / Container | Agent Session |
| 生命周期 | 请求级 / 任务级 | 会话级,可暂停可恢复 |
| 状态管理 | 无状态或外部存储 | 内置 checkpoint + 外部持久化 |
| 工具调用 | 硬编码在镜像里 | 运行时动态注入 |
| 调度粒度 | 节点级 | 步骤级 |
| 可观测性 | 日志 + 指标 | 决策链 + 工具调用轨迹 |
这里最关键的是Agent Session这个概念。它不是一个 Pod,而是一个逻辑执行单元,可以跨多个 Pod 存在。当一个 agent 需要等待外部工具返回时,它的 session 可以被挂起,释放计算资源;当结果回来时,再恢复执行。这就是所谓的orchestration——不是简单地调度容器,而是调度 agent 的执行步骤。
2.3 和 Karmada、agentic cloud 的关系
热搜里出现了“karmada 正式毕业”和“agentic cloud 坚实底座”。这不是巧合。Karmada 是 Kubernetes 的多集群编排项目,它解决的是“一个集群不够用,怎么把工作负载分发到多个集群”的问题。而 agentic cloud 的核心诉求之一,就是让 agent 能够在多个集群、多个区域之间灵活调度。
为什么 agent 需要多集群?因为 agent 调用的工具可能分布在不同环境。比如一个 agent 需要访问内部数据库,另一个需要访问公网 API,还有一个需要跑在 GPU 集群上做推理。如果所有东西都塞在一个集群里,网络策略、安全边界、成本控制都会变得极其复杂。ax 类 runtime 如果设计得当,应该能够和 Karmada 这类多集群编排层配合,把 agent 的不同步骤调度到最合适的集群。
我个人的判断是:未来 agentic runtime 的竞争点,不在于单集群内的调度效率,而在于跨集群、跨环境的编排能力。谁能把 agent 的工具调用、模型推理、状态存储这三件事在不同基础设施之间无缝衔接,谁就能成为 agentic cloud 的真正底座。
3. 核心组件拆解:一个可落地的 ax runtime 架构
3.1 控制平面:Agent Orchestrator 的职责边界
控制平面是整个 runtime 的大脑。它的核心职责不是执行 agent,而是决定 agent 下一步该做什么,以及在哪里做。具体来说,它需要处理四件事:
第一,会话管理。每个 agent session 有一个唯一 ID,控制平面需要维护 session 到执行单元的映射。当 session 被创建时,分配执行资源;当 session 挂起时,释放资源但保留状态引用;当 session 恢复时,重新分配资源并加载状态。
第二,步骤调度。Agent 的每一步(比如“调用搜索工具”)都是一个调度单元。控制平面需要根据步骤的类型、资源需求、当前集群负载,决定把它调度到哪个执行器上。这和 K8s 的 Pod 调度类似,但粒度更细。
第三,工具注册与发现。所有可用的工具需要在控制平面注册,包括工具的名称、输入输出 schema、调用凭证、限流策略。Agent 在运行时通过控制平面查询可用工具,而不是硬编码在代码里。
第四,策略执行。比如最大执行步数、最大 token 消耗、工具调用频率限制。这些策略在控制平面统一执行,避免每个 agent 自己实现一套。
我用一段伪代码来说明控制平面的核心逻辑:
class AgentOrchestrator: def create_session(self, agent_spec, user_input): session_id = generate_id() state = initialize_state(agent_spec, user_input) self.state_store.save(session_id, state) return session_id def step(self, session_id): state = self.state_store.load(session_id) next_action = self.planner.plan(state) if next_action.type == "tool_call": executor = self.scheduler.pick_executor(next_action) result = executor.execute(next_action) state.append_observation(result) elif next_action.type == "final_answer": return next_action.content self.state_store.save(session_id, state) return self.step(session_id)这段代码看起来简单,但每一行背后都有工程决策。比如state_store用什么?planner是本地模型还是远程 API?scheduler的调度策略是什么?这些选择直接决定了 runtime 的性能和可靠性。
3.2 数据平面:执行器、沙箱与工具代理
数据平面是真正干活的地方。它由三类组件构成:
执行器(Executor):负责运行 agent 的单步逻辑。它可以是无状态的,因为所有状态都在 state store 里。执行器从控制平面接收任务,执行,返回结果。这种设计的好处是执行器可以水平扩展,也可以快速替换。
沙箱(Sandbox):当 agent 需要执行代码时,必须在沙箱里跑。沙箱需要提供文件系统隔离、网络隔离、资源限制。在 Kubernetes 里,这通常通过 gVisor、Kata Containers 或者简单的 seccomp 来实现。我实测下来,如果只是跑 Python 代码片段,用 gVisor 的 overhead 是可以接受的;但如果需要 GPU 推理,就得用更轻量的隔离方案。
工具代理(Tool Proxy):Agent 调用的外部工具,不应该直接暴露给执行器。中间需要一层代理,负责凭证注入、请求重试、结果缓存、审计日志。这层代理在 K8s 里通常以 Sidecar 或者独立的 Deployment 形式存在。
这里有个关键设计决策:执行器和工具代理之间是同步调用还是异步调用。同步调用实现简单,但会阻塞执行器;异步调用需要消息队列,但能更好地处理长耗时工具。我的建议是:默认异步,但对短耗时工具提供同步快捷路径。因为 agent 的很多工具调用(比如搜索、计算)是毫秒级的,走消息队列反而增加延迟。
3.3 状态层:Checkpoint、记忆与上下文窗口管理
状态层是最容易被低估的部分。很多人以为 agent 的状态就是对话历史,存个 Redis 就行了。实际上,agent 的状态至少包含四类数据:
- 执行状态:当前走到哪一步,下一步该做什么
- 上下文状态:当前上下文窗口里有哪些内容,token 数是多少
- 记忆状态:长期记忆里存了什么,如何检索
- 工具状态:哪些工具调用正在进行,哪些已完成
这四类数据的生命周期和访问模式完全不同。执行状态需要强一致,每次步骤切换都要更新;上下文状态需要频繁读写,但可以容忍最终一致;记忆状态是追加写的,读的时候需要向量检索;工具状态需要支持超时和取消。
我的做法是分层存储:执行状态放 etcd 或者 PostgreSQL,保证强一致;上下文状态放 Redis,设置合理的 TTL;记忆状态放向量数据库;工具状态放消息队列的消费位点。这样每层都能选最适合的存储,而不是用一个 Redis 硬扛所有。
上下文窗口管理是另一个坑。Agent 的上下文会随着步骤增加而膨胀,最终超过模型的 token 限制。你需要一套上下文压缩策略:比如保留最近 N 轮对话,对更早的内容做摘要,或者用向量检索只召回相关片段。这个策略不能硬编码,应该作为 agent spec 的一部分,允许不同 agent 用不同策略。
3.4 和 Kubernetes 的集成点:CRD、Operator 与调度器扩展
ax runtime 要跑在 Kubernetes 上,就必须和 K8s 的扩展机制结合。核心是三个集成点:
CRD 定义:把 Agent、AgentSession、Tool 这些概念定义成 Custom Resource。比如:
apiVersion: ax.io/v1alpha1 kind: AgentSession metadata: name: session-abc123 spec: agentRef: my-agent input: "帮我分析这份销售数据" maxSteps: 20 toolRefs: - sql-query - chart-generator status: phase: Running currentStep: 3 checkpointRef: s3://checkpoints/session-abc123/step-3Operator 实现:Operator 监听这些 CRD 的变化,驱动实际执行。比如当 AgentSession 被创建时,Operator 创建对应的执行器 Pod;当 session 挂起时,Operator 删除 Pod 但保留 checkpoint。
调度器扩展:如果 agent 步骤有特殊的调度需求(比如需要 GPU、需要特定工具代理),可以通过 K8s 的 Scheduler Framework 实现自定义调度插件。不过我的经验是,大多数场景下用 nodeSelector + affinity 就够了,没必要上自定义调度器,除非你的规模真的很大。
这里要特别提一下container runtime is not running这个热搜词。这是 K8s 部署中最常见的报错之一,通常是因为 containerd 或 CRI-O 没启动,或者 kubelet 配置的 runtime endpoint 不对。在 agentic runtime 场景下,这个问题更复杂,因为你可能需要多种 runtime:跑普通容器的 containerd、跑沙箱的 gVisor、跑 GPU 推理的 nvidia-container-runtime。你需要确保 kubelet 能正确识别这些 runtime class,否则 agent 的沙箱步骤会直接失败。
4. 实操落地:从零搭建一个最小可用的 ax runtime
4.1 环境准备与依赖检查
在开始之前,先把环境检查清单过一遍。我踩过的坑是:很多人直接上 Helm chart,结果底层 runtime 没配好,Pod 一直 Pending。
# 检查 Kubernetes 版本,建议 1.26 以上 kubectl version --short # 检查 container runtime 状态 systemctl status containerd crictl info # 检查节点资源 kubectl describe nodes | grep -A 5 "Allocated resources" # 检查是否有 GPU 节点(如果需要推理) kubectl get nodes -l accelerator=nvidia-gpu如果crictl info报错,说明 container runtime 没跑起来。常见原因是 containerd 的配置文件/etc/containerd/config.toml里SystemdCgroup没设成 true,或者 kubelet 的--container-runtime-endpoint指向了错误的 socket。这两个问题在container runtime is not running报错里占了八成以上。
4.2 部署控制平面:用 Helm 还是手写 YAML
控制平面的部署,我建议先用 Helm 快速起一个,再根据需求改。手写 YAML 适合学习,但不适合生产,因为组件之间的依赖关系太多。
控制平面至少需要这些组件:
| 组件 | 作用 | 推荐实现 |
|---|---|---|
| API Server | 接收 AgentSession CRD 请求 | 基于 kube-apiserver 的 aggregated API |
| Orchestrator | 步骤调度与状态机 | 自研 Go 服务 |
| State Store | 执行状态持久化 | PostgreSQL + Redis |
| Tool Registry | 工具注册与发现 | etcd + 自研服务 |
| Checkpoint Store | 状态快照 | S3 兼容对象存储 |
Helm values 里最关键的是资源配额。Orchestrator 是 CPU 密集型(要跑 planner),State Store 是 IO 密集型。我一般给 Orchestrator 配 2C4G,State Store 配 4C8G,具体看 agent 并发数。
4.3 编写第一个 AgentSession CRD
从最简单的开始:一个只调用一个工具的 agent。CRD 定义如下:
apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: name: agentsessions.ax.io spec: group: ax.io versions: - name: v1alpha1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: agentRef: type: string input: type: string maxSteps: type: integer default: 10 toolRefs: type: array items: type: string scope: Namespaced names: plural: agentsessions singular: agentsession kind: AgentSession创建 session:
kubectl apply -f - <<EOF apiVersion: ax.io/v1alpha1 kind: AgentSession metadata: name: demo-session spec: agentRef: simple-agent input: "计算 123 乘以 456" maxSteps: 5 toolRefs: - calculator EOF然后观察 Operator 的日志,看它是否创建了执行器 Pod,是否调用了 calculator 工具,是否写入了 checkpoint。
4.4 工具代理的配置与凭证注入
工具代理是安全边界。绝对不要把工具凭证放在 agent 的镜像里。正确做法是用 K8s Secret 挂载到工具代理,代理再以受控方式调用外部服务。
apiVersion: v1 kind: Secret metadata: name: tool-credentials type: Opaque stringData: search-api-key: "sk-xxxx" database-url: "postgres://..." --- apiVersion: apps/v1 kind: Deployment metadata: name: tool-proxy spec: replicas: 2 selector: matchLabels: app: tool-proxy template: metadata: labels: app: tool-proxy spec: containers: - name: proxy image: ax/tool-proxy:latest envFrom: - secretRef: name: tool-credentials ports: - containerPort: 8080工具代理需要实现的核心接口是:接收工具调用请求 → 校验权限 → 注入凭证 → 调用外部服务 → 返回结果。这中间还要做限流和重试。我一般用 Envoy 做 sidecar 来实现限流,用简单的 Go 服务做凭证注入。
4.5 状态持久化与恢复演练
状态持久化最容易出问题的地方是恢复时的幂等性。假设一个 agent 执行到第 5 步时崩溃了,从第 4 步的 checkpoint 恢复,那么第 5 步的工具调用可能会被执行两次。如果这个工具是“扣款”,那就出大事了。
解决方案是给每个步骤分配唯一 ID,工具代理记录已执行的步骤 ID。恢复时,如果步骤 ID 已经执行过,直接返回缓存结果,不重复执行。
def execute_tool_call(session_id, step_id, tool_name, params): cache_key = f"{session_id}:{step_id}" cached = redis.get(cache_key) if cached: return json.loads(cached) result = tool_proxy.call(tool_name, params) redis.setex(cache_key, 3600, json.dumps(result)) return result这个简单的缓存机制,能解决 90% 的重复执行问题。剩下的 10% 是工具本身不支持幂等,那就需要在工具层面做补偿。
5. 常见问题与排查技巧实录
5.1 container runtime 相关报错速查
这是部署阶段最高频的问题。我把常见的报错和原因整理成表:
| 报错信息 | 根本原因 | 解决方法 |
|---|---|---|
container runtime is not running | containerd/CRI-O 未启动 | systemctl restart containerd,检查 socket 路径 |
failed to pull image | 镜像仓库凭证或网络问题 | 检查 imagePullSecrets,测试节点到仓库的连通性 |
no runtime for pod | RuntimeClass 未配置 | 创建对应的 RuntimeClass 资源 |
OOMKilled | 执行器内存不足 | 调大 resources.limits.memory,或优化上下文压缩 |
context deadline exceeded | 工具调用超时 | 检查工具代理的 timeout 配置,增加重试 |
特别说一下no lm runtime found for model format 'gguf'这个报错。如果你在 agent 里集成了本地模型推理,用的是 llama.cpp 这类引擎,需要确保推理服务已经加载了正确的模型格式。GGUF 是 llama.cpp 的格式,如果你用 vLLM,就得用 safetensors。模型格式和推理引擎必须匹配,这个在 agent 的模型配置里要写清楚。
5.2 agent 步骤卡死与超时排查
Agent 卡死通常有三种原因:工具调用没返回、planner 陷入循环、状态存储不可用。
排查顺序是:先看 Orchestrator 日志,确认当前卡在哪一步;再看工具代理日志,确认请求是否发出;最后看 state store 的延迟指标。我遇到过一次,agent 卡在第 7 步,查了半天发现是工具代理在等一个外部 API,而那个 API 的 DNS 解析在集群里出了问题。集群内的 DNS 问题会伪装成工具超时,这个坑要记住。
5.3 上下文膨胀导致的内存问题
上下文膨胀是 agent 特有的问题。一个跑了 50 步的 agent,上下文可能有几十万 token。如果全部放在内存里,执行器很容易 OOM。
我的做法是上下文分页:只把最近 N 轮放在内存里,更早的放在对象存储,需要时按需加载。同时设置硬性上限,超过就触发摘要压缩。摘要压缩用一个小模型(比如 7B 级别)就够了,不需要用大模型,因为摘要的质量要求没那么高。
5.4 多集群调度下的状态一致性
如果你用 Karmada 做多集群调度,状态一致性是个大问题。Agent 的步骤可能在不同集群执行,但状态必须统一。我的建议是状态存储集中化:所有集群都访问同一个 PostgreSQL 和 Redis,不要在每个集群本地存状态。这样虽然增加了网络延迟,但避免了状态分裂。
如果延迟不可接受,可以用状态同步方案:每个集群本地存一份,异步同步到中心。但这会引入冲突解决问题,复杂度高很多。除非你的 agent 步骤对延迟极其敏感,否则不建议。
6. 一些实操心得与后续扩展方向
先说几个我踩过的坑。第一,不要过早优化调度器。我一开始花了两周写自定义调度插件,结果发现用 nodeSelector 就能解决 95% 的场景。第二,工具代理的重试策略要区分工具类型。查询类工具可以随便重试,写入类工具必须幂等才能重试。第三,checkpoint 的频率要权衡。每步都存,IO 压力大;存得太少,恢复时丢的步骤多。我的经验是每 3 到 5 步存一次,或者遇到工具调用前必须存。
关于后续扩展,我觉得有几个方向值得关注。一是agent 之间的协作,现在大多数 runtime 只支持单 agent,多 agent 的通信和协调还没有标准方案。二是成本感知调度,不同模型、不同工具的调用成本差异很大,runtime 应该能根据预算动态选择。三是和 agentic RAG 的深度集成,检索不应该是一个外部工具,而应该是 runtime 的内置能力。
最后分享一个小技巧:在 agent spec 里加一个dryRun模式。这个模式下,所有工具调用都返回 mock 数据,不真正执行。这在调试 agent 逻辑时非常有用,能让你快速验证 planner 的决策路径,而不用等真实工具返回。我现在的每个 agent 都会先跑 dryRun,确认逻辑没问题再上真实工具。
这个领域变化很快,Karmada 毕业、agentic cloud 兴起,都说明基础设施层正在为 agent 做专门优化。ax 这类 runtime 的价值,不在于它现在有多完善,而在于它指出了一个方向:agent 需要的不只是模型,还需要一整套运行时。谁先把这套运行时做稳,谁就能在下一波应用爆发里占到位置。