Agent运行时编排实战:基于Kubernetes的会话、工具与推理进程落地 1. 从“ax”这个标题说起一个被低估的运行时编排命题第一次看到“ax”这个标题很多人会一头雾水——两个字母既不像产品名也不像技术栈缩写。但把热搜词摊开来看线索就清楚了agentic、orchestration、runtime、Kubernetes这几个词反复出现再加上“karmada正式毕业”“agentic cloud坚实底座”这类行业动态基本可以判断这里讨论的“ax”不是某个具体软件的名字而是指向一类面向智能体Agent的运行时编排层——你可以把它理解成“Agent 时代的调度内核”。我之所以敢这么判断是因为过去一年多我在几个内部项目里反复折腾过类似的东西把一堆各自为战的 Agent 服务、工具调用、模型推理进程塞进一个统一的运行时里让它们能像 Kubernetes 调度容器一样被调度、被观测、被扩缩容。这个过程踩的坑比当年从物理机迁移到容器还多。原因很简单——容器是“无状态、可重启、生命周期短”的而 Agent 是有上下文、有会话、有工具依赖、有推理成本的它的“状态”比传统微服务复杂一个数量级。所以这篇博文我想把“ax”当作一个运行时编排命题来拆解它要解决什么问题核心抽象是什么怎么落地到 Kubernetes 这类基础设施上以及在实际操作中哪些地方最容易翻车。适合正在做 Agent 平台、AI 基础设施、或者单纯想把多个模型服务编排起来的同学参考。哪怕你只是刚接触 Kubernetes我也会尽量用生活化的类比把关键概念讲清楚。先给一个最直白的定义ax 在这里代表的是一层“Agent 运行时编排”它向下对接 Kubernetes 等资源调度底座向上暴露 Agent 生命周期管理、工具注册、会话路由、推理进程托管等能力。它不是模型本身也不是某个框架而是把“Agent 怎么跑起来、怎么被调度、怎么被观测”这件事工程化的那一层。理解了这一点后面所有的技术选型和踩坑才有落脚点。2. 为什么 Agent 需要独立的运行时编排层2.1 传统容器编排为什么不够用Kubernetes 解决的是“进程怎么被调度到机器上、怎么保持期望状态”的问题。它的核心抽象是 Pod、Deployment、Service假设的是无状态、可水平复制、随时可杀的工作负载。这套模型对 Web 服务、批处理任务非常合适但放到 Agent 场景就会露出短板。Agent 的典型特征是一次会话可能持续几分钟甚至几小时中间要调用多个工具、访问外部 API、维护对话历史推理进程比如 llama-server、vLLM 这类启动慢、显存占用大、不能随便重启不同 Agent 之间还有依赖关系A 的输出是 B 的输入。这些特性决定了它不能简单地当成一个 Deployment 来对待。我举个实际例子。早期我们直接把一个 Agent 服务打包成 Deployment副本数设成 3。结果发现用户会话被随机打到不同副本上下文对不上某个副本因为调用外部工具超时被 OOM kill整个会话直接断掉推理进程冷启动要 40 多秒用户等得直接关页面。这就是典型的“用容器编排的思维套 Agent必然水土不服”。2.2 Agent 运行时的四个核心抽象踩了这些坑之后我逐渐总结出 Agent 运行时必须提供的四个核心抽象这也是判断一个“ax”类系统是否合格的标准会话Session比 Pod 更粗的调度单位绑定用户上下文保证同一会话路由到同一运行时实例。它解决的是“状态一致性”问题。工具ToolAgent 可调用的外部能力需要注册、发现、鉴权、限流。它解决的是“能力复用”问题。推理进程Inference Runtime模型加载、显存管理、请求排队。它解决的是“昂贵资源复用”问题。编排策略Orchestration Policy决定哪个 Agent 在什么时候、用什么资源、按什么顺序执行。它解决的是“多 Agent 协作”问题。把这四个抽象映射到 Kubernetes 上就形成了“ax”这类系统的典型架构Kubernetes 负责底层资源池化和故障自愈运行时层负责会话粘性、工具治理和推理托管。两者是互补关系不是替代关系。2.3 从 Karmada 毕业看编排层的演进方向热搜里提到“karmada正式毕业”这个信号值得单独说一句。Karmada 解决的是多集群编排问题——把工作负载分发到多个 Kubernetes 集群并保持策略一致。它毕业意味着多集群编排从“实验特性”走向“生产可用”。这对 Agent 运行时意味着什么意味着未来的 Agent 编排很可能不是单集群的而是跨集群、跨地域的。一个推理进程可能跑在 GPU 集群工具服务跑在通用集群会话状态存在边缘节点。运行时层必须能感知这种分布并做出合理的调度决策。这也是为什么“ax”这类系统不能只盯着单机或单集群必须从一开始就把多集群编排纳入设计。3. 核心细节拆解会话、工具与推理进程怎么落地3.1 会话粘性让同一个用户始终落到同一个实例会话粘性是 Agent 运行时最基础也最容易做错的一环。Kubernetes 的 Service 默认是轮询负载均衡同一个用户的请求会被打到不同 Pod上下文自然就丢了。解决办法有几种我按推荐程度排序第一种是基于会话 ID 的一致性哈希。在 Service 前面加一层网关比如 Envoy、Nginx根据请求头里的 session-id 做一致性哈希保证同一会话落到同一后端。优点是改动小缺点是后端扩缩容时会话会重新分布需要配合优雅下线。第二种是会话状态外置。把对话历史、工具调用记录存到 Redis 或数据库运行时实例变成无状态的任何实例都能处理任何会话。这是最干净的方案但引入了外部依赖且状态读写有延迟。第三种是会话亲和 状态外置混合。热数据放本地内存加速冷数据落外部存储实例重启时从外部恢复。这是我在生产环境最终采用的方案兼顾了性能和可靠性。注意会话粘性不是“绑定 IP”那么简单。用户可能切换网络IP 会变移动端可能频繁重连。一定要用业务层的 session-id而不是网络层的标识。3.2 工具注册与发现别让 Agent 硬编码工具地址Agent 要调用工具最原始的做法是在代码里写死工具地址。这在工具有限时能跑但一旦工具数量上到几十个、还要动态上下线就会变成灾难。正确的做法是引入工具注册中心。工具注册中心的核心职责有三个注册工具启动时上报自己的能力和地址、发现Agent 按能力查询可用工具、健康检查自动摘除不可用工具。实现上可以基于 Kubernetes 的 Service ConfigMap也可以引入独立的注册中心如 Consul、Nacos。我踩过的一个坑是工具注册后没有做版本管理结果某个工具升级接口不兼容所有依赖它的 Agent 全部报错。后来我们在注册信息里强制加了apiVersion字段Agent 调用前先校验版本不匹配就降级或报错问题才收敛。方案优点缺点适用场景硬编码地址简单直接无法动态变更工具少于 5 个的原型K8s Service ConfigMap复用现有设施更新有延迟中小规模、同集群独立注册中心功能完整、跨集群运维成本高大规模、多集群3.3 推理进程托管显存是最贵的资源推理进程llama-server、vLLM、TGI 等是 Agent 运行时里最“重”的部分。一个 7B 模型加载要占十几 GB 显存启动要几十秒而且不能像普通进程那样随便重启。托管推理进程的核心目标是最大化显存利用率同时保证请求不丢。我的做法是把推理进程单独抽成一层用 Kubernetes 的 StatefulSet 管理每个 Pod 绑定一块 GPU。请求进来先排队由调度器决定发给哪个推理实例。关键参数有三个maxBatchSize批处理大小影响吞吐和延迟、gpuMemoryUtilization显存利用率一般设 0.9 留余量、maxModelLen最大上下文长度直接影响显存占用。这里有个容易忽略的点显存碎片。如果频繁加载卸载不同模型显存会产生碎片最终导致明明有空间却加载不了新模型。解决办法是尽量固定模型组合或者用支持 PagedAttention 的推理引擎如 vLLM它能显著减少碎片。3.4 编排策略从串行到 DAG最简单的 Agent 编排是串行A 执行完执行 BB 执行完执行 C。但真实场景往往是 DAG有向无环图A 的输出同时给 B 和 CB 和 C 都完成后触发 D。运行时层必须支持这种依赖表达。实现 DAG 编排有两种思路一种是把依赖关系写进 Agent 定义里由运行时解析执行另一种是引入工作流引擎如 Argo Workflows、Temporal把 Agent 当成工作流中的一个节点。前者轻量但功能有限后者强大但引入额外复杂度。我的建议是Agent 数量少于 20 个、依赖关系简单时用前者超过这个规模就上工作流引擎否则自己实现的调度逻辑迟早会变成一团乱麻。4. 实操过程在 Kubernetes 上搭一套最小可用的 Agent 运行时4.1 环境准备与前置检查动手之前先把环境确认清楚。我用的是一套三节点的 Kubernetes 集群v1.26.0一个 master、两个 workerworker 上各挂一块 GPU。如果你没有 GPU用 CPU 跑小模型也能验证流程只是推理会慢很多。前置检查清单如下Kubernetes 版本不低于 1.24确保PodScheduler和RuntimeClass特性可用。容器运行时正常。如果看到container runtime is not running这类报错先检查 containerd 或 CRI-O 服务状态别急着往下走。GPU 节点装好驱动和 device pluginkubectl describe node能看到nvidia.com/gpu资源。准备好镜像仓库推理镜像动辄几个 GB本地构建推送很慢。提示[preflight] running pre-flight checks这类输出是 kubeadm 初始化时的正常日志看到它说明流程在走不用慌。真正要关注的是后面有没有[ERROR]级别的输出。4.2 部署会话网关会话网关是整个运行时的入口负责解析 session-id 并做一致性哈希。我用 Envoy 实现核心配置片段如下static_resources: listeners: - name: agent_gateway address: socket_address: { address: 0.0.0.0, port_value: 8080 } filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: type: type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: agent domains: [*] routes: - match: { prefix: / } route: cluster: agent_runtime hash_policy: - header: header_name: x-session-id这段配置的关键是hash_policy它让 Envoy 根据x-session-id请求头做一致性哈希。客户端每次请求都带上同一个 session-id就能保证落到同一个后端实例。4.3 部署推理进程 StatefulSet推理进程用 StatefulSet 部署每个 Pod 绑定一块 GPU。核心配置如下apiVersion: apps/v1 kind: StatefulSet metadata: name: inference-runtime spec: serviceName: inference-runtime replicas: 2 selector: matchLabels: { app: inference-runtime } template: metadata: labels: { app: inference-runtime } spec: containers: - name: llama-server image: ghcr.io/ggerganov/llama.cpp:server-cuda args: - --model - /models/qwen2.5-7b-instruct-q4_k_m.gguf - --host - 0.0.0.0 - --port - 8080 - --n-gpu-layers - 99 - --ctx-size - 8192 - --parallel - 4 resources: limits: nvidia.com/gpu: 1 volumeMounts: - name: models mountPath: /models volumeClaimTemplates: - metadata: { name: models } spec: accessModes: [ReadOnlyMany] resources: requests: storage: 50Gi参数说明--n-gpu-layers 99表示把所有层都放到 GPU 上--ctx-size 8192是上下文长度越大越吃显存--parallel 4表示同时处理 4 个请求配合批处理能提升吞吐。实测下来7B 的 Q4 量化模型在 16GB 显存上跑 8192 上下文显存占用约 13GB留了 3GB 余量比较稳。注意如果你看到no lm runtime found for model format gguf这类报错说明推理引擎版本和模型格式不匹配。gguf 格式需要较新版本的 llama.cpp老版本不认。升级镜像即可。4.4 工具服务注册与健康检查工具服务用普通 Deployment 部署启动时向注册中心上报。我用一个简单的 ConfigMap 加 sidecar 实现sidecar 定期上报健康状态。核心逻辑是工具启动后sidecar 每 10 秒向注册中心发一次心跳注册中心超过 30 秒没收到心跳就摘除该工具。这里有个细节摘除工具时要通知正在使用它的 Agent。否则 Agent 还在往一个已经下线的工具发请求会一直超时。我的做法是注册中心维护一个“工具状态”接口Agent 每次调用前先查状态状态为draining时就不再发起新请求等存量请求处理完再完全摘除。4.5 编排 DAG 的落地DAG 编排我用一个轻量的自研调度器实现核心数据结构是邻接表。每个 Agent 节点记录自己的依赖节点列表调度器从入度为 0 的节点开始执行每完成一个节点就更新下游节点的入度入度归零则触发执行。from collections import deque def schedule(dag): indegree {node: 0 for node in dag} for node, deps in dag.items(): for dep in deps: indegree[node] 1 queue deque([n for n, d in indegree.items() if d 0]) order [] while queue: node queue.popleft() order.append(node) for n, deps in dag.items(): if node in deps: indegree[n] - 1 if indegree[n] 0: queue.append(n) return order这段代码返回的是拓扑排序后的执行顺序。实际运行时每个节点执行完还要把输出传给下游节点这部分我用一个共享的上下文存储Redis来传递避免节点之间直接耦合。5. 常见问题与排查技巧实录5.1 推理进程启动慢、冷启动超时这是最高频的问题。7B 模型冷启动 30 到 60 秒很正常如果客户端超时设得短用户直接看到报错。解决办法有三个一是预热在流量低峰期主动加载模型保持常驻二是就绪探针把initialDelaySeconds设大一点比如 60 秒确保 Pod 真正就绪后才接流量三是请求排队网关层做队列冷启动期间的请求先排队等实例就绪再转发。我实测下来预热加就绪探针组合最有效。就绪探针配置如下readinessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 60 periodSeconds: 10 failureThreshold: 35.2 显存不足导致 OOM显存 OOM 的排查思路是先看nvidia-smi确认实际占用再看推理引擎日志确认模型加载参数。常见原因是ctx-size设太大或者parallel设太高导致并发请求把显存吃满。我的经验值是16GB 显存跑 7B Q4 模型ctx-size 不超过 8192parallel 不超过 4。超过这个配置就要考虑换更大显存的卡或者用更激进的量化。5.3 会话丢失、上下文对不上会话丢失通常有两个原因一是网关没做一致性哈希请求被轮询到不同实例二是实例重启后本地状态没恢复。排查时先看网关日志确认同一 session-id 是否落到同一后端再看实例日志确认重启后是否从外部存储恢复了状态。如果两个都没问题那可能是客户端没带 session-id检查请求头。5.4 工具调用超时、级联失败工具调用超时如果处理不当会引发级联失败A 等 BB 等 CC 挂了A 和 B 都卡住。解决办法是给每个工具调用设独立超时并配置熔断。超时后立即返回错误让 Agent 决定是重试还是降级。熔断则是在工具连续失败 N 次后直接拒绝后续请求一段时间避免雪崩。问题现象可能原因排查方向解决手段推理启动慢模型大、冷启动看启动日志耗时预热 就绪探针显存 OOMctx-size 或 parallel 过大nvidia-smi 引擎日志调小参数或换卡会话丢失无一致性哈希网关日志配置 hash_policy工具级联失败无超时无熔断调用链日志独立超时 熔断5.5 多集群场景下的调度延迟如果你的 Agent 运行时跨多个集群调度延迟会明显上升。原因是跨集群网络往返比同集群慢一个数量级。我的优化手段是把会话状态和工具服务尽量部署在离用户近的集群推理进程集中在 GPU 集群。这样会话读写是本地操作只有推理请求跨集群延迟可控。另外跨集群调用要配置连接池和长连接避免每次请求都重新建连。6. 我在这套方案里踩过的几个真实坑第一个坑是把推理进程和 Agent 逻辑塞进同一个 Pod。看起来省事实际上两者生命周期完全不同Agent 逻辑要频繁更新推理进程要长期稳定。放一起的结果是每次更新 Agent 都要重启推理冷启动把用户全赶跑了。后来拆成两个 Deployment各自独立发布问题才解决。第二个坑是忽略工具调用的幂等性。Agent 重试工具调用时如果工具不是幂等的会产生重复副作用。比如“发邮件”工具被重试两次用户收到两封邮件。解决办法是在工具层引入幂等键Agent 每次调用带一个唯一 ID工具层根据 ID 去重。第三个坑是没有给推理进程设资源上限。有一次某个 Agent 疯狂发请求把推理进程的显存吃满导致同节点其他推理实例全部 OOM。后来给每个推理 Pod 设了nvidia.com/gpu: 1的硬限制并在网关层做了限流才杜绝了这类问题。第四个坑是日志和指标没打通。Agent 出问题时你需要在网关、运行时、推理进程、工具服务之间来回跳转查日志效率极低。后来我们统一了 trace-id每个请求从入口到出口带同一个 ID所有组件日志都打这个 ID排查时一个 ID 串起全链路效率提升非常明显。这套东西没有银弹每个环节都要根据实际负载调。我现在的做法是先用最小配置跑通全流程再逐步加压观察瓶颈在哪针对性优化。别一上来就追求完美架构那样只会陷入过度设计。