上海开源大赛公布的 ZSvirt 命题,把一个越来越棘手的问题摆到了台前:AI 应用已经运行在由 GPU、vGPU、虚拟机、容器、模型服务和 Agent 共同组成的智算云中,但故障信息仍然散落在不同层级。基础设施监控能看到主机、网络、存储和告警,业务侧却只知道“推理超时”或“Agent 没有返回结果”。真正困难的不是再增加一个监控面板,而是把这些信号拼成一条可验证的故障链路。
同一个超时,可能来自六个层级
一次模型请求超时,表面上是 API 返回 504,根因却可能出现在完全不同的位置:
- GPU 层:显存耗尽、温度过高、设备掉卡或 ECC 错误。
- 虚拟化层:vGPU 配额不足、虚拟机被迁移,或者宿主机资源争抢。
- 容器层:Pod 重启、健康检查失败、CPU 限流或内存被回收。
- 网络与存储层:模型权重加载缓慢、跨节点通信抖动、依赖服务不可达。
- 模型服务层:批处理队列积压、上下文过长、推理引擎异常。
- Agent 层:工具调用超时、重试放大流量、工作流停在某个步骤。
因此,诊断系统不能只按日志文本搜索关键词。它至少需要回答三个问题:故障发生在哪个时间窗口,受影响的业务实例运行在哪些资源上,以及这些资源之间有什么依赖关系。
ZSvirt 提供的主机、虚拟机、网络、存储、GPU/vGPU 和告警视角,可以成为基础设施侧的数据入口。至于容器、模型服务和 Agent,则需要通过 Kubernetes 元数据、指标、调用链和业务事件补齐。来源摘要没有给出具体接口,下面的实现按通用 Kubernetes 与 JSON 事件格式展示,可以根据实际环境改造。
用资源关系图代替孤立告警
跨层诊断的关键数据结构不是“告警列表”,而是资源关系图。例如一次请求可以沿着下面的路径定位:
request_id
-> agent_run_id
-> model_service
-> pod
-> virtual_machine
-> host
-> gpu_or_vgpu
每个节点还应保留稳定标识。Pod 名称可能因重建而变化,所以应记录 Pod UID;虚拟机应使用实例 ID;GPU 应记录设备 UUID,而不是只保存 GPU 0 这样的局部编号。边也要带时间,因为 Pod 调度位置和 vGPU 绑定关系都会变化。
统一事件至少可以包含这些字段:
{
"timestamp": "2025-03-08T10:15:31Z",
"source": "model-service",
"event_type": "inference_timeout",
"severity": "error",
"request_id": "req-42",
"entities": {
"pod_uid": "pod-a1",
"vm_id": "vm-17",
"host_id": "host-03",
"gpu_uuid": "GPU-abcd"
},
"attributes": {
"latency_ms": 30120,
"model": "example-model"
}
}
这类结构化事件可以与 ZSvirt 的基础设施告警对齐,也便于按时间窗口计算相关性。需要注意,时间接近只代表相关,不能直接证明因果。诊断结果应附带证据,例如“Pod 在超时前 20 秒被重建”和“同一 GPU 同期出现错误”,而不是只输出一个未经解释的根因标签。
可以这样实践:采集 Kubernetes 与 GPU 现场
下面的脚本可以直接运行,用于在故障发生后快速收集集群现场。运行前将 NAMESPACE 和 LABEL 改成模型服务实际使用的值;环境需要安装 kubectl,GPU 指标部分假设集群已部署 NVIDIA DCGM Exporter,没有部署时会自动跳过。
#!/usr/bin/env bash
set -euo pipefail
NAMESPACE="ai-inference"
LABEL="app=model-server"
OUT="diagnostic-$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$OUT"
kubectl get pods -n "$NAMESPACE" -l "$LABEL" -o wide \
> "$OUT/pods.txt"
kubectl get pods -n "$NAMESPACE" -l "$LABEL" -o json \
> "$OUT/pods.json"
kubectl get events -n "$NAMESPACE" --sort-by=.metadata.creationTimestamp \
> "$OUT/events.txt"
kubectl top pods -n "$NAMESPACE" -l "$LABEL" \
> "$OUT/pod-resources.txt" 2>&1 || true
kubectl get nodes -o wide \
> "$OUT/nodes.txt"
while read -r pod; do
kubectl describe pod -n "$NAMESPACE" "$pod" \
> "$OUT/describe-${pod}.txt"
kubectl logs -n "$NAMESPACE" "$pod" --all-containers --since=30m \
> "$OUT/logs-${pod}.txt" 2>&1 || true
kubectl logs -n "$NAMESPACE" "$pod" --all-containers --previous \
> "$OUT/logs-${pod}-previous.txt" 2>&1 || true
done < <(kubectl get pods -n "$NAMESPACE" -l "$LABEL" -o name | cut -d/ -f2)
kubectl get --raw '/apis/custom.metrics.k8s.io/v1beta1' \
> "$OUT/custom-metrics.json" 2>/dev/null || true
printf 'Diagnostic bundle written to %s\n' "$OUT"
这份采集包适合排查 Pod 重启、调度变化、资源压力和应用错误,但它还不能覆盖宿主机、虚拟机、vGPU 绑定、网络路径及存储延迟。实际接入时,可以把 ZSvirt 导出的对应时间窗口数据写入同一目录,并使用 host_id、vm_id、节点名和 GPU UUID 建立映射。
对于 Agent,还应在每次运行中传递统一关联标识:
import logging
import time
import uuid
logging.basicConfig(level=logging.INFO, format="%(message)s")
run_id = str(uuid.uuid4())
request_id = str(uuid.uuid4())
start = time.monotonic()
logging.info({
"event": "agent_model_call_started",
"run_id": run_id,
"request_id": request_id,
"model_service": "model-server.ai-inference.svc"
})
# 将这里替换为实际的模型 API 调用。
time.sleep(0.2)
logging.info({
"event": "agent_model_call_finished",
"run_id": run_id,
"request_id": request_id,
"latency_ms": round((time.monotonic() - start) * 1000)
})
生产环境应输出严格 JSON,而不是 Python 字典字符串,并把 request_id 继续传入模型服务的 HTTP 请求头。这样,Agent 运行记录、模型日志和底层资源事件才有机会在同一条时间线上汇合。
诊断引擎需要给出证据,而不是猜测
一个可用的诊断流程可以分成四步:先由业务异常确定时间窗口和受影响请求,再解析请求到 Pod、虚拟机、宿主机与 GPU 的资源关系;随后筛选同一关系链上的指标突变和告警;最终按时间、拓扑距离和故障规则排序候选根因。
大模型可以协助总结告警和生成排查步骤,但不应直接替代确定性的关联逻辑。资产关系、时间计算、阈值判断和权限控制更适合由程序完成;模型负责把证据组织成工程师可读的说明。把原始日志全部发送给外部模型还会带来凭据、提示词、用户数据和基础设施信息泄露风险,需要先脱敏并限制上下文范围。
落地时先打通一条最短链路
不必一开始覆盖整个智算云。可以选择“Agent 请求超时”这一类高频故障,先打通 request_id -> model service -> Pod UID -> VM ID -> host ID -> GPU UUID,并对齐各系统时钟。验收时重点检查:资源标识是否稳定、重调度后关系是否保留、告警是否包含时间范围、诊断结论是否附带原始证据,以及权限是否阻止跨租户查询。
当这条链路能够稳定回答“哪个请求受影响、它当时运行在哪里、附近发生了什么变化”时,再扩展网络、存储和 Agent 工具调用。跨层诊断的价值不在于收集最多的数据,而在于用最少但可靠的证据,把业务故障与基础设施状态连接起来。