1. 从“ax”这个标题说起一个被低估的调度关键词第一次看到“ax”这个标题很多人会以为是某个库的缩写或者某个命令行工具的别名。但把热搜词摊开来看——ax调度、agent、kubernetes、workspace、gateway——这几个词凑在一起指向的其实是一个非常具体的场景在 Kubernetes 之上跑 Agent 工作负载时如何做资源调度与网关接入。ax 在这里我更倾向于把它理解成一个“调度轴”或者“执行轴”的代称它不是一个具体的开源项目名而是一类问题的统称。我最早接触这类需求是在做一个多 Agent 协作平台的时候。当时集群里跑着几十个 Agent 实例每个 Agent 有自己的 workspace、有自己的工具调用链、还要通过 gateway 对外暴露接口。跑着跑着就发现Pod 调度不均匀、workspace 挂载冲突、gateway 502 报错、Agent 执行到一半被终止。这些问题单独看都是小问题但叠在一起就是一套完整的“Agent on Kubernetes”工程难题。所以这篇内容我想聊的不是某个具体工具怎么装而是把 ax 调度、Agent 运行时、Kubernetes 编排、workspace 隔离、gateway 接入这五件事串起来讲清楚它们之间的关系以及我在实际项目里踩过的坑和总结出来的做法。适合正在做 Agent 平台、AI 工作流编排、或者准备把 Agent 部署到 K8s 上的同学参考。不管你是刚接触 Kubernetes 的新手还是已经能写 Operator 的老手应该都能从里面找到一些能直接抄作业的东西。2. 整体设计思路为什么 Agent 负载不能照搬普通微服务2.1 Agent 负载和普通服务的本质差异普通微服务是无状态的请求来了处理完就走副本之间可以随意替换。但 Agent 不一样。一个 Agent 实例往往带着自己的会话上下文、工具状态、临时文件、记忆数据。你把它杀掉再拉起来之前跑到一半的任务就丢了。这就是为什么热搜里会出现“agent execution terminated due to error”和“setting up workspace: loading packages...卡住”这类问题——本质上都是状态管理没做好。我在设计的时候把 Agent 负载分成三类来对待无状态 Agent每次调用都是独立的比如单纯的文本改写、分类。这类可以像普通 Deployment 一样跑随便扩缩容。有状态 Agent带会话记忆、带 workspace 文件。这类必须用 StatefulSet 或者带 PVC 的 DeploymentPod 重建后要能挂回原来的存储。长任务 Agent一次执行可能几分钟到几十分钟比如代码生成、多轮工具调用。这类要考虑超时、心跳、断点续跑。把这三类混在一起调度就是灾难的开始。我见过一个团队把所有 Agent 都塞进一个 Deployment结果长任务把副本占满短请求全部排队最后 gateway 直接 502。2.2 ax 调度层的定位ax 调度在我这套体系里承担的是“决策层”的角色。它不直接干活而是决定哪个 Agent 该跑到哪个节点上、用多少资源、什么时候该迁移。具体来说它要解决三个问题第一是资源匹配。Agent 对资源的需求差异很大有的吃 CPU有的吃内存有的需要 GPU。Kubernetes 默认的调度器只看 request 和 limit但 Agent 的实际消耗是波动的。我在 ax 调度层加了一层“历史消耗画像”根据过去一段时间的实际用量来修正调度权重。第二是亲和性控制。同一个用户的多个 Agent 最好调度到同一节点减少跨节点网络开销但同一个 Agent 的多个副本又要打散避免单点故障。这种“既要聚又要散”的需求靠默认调度器的 podAffinity 写起来很痛苦ax 层做了一层封装。第三是故障域隔离。Agent 跑飞了不能影响别的 Agent。我在 ax 调度里给每个 Agent 打了“爆炸半径”标签同一爆炸半径的 Agent 不会调度到同一节点。2.3 为什么选 Kubernetes 作为底座有人会问跑 Agent 为什么非得用 Kubernetes用 Docker Compose 不行吗。小规模确实可以但一旦超过二十个 Agent 实例你就会遇到手动分配端口、手动管理存储、手动做健康检查、手动处理重启。这些事 Kubernetes 都帮你做了而且做得比你手写脚本稳。更重要的是 Kubernetes 的Device Plugin 机制。热搜里出现了“kubernetes device plugin”这个不是偶然。Agent 如果要调用 GPU、NPU 或者特殊硬件Device Plugin 是标准接入方式。你不需要改调度器核心代码只要实现一个 Device Plugin就能让 K8s 认识你的硬件资源。这个扩展性是我选 K8s 的核心原因。3. 核心细节解析workspace、gateway、Agent 运行时的三角关系3.1 workspace 隔离的三种方案与选型workspace 是 Agent 的工作目录里面放着代码、临时文件、缓存、日志。热搜里“claudes workspace requires the virtual machine platform on windows”和“couldnt complete the workspace policy acknowledgment”都指向 workspace 的初始化问题。在 K8s 环境下workspace 的隔离我试过三种方案方案隔离级别启动速度适用场景坑点emptyDirPod 级秒级临时任务Pod 重建数据丢失PVC 挂载卷级秒级有状态 Agent多副本读写冲突独立容器 workspace容器级秒级强隔离需求网络配置复杂我最后选的是PVC 子路径的组合。每个 Agent 分配一个独立的 PVC然后在 PVC 里按 Agent ID 划分子目录。这样既保证了数据持久化又避免了多副本同时写同一目录。具体做法是在 StatefulSet 的 volumeClaimTemplates 里定义模板K8s 会自动为每个副本创建独立的 PVC。注意PVC 的 accessModes 一定要选对。ReadWriteOnce 只能被一个节点挂载如果你的 Agent 副本跨节点必须用 ReadWriteMany而这又依赖底层存储支持 NFS 或 CephFS。我在这上面浪费过整整两天。3.2 gateway 接入的常见故障与排查gateway 是 Agent 对外的入口也是故障最集中的地方。热搜里“unexpected status 502 bad gateway: cc switch local proxy failed”和“502 bad gateway: unknown error, url: http://127.0.0.1:15721/v1/responses”这两个报错我太熟悉了。502 的本质是 gateway 把请求转发给上游但上游没给有效响应。在 Agent 场景下常见原因有四个Agent 还没启动完workspace 加载慢gateway 已经开始转发。解决办法是配 readinessProbe让 Pod 真正就绪后再加入 Endpoints。Agent 执行超时长任务超过 gateway 的 timeout 设置。默认 60 秒对 Agent 来说太短我一般调到 300 秒以上。端口配置错误Agent 监听 8080gateway 转发到 8000。这种低级错误在 YAML 多了之后特别容易犯。本地代理冲突热搜里提到的“cc switch local proxy failed”就是本地代理和 gateway 抢端口。开发环境尤其常见。我的排查顺序是先看 gateway 日志确认转发目标再kubectl exec进 Agent Pod 用 curl 测本地端口最后检查 Service 的 selector 是否匹配 Pod 标签。三步下来基本能定位。3.3 Agent 运行时的生命周期管理Agent 从启动到销毁中间有几个关键节点容易出问题初始化阶段加载依赖、拉取模型、恢复记忆。热搜里“setting up workspace: loading packages...卡住”就是卡在这一步。我的做法是给初始化加超时超过 120 秒就重启同时把初始化日志单独输出到一个 sidecar 容器方便排查。执行阶段Agent 开始处理任务。这时候要监控 CPU、内存、以及“任务进度”。K8s 默认的 livenessProbe 只能探活探不了进度。我在 Agent 里暴露一个/progress接口返回当前任务完成百分比如果长时间不变化就判定为卡死。销毁阶段Agent 被终止时要保证 workspace 数据落盘、记忆写回。我在 preStop hook 里加了一个优雅退出脚本给 Agent 30 秒时间保存状态。4. 实操过程从零搭一套 Agent 调度环境4.1 环境准备与基础组件安装假设你已经有一个 K8s 集群版本 1.24 以上。第一步是装 gateway。我用的是 Spring Cloud Gateway因为它的路由配置灵活支持动态刷新。热搜里“springcloud gateway”和“gateway配置路由转发固定链接地址”都是这个方向。安装步骤# 添加 helm repo helm repo add spring-cloud-gateway https://example.com/charts helm repo update # 安装 gateway helm install agent-gateway spring-cloud-gateway/gateway \ --namespace agent-system \ --create-namespace \ --set replicaCount2 \ --set resources.requests.cpu500m \ --set resources.requests.memory512Mi装完之后验证kubectl get pods -n agent-system kubectl get svc -n agent-systemgateway 的配置我放在 ConfigMap 里路由规则大概长这样spring: cloud: gateway: routes: - id: agent-route uri: http://agent-service.agent-system.svc.cluster.local:8080 predicates: - Path/api/agent/** filters: - StripPrefix2 - name: Retry args: retries: 3 statuses: BAD_GATEWAY提示Retry 过滤器一定要配Agent 冷启动慢的时候重试能救回不少请求。但重试次数别超过 3 次否则会放大后端压力。4.2 Agent 镜像构建与 workspace 初始化Agent 镜像我基于 python:3.11-slim 构建关键是把 workspace 初始化脚本放进去。Dockerfile 大概这样FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY agent/ ./agent/ COPY scripts/init_workspace.sh /usr/local/bin/ RUN chmod x /usr/local/bin/init_workspace.sh EXPOSE 8080 ENTRYPOINT [/usr/local/bin/init_workspace.sh]init_workspace.sh 做三件事检查 workspace 目录是否存在、从 PVC 恢复数据、启动 Agent 主进程。#!/bin/bash set -e WORKSPACE_DIR/workspace/${AGENT_ID} if [ ! -d $WORKSPACE_DIR ]; then mkdir -p $WORKSPACE_DIR echo workspace created: $WORKSPACE_DIR fi # 恢复记忆数据 if [ -f $WORKSPACE_DIR/memory.json ]; then echo memory restored fi exec python -m agent.main这个脚本看着简单但解决了我早期遇到的大部分“workspace 加载卡住”问题。核心思路是把初始化逻辑显式化不要藏在 Agent 代码里这样出问题一眼就能看到卡在哪一步。4.3 ax 调度策略的配置与参数计算ax 调度我通过自定义调度器实现核心是给 Pod 打上调度权重标签。参数计算这块我举个例子假设节点总内存 16GB已分配 12GB剩余 4GB。Agent A 请求 2GBAgent B 请求 3GB。默认调度器会先调度 A因为 A 能放下。但 B 如果一直等不到节点就会 Pending。我的 ax 调度策略是按请求大小降序调度先放 B 再放 A这样两个都能跑起来。具体配置apiVersion: v1 kind: Pod metadata: labels: ax-schedule-priority: high ax-explosion-radius: r1 spec: containers: - name: agent resources: requests: memory: 2Gi cpu: 1000m limits: memory: 4Gi cpu: 2000m调度器读取ax-schedule-priority标签high 的先调度low 的后调度。ax-explosion-radius用来做反亲和同一半径的 Pod 不会调度到同一节点。注意自定义调度器要注册到 K8s 的 scheduler framework 里或者用 scheduler extender 的方式挂载。前者性能好但侵入性强后者灵活但多一次网络调用。我选的是 extender 方式因为改动小升级 K8s 版本时不容易挂。4.4 gateway 路由转发固定链接地址的配置热搜里“gateway配置路由转发固定链接地址”是个很具体的需求。比如你想让/agent/12345/*固定转发到某个 Agent 实例而不是负载均衡。这在调试单个 Agent 时特别有用。Spring Cloud Gateway 支持这种配置spring: cloud: gateway: routes: - id: agent-fixed-route uri: http://agent-12345.agent-system.svc.cluster.local:8080 predicates: - Path/agent/12345/** filters: - StripPrefix2这样访问/agent/12345/chat就会固定打到 agent-12345 这个 Service。生产环境一般不这么用但排查问题时非常方便。5. 常见问题与排查技巧实录5.1 502 报错速查表报错信息可能原因排查命令解决方式502 bad gateway: cc switch local proxy failed本地代理端口冲突netstat -ano | findstr 15721换端口或关代理502 unknown error, url: http://127.0.0.1:15721Agent 未就绪kubectl logs pod加 readinessProbenet::err_connection_timed_out网络策略拦截kubectl get networkpolicy放行 gateway 到 Agent 的流量agent execution terminated due to errorAgent 内部异常kubectl describe pod pod看 Events 和退出码5.2 workspace 加载卡住的排查思路“setting up workspace: loading packages...卡住”这个问题我遇到过三次每次原因都不一样第一次是 PVC 挂载慢底层存储是 NFS网络抖动导致挂载超时。解决办法是给 PVC 加volumeBindingMode: WaitForFirstConsumer让 Pod 调度后再绑定卷。第二次是 pip 安装依赖时访问外部源超时。解决办法是在镜像构建阶段就把依赖装好运行时不再装。第三次是 Agent 代码里有个死循环在加载配置文件时卡住了。解决办法是加超时和日志把加载过程拆成多个步骤每步打时间戳。5.3 Agent 记忆丢失的预防Agent 记忆丢失是最难排查的问题因为往往过了很久才发现。我的预防措施有三条定期快照每 5 分钟把 memory.json 备份到独立 PVC。双写机制记忆同时写本地和远程存储本地丢了还能从远程恢复。版本标记每次记忆更新带上版本号恢复时选最新版本。热搜里“a-memguard: a proactive defense framework for llm-based agent memory”提到的就是这个方向。虽然我不建议直接上这么重的框架但思路是对的——记忆要有保护机制。5.4 Kubernetes 未授权访问漏洞的防范热搜里出现了“kubernetes 未授权访问漏洞”这个必须重视。Agent 平台往往需要访问 K8s API 来创建 Pod、查状态如果 RBAC 配得松风险很大。我的做法是每个 Agent 用独立的 ServiceAccount只给最小权限。禁用默认 ServiceAccount 的 automountServiceAccountToken。API Server 开启审计日志记录所有 Agent 的 API 调用。用 NetworkPolicy 限制 Agent 只能访问必要的服务。apiVersion: v1 kind: ServiceAccount metadata: name: agent-sa namespace: agent-system automountServiceAccountToken: false这四步做完即使 Agent 被攻破能造成的破坏也有限。6. Agent 开发与部署的进阶经验6.1 Agent 框架选型的几个考量热搜里“agent框架”、“agent开发学习路线”、“skill和agent的区别”都是高频问题。我个人的经验是选框架看三点第一看状态管理。框架有没有内置的会话、记忆、workspace 管理。没有的话你得自己造轮子工作量不小。第二看工具调用。Agent 要调外部工具框架的工具注册、参数校验、错误处理做得好不好直接决定开发效率。第三看部署友好度。能不能打成容器、能不能水平扩展、能不能接 K8s。有些框架设计时只考虑单机上 K8s 要改很多代码。至于 skill 和 agent 的区别我的理解是skill 是能力单元agent 是决策主体。一个 agent 可以调用多个 skill但 skill 本身不做决策。热搜里“harness和agent区别”也是类似的关系harness 是执行框架agent 是执行者。6.2 Agent 部署测试软件的搭建“agent 部署 测试软件”这个需求我建议用一套轻量方案本地用 kind 或 minikube 起一个 K8s用 skaffold 做热更新用 k6 做压力测试。# 用 kind 起集群 kind create cluster --name agent-test # 用 skaffold 部署 skaffold dev --port-forward # 用 k6 压测 k6 run --vus 10 --duration 30s loadtest.js这套组合的好处是启动快、资源占用小、和线上环境基本一致。我本地开发基本都用这个流程。6.3 Agent 面试题背后的知识体系热搜里“agent 面试题”说明这个方向已经开始卷了。我面过不少人发现大家普遍对调度和隔离这块理解不深。常见问题比如“Agent 副本如何做状态同步”、“gateway 超时怎么调”、“workspace 冲突怎么解”能答好的人不多。我的建议是准备面试时不要只背概念要动手搭一遍。把 Agent 部署到 K8s 上故意制造 502、故意让 workspace 冲突、故意让 Pod 被驱逐然后看日志、查文档、解决问题。这个过程走一遍比看十篇文章都管用。7. 我在实际项目中的几点体会这套 Agent 调度体系我前后迭代了大概半年从最初的单机 Docker 到现在的 K8s 集群中间踩的坑能写一本书。最大的体会是Agent 平台的复杂度不在 Agent 本身而在它和基础设施的交互。workspace、gateway、调度、存储、网络每一个环节出问题都会表现为“Agent 跑不起来”但根因可能在完全不同的地方。另一个体会是日志和监控要提前做。我早期为了赶进度Agent 日志直接打到 stdout没有结构化出问题只能kubectl logs一行行看。后来改成 JSON 格式接入 Loki排查效率至少提升三倍。监控也是CPU、内存、任务进度、gateway 延迟这四个指标必须实时看。最后分享一个小技巧给每个 Agent 加一个/healthz接口返回的不只是 ok还包括当前任务队列长度、workspace 使用率、最近一次错误信息。这样 gateway 和调度器都能拿到更丰富的信息做决策更准。这个接口实现起来不到五十行代码但收益很大。