A2A 生产就绪检查:测试、观测与回滚
一个服务能成功响应一次 A2A 请求,并不代表它已经适合生产环境。公开 Agent Card 可能与实际部署的端点发生偏移;通过认证的调用方仍可能读到别人的任务;流式连接中断后可能没有恢复路径;Webhook 能力也可能变成 SSRF 入口。
本指南把这些风险转化为一次明确的发布判断。它假设服务端已经实现 A2A v1.0.1。构建和校验卡片请使用 Agent Card 实现指南 ,校验任务轨迹请使用 Task 运行时指南 。这里要完成的是另一件事:证明已部署系统、公开契约和恢复控制彼此一致。
发布判定
不要用一次健康检查代替生产就绪判断。下面每个适用的门禁,都应保留一份可复现证据。
| 门禁 | 应保留的证据 | 阻止发布的情况 |
|---|---|---|
| 发现 | 公开卡片 URL、响应头、卡片摘要和校验输出 | 卡片不可访问、无效,或指向不同的版本、绑定、端点 |
| 传输 | 每个生产接口的 TLS 结果 | 生产 HTTP 绑定使用明文,或证书校验失败 |
| 认证 | 正常凭据,以及缺失、无效、过期凭据的测试 | 受保护端点接受错误凭据,或挑战响应不正确 |
| 授权 | 跨用户、跨租户的任务读取、取消、订阅及推送配置测试 | 调用方能推断或访问权限边界外的任务 |
| 任务生命周期 | 直接完成、中断、失败、取消和重试行为的轨迹 | 状态转换或终态行为与协议、产品契约不一致 |
| 流式传输 | 初始结果、有序更新、终态事件、断连和重新订阅证据 | 卡片声明支持流式传输,但重连不能安全恢复 |
| 推送通知 | URL 校验、出站认证、重复投递和重放测试 | 可以访问私有或链路本地地址,或重复投递破坏状态 |
| 可观测性 | 可关联的请求、任务、上下文、追踪标识,以及指标和脱敏日志 | 运维人员无法解释失败任务,或排查依赖凭据、消息正文 |
| 过载与恢复 | 超时、限流、依赖故障和重启测试 | 负载导致工作无界增长、已接收状态丢失或重试失控 |
| 回滚 | 上一版本制品/配置标识,以及演练过的能力下线顺序 | 无法撤销故障能力,或回滚后 Agent Card 仍在做错误声明 |
协议定义的是互操作与安全要求,不会替你的部署做认证。检查通过只是你自己的发布证据,不是“A2A 官方认证”。
1. 执行公开发现预检
请从生产网络之外执行下面的脚本。它检查的是公开响应,而不是构建目录中的本地文件。脚本依赖 curl 和 jq。
#!/usr/bin/env bash
set -euo pipefail
: "${A2A_CARD_URL:?Set A2A_CARD_URL to the public Agent Card URL}"
case "$A2A_CARD_URL" in
https://*) ;;
*) printf 'ERROR: production Agent Card URL must use HTTPS\n' >&2; exit 1 ;;
esac
work_dir="$(mktemp -d)"
cleanup() { rm -rf "$work_dir"; }
trap cleanup EXIT HUP INT TERM
status="$({ curl --silent --show-error --location \
--proto '=https' --tlsv1.2 \
--connect-timeout 5 --max-time 15 \
--dump-header "$work_dir/headers" \
--output "$work_dir/card.json" \
--write-out '%{http_code}' \
"$A2A_CARD_URL"; } 2>"$work_dir/curl.error")" || {
cat "$work_dir/curl.error" >&2
exit 1
}
if [[ "$status" != "200" ]]; then
printf 'ERROR: Agent Card returned HTTP %s\n' "$status" >&2
exit 1
fi
content_type="$(tr -d '\r' < "$work_dir/headers" \
| awk -F ': *' 'tolower($1) == "content-type" {print tolower($2)}' \
| tail -n 1)"
case "$content_type" in
application/json*) ;;
*) printf 'ERROR: expected application/json, got %s\n' "${content_type:-missing}" >&2; exit 1 ;;
esac
jq -e '
type == "object"
and ([.name, .description, .version] | all(type == "string" and length > 0))
and (.supportedInterfaces | type == "array" and length > 0)
and (.skills | type == "array" and length > 0)
and all(.supportedInterfaces[];
(.protocolBinding == "GRPC" and (.url | test("^([^/:]+|\\[[0-9A-Fa-f:]+\\]):[0-9]+$")))
or (.protocolBinding != "GRPC" and (.url | startswith("https://"))))
and ((.securitySchemes // {}) as $schemes
| [(.securityRequirements // [])[]?.schemes? | keys[]] as $refs
| ($refs | all(. as $name | $schemes | has($name))))
' "$work_dir/card.json" >/dev/null
digest="$(shasum -a 256 "$work_dir/card.json" | awk '{print $1}')"
printf 'OK: HTTP 200, JSON contract and production interfaces passed\n'
printf 'card_sha256=%s\n' "$digest"
jq -r '.supportedInterfaces[] | "interface=\(.protocolBinding) \(.protocolVersion) \(.url)"' \
"$work_dir/card.json"调用示例:
A2A_CARD_URL='https://agent.example/.well-known/agent-card.json' \
bash production-preflight.sh脚本检查可访问性、仅用 TLS 获取、媒体类型、卡片基本结构、生产接口地址,以及卡片级安全要求对安全方案的引用。它不能证明端点真正实现了声明的操作,也不会验证密码学签名。还要运行 Agent Card 指南里的完整校验器,并把输出与本次预检结果放在一起。
把卡片摘要和部署标识一起记录。如果同一个发布版本后来返回了不同摘要,即使两份卡片分别都合法,也已经发生配置漂移。
2. 证明认证与隔离有效
对公开的每一种绑定都执行测试,不要只经过内部 SDK。对于受保护端点,至少保留下列请求及状态:
| 场景 | 预期结果 |
|---|---|
| 未提供凭据 | 按未认证拒绝;HTTP 通常返回 401,并带恰当的 WWW-Authenticate 挑战 |
| 凭据格式错误、已过期或 audience 错误 | 按未认证拒绝,并且不执行智能体工作 |
| 身份有效但缺少所需权限 | 按禁止访问拒绝;HTTP 通常返回 403 |
| 身份有效且具有所需权限 | 接受请求,并把行为归属到该身份 |
认证只是第一道边界。先由调用方 A 创建任务,再让调用方 B 尝试 GetTask、ListTasks、CancelTask、SubscribeToTask,以及管理任务的推送通知配置。使用 AgentInterface.tenant 时,再跨租户执行同一组测试。拒绝响应不应通过响应文本、耗时或分页数量暴露隐藏任务是否存在。
A2A 安全规范要求任务结果和操作都限制在调用方的授权边界内。协议有意把授权模型留给实现,因此发布证据必须注明每次结果对应的身份、租户、权限范围和策略版本。详见认证、授权与安全注意事项 。
3. 逐项验证声明的能力
Agent Card 是对外运行契约。每项能力都要覆盖支持路径和不支持路径:
| 声明 | 正向测试 | 负向或恢复测试 |
|---|---|---|
streaming: true | 收到初始 Task 或 Message、有序更新和终态结果 | 在任务中途断开,再用 SubscribeToTask 连接,证明终态不会静默丢失 |
streaming: false 或缺省 | 普通非流式请求正常 | 流式操作返回 UnsupportedOperationError |
pushNotifications: true | 创建、读取、列出、删除配置,并收到通过认证的通知 | 拒绝不安全目的地址;制造重复投递并证明幂等处理 |
pushNotifications: false 或缺省 | 任务处理仍然正常 | 四个配置操作都返回 PushNotificationNotSupportedError |
extendedAgentCard: true | 已授权调用方取得配置好的扩展卡片 | 缺少凭据、权限不足时拒绝,并且不泄露卡片 |
| 必需扩展 | 声明支持扩展的客户端能完成操作 | 未声明支持的客户端收到 ExtensionSupportRequiredError |
把采集的任务与流式事件交给 Task 运行时指南
中的校验器。至少保留完成、失败、取消三种任务轨迹;产品包含 INPUT_REQUIRED、AUTH_REQUIRED 路径时,也要分别保留。
处理推送通知时,每次重定向之后、真正连接之前都要重新解析并校验目的地址。在应用层和网络出口同时阻止回环、私有、链路本地及其他不允许的地址。对出站调用做认证,对入站调用做验证,并以幂等方式处理重复通知。完整的责任边界见流式传输与异步操作 。
4. 让故障可以解释
一个生产任务会经过多种标识。日志要能关联各层,但不能记录秘密或完整消息正文:
timestamp, release_id, protocol_version, binding, operation,
request_id, trace_id, task_id, context_id, tenant_id,
caller_subject_hash, outcome, protocol_error, duration_ms边缘代理与 A2A 服务使用一致的字段名。默认脱敏授权头、Cookie、Webhook 凭据、文件内容、Part、Artifact 和自由文本消息。任务 ID 只适合关联,不是授权证明。
至少监控请求量、延迟、协议错误、任务终态、中断任务、活跃流连接、重连次数、Webhook 投递与重试时长、依赖故障、被拒绝的不安全地址。告警应针对用户影响和停滞工作,而不是每个单独失败。
5. 测试过载、重启与重试边界
使用有明确上限的压力测试,并包含慢依赖和长任务。放量前逐项确认:
- 新工作在既定上限处被拒绝或排队;内存和线程/协程数量不会无界增长;
- 客户端超时不会自动取消服务端工作,除非产品明确实现并约定了该行为;
- 进程重启后,已接收任务会按照产品契约保留,或以明确结果失败;
- 对超时请求的重试不会制造失控的重复副作用;业务操作需要 exactly-once 效果时,要由应用定义幂等策略,A2A 不会替业务行为自动提供这一保证;
- 下游依赖故障会形成有界协议错误或可恢复任务状态,而不是永远停在
WORKING。
保留负载模型、发布标识、限制参数和结果。“端点返回了 200”不能作为这一门禁的充分证据。
6. 准备不破坏能力契约的回滚
回滚顺序很重要。如果接口已经撤销,公开卡片却仍声明对应能力,遵循协议的客户端就会继续调用不存在的路径。
单项能力发生故障时:
- 在流量或功能开关处阻止新请求使用该能力;
- 发布不再声明该能力或受影响接口的 Agent Card;
- 遵守卡片的缓存行为,为发现缓存留出刷新时间;
- 排空在途工作,或以明确结果终止;
- 回滚服务制品或配置;
- 重新执行公开预检和受影响的负向测试。
完整版本回滚时,把上一版服务制品、配置标识、Agent Card、数据库兼容性决策和回滚负责人放在一起。某个部署步骤的远端结果未知时,不要自动重试;先回读已部署身份和当前卡片摘要。
故障到行动的映射
| 观察结果 | 更可能出问题的边界 | 第一行动 |
|---|---|---|
| 卡片在本地校验通过,但公开摘要不同 | CDN、部署配置或旧源站 | 不依赖本地文件重新获取,检查缓存头,并比较部署版本标识 |
有效令牌收到 401 | 认证配置 | 先检查 issuer、audience、时间和绑定对应的凭据传输,再调整授权 |
| 调用方 B 可以看到 A 的任务 | 授权/数据查询 | 冻结发布;在返回存在性或内容之前先按调用方范围查询 |
流结束在 SUBMITTED 或 WORKING | 连接或服务端故障 | 使用 SubscribeToTask 或 GetTask,不能假设任务已完成或失败 |
| 推送只对内部地址可用 | 测试设计不安全或存在 SSRF 暴露 | 关闭推送配置创建,落实公开地址策略与出口控制,再测重定向和 DNS 变化 |
| 代码已回滚,但客户端仍调用已移除操作 | 发现契约漂移 | 修正公开 Agent Card,并等待它声明的缓存窗口结束 |
证据清单
随发布保留一份小型清单,不要把证据散落在聊天截图中:
release_id: "git-sha-or-artifact-digest"
protocol_version: "1.0"
tested_at: "2026-10-25T12:00:00Z"
agent_card_url: "https://agent.example/.well-known/agent-card.json"
agent_card_sha256: "..."
bindings: ["HTTP+JSON"]
capabilities: ["streaming"]
checks:
discovery: pass
authentication: pass
authorization_isolation: pass
task_lifecycle: pass
streaming_recovery: pass
push_security: not_applicable
overload_restart: pass
rollback_artifact: "previous-release-id"
owner: "team-or-role"清单只是索引,不是证据本身。把它链接到机器可读测试结果和脱敏轨迹,与发布版本一起保存。只要 Agent Card、认证策略、协议绑定、能力开关、存储模型或边缘路由发生变化,就重新执行受影响的门禁。