Ohhnews

分类导航

$ cd ..
DZone Java原文

第三部分:Goose、agentgateway与Quarkus的端到端追踪与可观测性

#分布式追踪#可观测性#opentelemetry#mcp#quarkus

企业背景 --- Acme FinServ。SOC 2 CC7(系统监控)要求 Acme 能够检测并调查异常活动。当代理驱动的工作流在凌晨 2 点接触客户数据时,“我们在某处有日志”不是审计员能接受的答案。本部分构建的分布式追踪是取证证据链:一个单一的追踪 ID,将 Goose 提示与每个 agentgateway 策略决策和每个 Quarkus 工具调用联系起来,因此事后事件审查可以准确重建哪个代理做了什么、按什么顺序、以及每个受治理的跳花费了多长时间。

核心问题

在第 1 部分中,我们构建了一个 Quarkus MCP 工具服务器。在第 2 部分中,我们使用 agentgateway 的 JWT 认证、RBAC 和 ExtMCP 护栏对其进行了保护。架构是有效的 --- 但当生产环境出现问题时,你就是在盲目飞行。

代理工作流与传统请求-响应 API 有根本的不同。一个简单的用户提示,如*“调试客户 CUST-4091”*,会触发一个多轮往返循环:

  1. Goose 调用 tools/list 以发现可用工具
  2. LLM 选择 getCustomerStatus,然后 Goose 发送 tools/call
  3. LLM 读取响应,看到 primaryRegion: US-EAST-1,并链式发起第二个 tools/call 到 getZoneHealthLogs
  4. LLM 关联两个结果并生成诊断摘要

这些跳中的每一个都跨越进程边界:Goose → agentgateway → Quarkus。没有分布式追踪,你会在访问日志中看到四个孤立的 HTTP 请求。你无法判断它们属于同一个代理工作流。当第 3 步花费 12 秒而不是 200 毫秒时,你没有瀑布图来定位延迟是来自 agentgateway 策略评估、Quarkus Bean 验证,还是缓慢的下游调用。这会在遥测仪表板中造成黑洞 --- 这正是自主代理利用来静默降级的缺口。

解决方案:跨所有三层的 W3C 追踪上下文

修复方法是将标准分布式追踪应用于 MCP 传输层:

[LOADING...]

  • agentgateway 为每个代理的 MCP 请求导出 span,并将 traceparent 头传播到后端。
  • Quarkus 使用 quarkus-opentelemetry 获取传入的 traceparent,为工具执行和 Bean 验证创建子 span,并将它们导出到同一个 Jaeger 实例。
  • Jaeger 将两侧关联为单个追踪瀑布图 --- 从代理提示到工具结果的单一视图。

先决条件

第 1 部分和第 2 部分的所有内容,外加:

  • Podman -- 用于运行 Jaeger(podman compose)

验证 Podman 是否可用:podman --version

步骤 1:启动可观测性后端

我们使用 Jaeger v2 作为 OTLP 收集器和追踪 UI。单个容器在端口 4317(OTLP gRPC)上接受来自 agentgateway 的追踪,在端口 4318(OTLP HTTP)上接受来自 Quarkus 的追踪,并在端口 16686 上提供查询 UI。

$ bash
cd part3-observability
podman compose up -d

这将启动 Jaeger v2,默认启用 OTLP 收集。验证其正在运行:curl -sf http://localhost:16686/ > /dev/null && echo "Jaeger UI is ready"

打开 http://localhost:16686 --- 你会看到一个空的 Jaeger UI。我们将在以下步骤中用 MCP 追踪填充它。

生产替代方案:Grafana Tempo

对于生产部署,请将 Jaeger 替换为由对象存储(S3/GCS)支持的 Grafana Tempo。OTLP 端点保持不变 --- 只有 compose.yml 发生变化。Grafana 提供更丰富的仪表板、告警和长期追踪保留。

步骤 2:在 Quarkus 中启用 OpenTelemetry

将 quarkus-opentelemetry 扩展添加到第 1 部分的 pom.xml 中:

$ xml
io.quarkus
quarkus-opentelemetry

在 application.properties 中配置导出器:

$ properties
# OpenTelemetry
quarkus.otel.service.name=customer-tools
quarkus.otel.exporter.otlp.traces.endpoint=http://localhost:4318
quarkus.otel.exporter.otlp.traces.protocol=http/protobuf
quarkus.otel.traces.sampler=always_on
quarkus.otel.traces.suppress-non-application-uris=false
属性用途
service.name在 Jaeger 的服务下拉菜单中标识此服务
traces.endpointOTLP HTTP 接收器 --- Jaeger 的端口 4318(仅基础 URL;Quarkus 追加 /v1/traces)
traces.protocolhttp/protobuf --- Quarkus 使用其基于 Vert.x 的 HTTP 导出器
traces.sampleralways_on --- 采样每个 span(在生产环境中减少)
suppress-non-application-urisfalse --- 包含 MCP 端点 span(否则它们会被过滤)

当没有 OTLP 收集器运行时(第 1 部分和第 2 部分没有 Jaeger),Quarkus 会记录连接警告,但 MCP 服务器正常工作。当收集器正在运行时(第 3 部分),追踪会自动流动。对 MCP 工具零代码更改。

重新构建第 1 部分:

$ bash
cd part1-quarkus-mcp
mvn package -DskipTests

Quarkus 自动检测什么

当 quarkus-opentelemetry 在类路径上且 SDK 已启用时,Quarkus 会自动为以下内容创建 span:

层Span 名称捕获内容
HTTP 服务器POST /mcp具有方法、状态、延迟的入站 MCP 请求
CDI BeanCustomerServiceTools.getCustomerStatusMCP 处理程序内的工具执行时间
Bean 验证HibernateValidator工具逻辑运行前的参数验证
REST 客户端出站 HTTP 调用任何下游 API 调用(未来扩展)

不需要 @WithSpan 注解。Quarkus OpenTelemetry 扩展会自动检测响应式管道。

步骤 3:在 agentgateway 中配置 W3C 追踪上下文

agentgateway 支持原生 OpenTelemetry 追踪导出。将 tracing 块添加到网关配置中:

$ config
config:
  adminAddr: localhost:15000
  tracing:
    otlpEndpoint: http://localhost:4317
    otlpProtocol: grpc
    randomSampling: 1.0
字段用途
otlpEndpointOTLP 接收器 --- Jaeger 的端口 4317
otlpProtocolgrpc 用于 OTLP/gRPC(也支持 http)
randomSampling采样 100% 的追踪(在生产环境中减少到 0.01--0.1)

追踪传播如何工作

当 agentgateway 收到 MCP 请求时:

  1. 为代理操作创建根 span(例如 agentgateway.mcp.proxy)
  2. 将 traceparent 头注入到转发给 Quarkus 的请求中:traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
  3. Quarkus 读取 traceparent,在同一个追踪 ID 下创建子 span,并记录工具执行
  4. 两个 span 都通过 OTLP 导出到 Jaeger,在那里它们显示为单个关联追踪

这是标准的 W3C 追踪上下文传播 --- 与所有 OpenTelemetry 插桩服务中使用的机制相同。

配置文件

第 3 部分提供了两种 agentgateway 配置:

配置用例
config-traced.yaml仅追踪 --- 代理 + OTLP 导出,无安全层
config-traced-guardrails.yaml追踪 + ExtMCP 护栏 --- 也观察护栏评估 span

步骤 4:运行交互式演示

使用一键脚本启动所有服务:

$ bash
cd part3-observability
./start-all.sh

该脚本启动 Jaeger、Quarkus(启用 OTel)和 agentgateway(启用追踪导出),然后在 :8890 上启动演示 SPA。

打开 MCP 可观测性控制台,网址为 http://localhost:8890/index.html,并完成三个演示步骤:

  1. 初始化 -- 通过 agentgateway 建立 MCP 会话。架构图动画展示追踪传播:agentgateway 中的根 span 创建、traceparent 注入、Quarkus 中的子 span,以及 OTLP 导出到 Jaeger。
  2. 列出工具 -- 通过已追踪的代理发现所有 5 个工具。追踪瀑布图面板并排显示 agentgateway 代理 span 和 Quarkus HTTP span 及时间。
  3. 多工具工作流 -- 模拟 Goose 的多轮推理:getCustomerStatus(找到区域 US-EAST-1)→ getZoneHealthLogs(检查区域健康)→ getSLACompliance(关联 SLA 指标)。每一步都会生成带有瀑布图可视化的完整追踪。

统计磁贴跟踪生成的追踪、收集的 span 和 Jaeger 状态。点击 打开 Jaeger 以在 http://localhost:16686 的 Jaeger UI 中查看真实的追踪瀑布图。

步骤 5:通过 CLI 生成追踪

要手动生成额外的追踪,请模拟一个多轮代理工作流:

$ bash
# Step 1: Initialize MCP session
export MCP_SESSION_ID=$(curl -s -D - http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-03-26" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}' \
  | grep -i "mcp-session-id:" | sed 's/.*: //' | tr -d '\r')

# Step 2: Discover tools
curl -s http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-03-26" \
  -H "mcp-session-id: $MCP_SESSION_ID" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
  | grep '^data: ' | sed 's/^data: //' | jq .

# Step 3: Agent calls getCustomerStatus (first tool invocation)
curl -s http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-03-26" \
  -H "mcp-session-id: $MCP_SESSION_ID" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"getCustomerStatus","arguments":{"customerId":"CUST-4091"}}}' \
  | grep '^data: ' | sed 's/^data: //' | jq .

# Step 4: Agent chains getZoneHealthLogs based on the region from step 3
curl -s http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-03-26" \
  -H "mcp-session-id: $MCP_SESSION_ID" \
  -d '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"getZoneHealthLogs","arguments":{"zoneId":"US-EAST-1"}}}' \
  | grep '^data: ' | sed 's/^data: //' | jq .

# Step 5: Agent fetches SLA compliance for correlation
curl -s http://localhost:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-03-26" \
  -H "mcp-session-id: $MCP_SESSION_ID" \
  -d '{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"getSLACompliance","arguments":{"serviceId":"api-gateway"}}}' \
  | grep '^data: ' | sed 's/^data: //' | jq .

这些请求中的每一个都会生成一个追踪,该追踪通过 agentgateway 流入 Quarkus 并到达 Jaeger。

步骤 6:在 Jaeger 中可视化追踪瀑布图

在浏览器中打开 http://localhost:16686。

查找追踪

  1. 在服务下拉菜单中,选择 customer-tools(Quarkus)或 agentgateway
  2. 点击查找追踪
  3. 点击任意追踪以打开瀑布图视图

阅读瀑布图

一个典型的 tools/call 追踪显示以下 span 层次结构:

agentgateway.mcp.proxy [12ms]
└─ POST /mcp [8ms] ← Quarkus HTTP server
   └─ CustomerServiceTools.getCustomerStatus [2ms] ← CDI tool execution
Span服务它告诉你什么
agentgateway.mcp.proxyagentgateway包括策略评估在内的总代理开销
POST /mcpcustomer-toolsQuarkus 对 MCP 请求的 HTTP 处理时间
getCustomerStatuscustomer-tools纯工具执行时间(业务逻辑)

要寻找什么

  • 代理开销:agentgateway span 和 Quarkus span 之间的差距显示网络 + 策略评估时间。如果这个增长,检查护栏服务器延迟。
  • 验证时间:Bean 验证 span 出现在工具执行之前。像 ^CUST-[0-9]{4,8}$ 这样正则表达式密集的模式很快,但大型负载上的复杂验证器可能会增加延迟。
  • 多轮关联:当 Goose 链式调用多个工具调用(例如 getCustomerStatus → getZoneHealthLogs)时,每个都显示为单独的追踪。mcp-session-id 标签允许你过滤属于一个代理会话的所有追踪。
  • 错误追踪:验证失败(无效的客户 ID 格式)或护栏拒绝(阻止毒化负载)会产生带有异常详细信息的错误 span。

连接 Goose 以获取真实追踪

启动指向 agentgateway 的 Goose,并提示一个多工具工作流:

goose session "调试客户 CUST-4091 --- 检查他们的账户状态,然后获取其区域的健康日志以及 api-gateway 的 SLA 合规性。"

这会在 Jaeger 中生成一批关联的追踪,显示 Goose 从代理层到单个工具执行 span 的多轮工具编排。

我们实现了什么

从第 2 部分的安全架构开始,我们在不更改任何 MCP 工具代码的情况下添加了完整的可观测性:

层我们添加了什么配置更改
Quarkusquarkus-opentelemetry 依赖pom.xml + application.properties
agentgateway配置 YAML 中的 tracing 块config-traced.yaml
可观测性后端通过 Podman Compose 的 Jaeger all-in-onecompose.yml

整个堆栈通过单个 ./start-all.sh 命令在本地运行,并在 Jaeger 中生成端到端的追踪瀑布图。

生产考虑因素

关注点本地(本教程)生产
追踪后端Jaeger all-in-one(内存中)Grafana Tempo + 对象存储
采样率100%(default: 1.0)1-10% 或自适应采样
追踪保留容器生命周期持久存储中数天/数周
告警手动 Jaeger 检查在 span 延迟/错误率上进行 Grafana 告警
指标仅追踪添加 Prometheus + quarkus-micrometer 用于 RED 指标

第 4 部分即将推出

有了追踪,你现在可以看到系统中流动的每个 MCP 工具调用。在第 4 部分中,我们将超越单代理工具调用,转向多代理编排 --- 使用代理到代理 (A2A) 协议来协调自主代理,这些代理可以委派工作、通过 AGENTS.md 强制执行治理,并回调我们的 MCP 工具服务。