Agent-Reach:多Agent系统可达性巡检与探针验证实践
1. 代理为什么会触达失败——Agent-Reach要解决的第一个问题上手 Agent-Reach 这个项目之前我先交代一下背景。最近半年我一直在折腾多代理协作系统把之前单体的代码分析机器人拆成了调度器、检索器、执行器三层。拆完之后基础设施层的问题开始暴露任务下发后Agent 之间通过 HTTP 互相调用但时不时出现请求超时、连接重置、回调地址错误。打开日志看原因千奇百怪但本质上都指向同一个问题——目标服务到底能不能被正确触达。传统的监控工具能回答服务进程是否存活但存活和可触达完全是两码事。一个 Agent 调用另一个 Agent中间隔着服务发现、负载均衡、网关路由、容器网络、TLS 证书校验。任何一环配置错误结果都一样调用失败且报错信息往往极具迷惑性。比如你看到Connection refused可能会去检查目标进程但实际是网关路由规则没生效看到Timeout可能去调超时参数但实际是 DNS 解析到了旧 IP。Agent-Reach 这个项目就是我做的一个轻量级可达性巡检与验证工具它的定位是在 Agent 正式接入生产链路之前以及每次发布路由变更之后对目标服务是否真正可以被 Reach做系统性检查。它不是 APM不做链路追踪也不是传统的 uptime 监控不需要安装在被测目标上。它就做一件事按配置的三类探针去目标地址发起真实请求再按指纹规则判断结果是否符合预期。适合什么场景如果你在跑多 Agent 系统、给 LLM 服务商接代理网关、或者搭建企业内部的服务注册中心你会需要它。尤其当你的 Agent 数量超过五个、依赖关系开始变成网状之后人工检查哪个 Agent 调不通会把人逼疯Agent-Reach 就是用来把这个问题变成一条命令的事。我在第一次全量巡检里用一份 38 个端点的配置跑完抓出了 4 个表面上健康但实际调不通的服务。下文会写到具体排查过程有些坑是我之前完全没预料到的。2. Agent-Reach 的核心模型三类探针与一套指纹规则2.1 为什么ping 通不等于可达做 Agent-Reach 时我第一件事是明确可达的定义。最粗浅的理解是主机能 ping 通但这个定义在 Agent 场景下几乎没有任何价值。一个数据库容器能 ping 通不代表你的 Agent 能用 MongoDB 协议连上一个 Web 服务能 ping 通不代表 POST 请求能通过网关的鉴权中间件。所以我在设计里做了一个硬性约定Agent-Reach 里没有 ICMP 探针所有探针必须走真实的应用层协议。这个选择放弃了探测速度换来了探测结果的真实性。你要检查的不止是网络通不通而是按照 Agent 实际调用方式去请求能不能得到预期的响应。整个检查模型分为三层传输可达性TCP 端口能否建立连接、TLS 握手能否完成、证书是否有效。协议可达性HTTP 状态码是否在预期范围、响应体是否包含预期关键字、响应时间是否在阈值内。业务可达性对于自定义探针判断是否完成一次真实的业务握手比如向某个 Agent 发送ping消息并期待返回pong。这三层只要有一层不满足检查结果就是失败并明确标注是哪一层出了问题。这样排查时不需要重新做一遍定位直接看报告就能知道改哪里。2.2 探针配置与执行机制Agent-Reach 提供三类探针对应上面三层实际配置里可以按需组合探针类型作用配置项适用场景http发起真实 HTTP 请求验证状态码、响应体、响应时间url,method,headers,body,expect_status,expect_body,timeout_ms这是主力探针覆盖 Agent 间 API 调用、网关路由验证tls建立 TCP 连接并完成 TLS 握手校验证书链与 SNIhost,port,sni,allow_insecure,min_version检查 HTTPS 端点、内部 mTLS 网关、证书即将过期的主机command在探针本机执行任意命令验证返回码与输出cmd,args,expect_exit_code,expect_output检查数据库连接字符串、Redis 连通性、自定义初始化脚本执行机制上Agent-Reach 采用并发执行加独立超时控制。默认并发数为 10每个探针独立配置超时时间默认 5 秒。为什么要独立超时因为不同服务对延迟的容忍度差异很大一个 LLM 推理接口可能要 20 秒才返回而一个注册中心健康检查应该 200ms 内返回。统一超时阈值会让两类检查永远有一个是错的。2.3 指纹规则如何判断可达不是只看 200这是 Agent-Reach 与普通健康检查工具最大的区别。很多服务的/health接口在设计时只检查了进程存活性没有检查依赖的数据库连接池。结果就是健康检查返回 200实际请求却因为数据库连接耗尽而报错。所以我在指纹规则里支持三层判断状态码判断不只是等于某个值还支持区间匹配。比如expect_status: [200, 201]表示 200 或 201 都算通过expect_status: 2xx表示整个 2xx 段都算通过。响应体关键字判断支持正则表达式。比如一个 Agent 的注册接口正常会返回{status:ok,agent_id:...}你可以配置expect_body: status:ok。只有响应体里出现这个关键字才算可达。反向排除判断某些场景下返回 200 反而是异常。比如网关拦截器会把非法请求统一包装成 200 并在 body 里写error:blocked这种必须配置not_expect_body来兜底。用指纹规则去验证而不是简单看 TCP 连接是否建立核心原因是Agent 之间的调用链往往有中间层而这些中间层不会告诉你是哪里断了。反向代理能返回 502但报错信息不会告诉你后端连接池是否耗尽网关能返回 504但你不知道是上游超时还是下游真的慢了。Agent-Reach 通过在预期位置设置指纹把模糊的通或者不通变成具体哪一层不符合预期。3. 从零跑通最小配置、执行与结果解读3.1 最小配置长什么样Agent-Reach 的配置是一个 YAML 文件顶层是targets列表。每个 target 至少包含名称、类型、目标地址和探针定义。下面是一个真实的最小示例检查一个本地 Agent 编排服务的注册接口version: 1 global: concurrency: 10 default_timeout_ms: 5000 targets: - name: dispatcher-register-api type: http url: http://localhost:8080/api/agent/register method: POST headers: Content-Type: application/json X-Agent-Token: test-token-123 body: {agent_id: reach-probe-01, capabilities: []} expect_status: [200, 201] expect_body: status:registered timeout_ms: 3000 tags: [core, dispatcher] - name: internal-gateway-tls type: tls host: gateway.internal port: 8443 sni: gateway.internal allow_insecure: false min_version: TLSv1_2 tags: [infra, gateway] - name: redis-connection-check type: command cmd: redis-cli args: [-h, 127.0.0.1, -p, 6379, ping] expect_exit_code: 0 expect_output: PONG timeout_ms: 2000 tags: [cache]这份配置里包含了我反复强调的一个细节探针请求必须模拟真实调用形态而不是只打一个/health轻量接口。以 dispatcher-register-api 为例实际 Agent 调用注册接口时会带 token、会提交结构化 body那我探针也带同样格式的 token 和 body。如果我只是 GET 一下根路径检查出来的结果对生产没有参考意义。3.2 命令行执行与报告输出执行方式极简一条命令agent-reach check --config config.yaml --format table输出是终端的表格形式TARGET TYPE RESULT LATENCY(ms) DETAILS dispatcher-register-api http PASS 8 status200 internal-gateway-tls tls PASS 12 cert_validyes sniok redis-connection-check command PASS 1 outputPONG如果失败DETAILS 列会写明失败层级和原因。比如 TLS 层证书过期会提示cert_expired days3HTTP 层状态码不匹配会提示expect2xx got502响应体关键字缺失会提示body_missing_keyregistered。我额外加了--format json选项方便接入 CI 脚本和告警系统。JSON 输出里每一个 target 会带上完整的探测元数据包括本地时间戳、DNS 解析结果、TCP 连接耗时、TLS 握手耗时、首字节耗时、总耗时和响应体快照。这些分段耗时数据在后续排查里价值极大——它能直接告诉你延迟瓶颈在哪一层。3.3 结果报告里必须看的四个字段很多人跑完巡检只看 PASS/FAIL这是最大误区。LATENCY和DETAILS才是辩证看问题的地方。第一耗时异常但有 PASS 的情况。一个接口平时 50ms某次巡检 800ms 但没超阈值Agent-Reach 会标 PASS但你应该看到这个苗头。我在后续版本里加了warn_latency_ms配置项超过这个值会标记为 WARN相当于给慢响应提前拉警铃。第二分段耗时的对比。TCP 连接耗时正常但首字节耗时很长说明问题在服务端处理DNS 解析耗时异常提升说明缓存可能失效了TLS 握手耗时提升可能和证书链长度变化有关。这些信息在每个 target 的 JSON 输出里都有要做的是养成每次巡检都留存 JSON 日志的习惯便于做趋势对比。第三响应体快照。失败时 Agent-Reach 会把实际返回的 body 片段截取 500 个字符放进详情里。这个设计帮我在一次排查中直接看到网关把请求重定向到了登录页返回 302 HTML 登录页而状态码检查因为配置的是expect_status: [200, 201]直接判失败。如果只看状态码我会去检查后端服务但实际上后端好好的是网关鉴权配置变了。第四噪音与误报。巡检跑得越频繁越要警惕噪音告警。某个依赖外部网络的 Agent 接口可能每周有一两次偶发超时这种探针结果如果是 FAIL是否要告警需要你自己决定。Agent-Reach 提供了allow_failures: 2和stability_window_min: 60这种稳定性判定选项意思是某个 target 在 60 分钟内允许失败 2 次超过才算真正异常。这个机制能有效过滤偶发抖动。4. 一次真实演练给内部 Agent 网关做完一次全量可达性巡检4.1 巡检配置前的准备工作我要把 Agent-Reach 投入使用的契机是一次让我很狼狈的线上事故。某个 Agent 在凌晨调用另一个 Agent 的模型服务整体超时导致任务队列积压。事后查了半天发现是模型服务所在节点的负载均衡器配置被改坏了影响范围是所有经过该负载均衡的调用而该服务的健康检查接口恰好绕过了负载均衡器直连后端所以监控系统没有报警。这个事故让我意识到我需要的不是监控服务是不是活着而是按真实调用路径检查整条链路的可达性。所以在正式巡检前我没急着写配置文件而是先做了两件事第一把现有的 Agent 调用关系梳理成一张依赖清单。每一个谁调用谁都记录全调用协议、真实端口、路径、认证方式。这一步很费工夫但它是配置文件的来源不能省。我最终清出了 38 个端点分属四类Agent 间互调、对 LLM 服务商的调用、对内部工具服务的调用、对注册中心和配置中心的调用。第二给每个端点确定探针类型和指纹规则。Agent 间互调用 HTTP 探针指纹校验响应体对 LLM 服务商用 HTTP 探针但超时阈值放宽到 30 秒对内部工具服务用 TCPTLS 探针验证链路对 Redis、数据库这类无 HTTP 的服务用 command 探针。4.2 巡检发现的两个影子问题跑完一份包含 38 个 target 的配置结果是 34 个 PASS、4 个 FAIL。这 4 个失败里有两个是预期内的另外两个非常有意思——它们不在我最初怀疑的名单里。第一个影子问题是个 Worker 容器。它的服务进程活着docker ps正常容器内日志也显示正常启动。但 Agent-Reach 的 HTTP 探针从网关侧发起请求时直接连接超时。排查链路是探针报connect_timeout说明 TCP 层都没建立检查容器网络时发现这个 Worker 挂在一个自定义 bridge 网络上而网关在另一个网络里。问题是之前它们明明在同一个网络里为什么现在不在了答案指向一次docker compose配置变更某个服务加了networks:配置导致编排系统重建容器时把 Worker 的网络关联弄丢了。健康检查之所以还显示正常因为检查脚本在 Worker 容器内部本地执行根本不经过跨网络的链路。这就是我在 2.1 里说的三层可达性的典型场景——服务本地活着但从调用方视角已经不可达。第二个影子问题更有迷惑性探针检查一个内部模型网关的 HTTPS 接口返回了 200但响应体里的指纹关键字没匹配上。这个指纹实际是模型网关的正常响应格式{code:0,data:...}实际返回的却是网关层面生成的 500 错误包装成 200 的响应。这种情况在接入层做了统一错误处理的服务上很常见——框架捕获异常后返回 200 状态码但业务状态码写在 body 里。如果只做状态码检查这种问题永远发现不了。Agent-Reach 的expect_body正则匹配在这里救了我一命。4.3 一次路由变更的前后对比巡检完一批老的部署之后我又做了一次路由变更的验证。当时要把某个 Agent 的调用从旧地址切到新地址变更窗口是五分钟。我做了三份配置变更前巡检一次作为基线变更后立即巡检一次作为即时验证五分钟后巡检一次作为稳定确认。三次结果对比下来发现了新地址的一个严重问题新地址的 TCP 连接正常、TLS 握手正常、状态码 200但响应时间从旧地址的 12ms 飙升到 2.3 秒。表面看是可达的但按 Agent 的调用超时配置3 秒这已经是极限边缘了。而且这个 2.3 秒有明显上升趋势再叠加调用并发量极有可能触发超时雪崩。这里 Agent-Reach 的warn_latency_ms就起作用了——我把这个值设置成 800ms新地址的 target 直接被标记为 WARN。如果不是这个机制我大概率会认为切换成功等几分钟后真正接到生产流量才会发现问题。这类隐患是传统健康检查完全看不出来的因为它们只关心通和不通不关心通得够不够快。5. 关键经验Agent 可达性巡检的几个容易被忽视的陷阱5.1 DNS 缓存与驳回问题Agent-Reach 默认使用系统 DNS 解析目标域名。在一次巡检中我发现某 target 偶发失败查看 JSON 输出发现dns_lookup_ms有时候是 0有时候是 5000。排查后发现是本机 systemd-resolved 缓存问题第一次解析走了网络后续走缓存但缓存条目过期后下一次解析会触发上游查询如果上游 DNS 响应慢整个连接就超时。对策是在探针配置里增加dns_resolve选项可以强制使用自定义 DNS也可以设置no_cache: true跳过系统缓存。对于 Agent 间互调这种高频场景我还建议在探针配置里写死目标 IP不要依赖 DNS除非你有充分的理由必须用域名。5.2 健康检查路径与真实业务路径不一致这是我在内部排查中发现的最普遍的问题。很多服务的健康检查路径没有经过认证中间件、没有经过限流中间件、没有连接数据库。Agent-Reach 的探针设计从一开始就要求必须走真实调用路径这才让巡检结果有参考价值。但反过来也要注意如果你的探针请求带上了真实 token它也会被限流中间件计入配额。所以 Agent-Reach 的探针请求必须设立独立 token并把这个 token 的配额调高。我在实际配置里给探针建了一个专用账号权限设计为只读或最小调用权限。这样既不会污染业务数据也不会因为触发了风控策略导致探针被拉黑。5.3 对身体大小的敏感度这个坑让我花了大半天时间。一个 LLM 服务的网关接口我用一个小 body 做探针一切正常但实际 Agent 调用时会传上下文摘要body 有几十 KB。结果小 body 探针显示 PASS真实调用却频繁 413 Payload Too Large。原因就是网关的client_max_body_size配置只覆盖了部分路由。Agent-Reach 的 HTTP 探针支持直接配置大 body我的建议是至少准备一份大 payload 的探针配置专门用于检查上传类接口和原文类接口。探针 body 不用完全模拟真实数据但大小要在同一个量级——几十 KB 的接口就用几十 KB 的 body 去测几 MB 的接口就用几 MB 的 body 去测。5.4 并发探测的副作用Agent-Reach 默认并发是 10但如果你的目标服务是个数据库或者单实例 Agent10 个并发探针可能直接触发对端的连接数上限或者限流策略。我在一次巡检中发现目标服务的连接数飙升到接近最大值最后定位到就是探针并发导致的——它的连接池里堆满了 Agent-Reach 建立后主动断开的连接。对策是两层全局并发调低global.concurrency从 10 改为 4对已知敏感的服务单独设置target.concurrency: 1让探针串行执行。宁可巡检时间拉长一点也不要因为巡检本身把线上服务搞挂。5.5 探针的超时时间不能统一前面提过 LLM 推理接口可能要 20 秒而 Redis 应该 200ms。Agent-Reach 默认 5 秒超时在实际使用中太一刀切了。我在配置里对每个 target 都显式设置了timeout_ms这个值通常取该服务的 P95 响应时间 50% 余量。这样既能保证巡检不会因为偶发慢响应而报错又能保证真正的问题不会被过长的超时掩盖。6. Agent-Reach 后续可以扩展的方向Agent-Reach 目前在我的基础设施里承担两层职责一是每次部署发布前做一轮 pre-flight 检查跑在 CI 里作为发布卡点二是每隔 30 分钟定时巡检整个 Agent 调用网发现异常直接推动告警到值班群。下一步我计划做两件扩展。第一件是联动注册中心从服务注册表自动生成 targets而不是维护一份 YAML。这样新 Agent 上线时会自动纳入巡检范围不会出现服务注册了但没人检查它是否可达的盲区。第二件是根据巡检结果自动触发路由切换。目前的 Agent-Reach 只做检测和告警检测到某个节点不可达之后调度器仍会继续把任务发到故障节点。如果能把 Agent-Reach 的检测结果直接作为路由权重调整的输入源让调度器在检测到 FAIL 后自动把流量切到备选节点就可以把发现问题和解决问题连起来。当然这个方案需要谨慎。自动切换的风险在于误判导致的流量雪崩所以我计划在前面提过的稳定性判定机制基础上加入 fail 连续次数阈值至少连续 3 次巡检失败才认为是真故障才允许触发路由切换。回过头看Agent-Reach 本质上解决的是一个很朴素的工程问题在 Agent 数量多起来之后靠人肉肉眼检查依赖关系是否健康已经完全不现实了。把可达性检查变成一条命令、一份配置、一个自动化的卡点比等线上出事故再排查要省心太多。我在这次实践里最大的感性认识是探针设计必须死磕与真实调用形态一致凡是贪图方便而简化的探针最后都会在某个深夜咬你一口。