Docker部署AI-Infra-Guard:技能扫描与监控漏报复盘
1. 为什么我最终选了 Docker 来跑 AI-Infra-Guard先说背景。我做 AI 基础设施运维也有几年了手底下一堆推理服务、向量库、Agent 编排节点散落在好几台机器上。平时最怕的不是机器宕机而是某些服务悄悄降级了但监控没抓到——比如模型响应变慢、技能调用链路断了一半、某个节点注册信息过期。这类问题很阴它不报错只是让你觉得哪哪都不太对劲。AI-Infra-Guard 就是冲着这个痛点来的。它本质上是一个面向 AI 服务集群的巡检与技能扫描工具一方面帮你盯着节点的健康状态另一方面会主动向已注册的模型服务发起探测请求验证它们是否还具备该有的技能——也就是所谓的技能扫描Skill Scan。它会在你不知情的时候偷偷问模型几个问题校验回答是否达到预期从而判断模型的能力是不是退化、服务是不是被调包、路由是不是指向了错的目标。选 Docker 部署对我这种异构环境特别友好。我有 CentOS 7 的老机器也有 Ubuntu 22.04 的新机器还有一台 ARM 架构的开发板。如果直接裸装二进制依赖冲突和环境差异够我折腾一整天但 Docker 镜像只要打得出来在哪个机器上跑都是同一个行为。另外一个原因是你我都能想到的升级和回滚非常干净。镜像标签一切换容器拉起来就是新版本出问题就回退旧标签不用面对卸载残留这种恶心问题。但说句实话Docker 一键起只是入门简单真正决定这个工具好不好用的是部署前的规划和扫描规则的写法。下面我把整个部署过程拆开讲每一步都交代清楚为什么这么做。2. 一键部署前必须做对的四件事目录规划、端口、数据卷与健康检查很多人一看到docker run 一条命令就上头直接复制粘贴。我劝你先停一下把下面四件事想清楚否则后面排查问题的时候会非常被动。2.1 目录规划你的持久化数据放哪里AI-Infra-Guard 运行时会写三类数据任务执行记录数据库、扫描规则配置、扫描结果快照。如果你不做目录挂载容器一删所有历史数据跟着没了——第一次我没注意升级时把积累三个月的基线数据全丢了那种感觉不想再体验。我的做法是建立一个统一的工作目录mkdir -p /opt/aig-guard/{data,rules,snapshots} mkdir -p /opt/aig-guard/rules/custom # 自定义规则目录然后通过卷挂载的方式映射给容器。注意目录的属主和权限容器内进程一般以 uid 1000 运行所以通常这样处理chown -R 1000:1000 /opt/aig-guard不然容器启动后写数据会报 permission denied而且这类报错往往藏在日志里不仔细看根本发现不了。2.2 端口规划避开那些看起来没问题的坑AI-Infra-Guard 默认会暴露两个端口一个控制台 API默认 18080一个扫描任务回调端口默认 18081。18081 这个端口容易被忽略——它的作用是接收各目标服务回传的扫描结果。如果你把容器跑在 NAT 网络后面又不做端口映射扫描就会发出去了但收不到回包表现是任务一直卡在 running。我最终的 docker-compose 配置长这样你可以直接参考version: 3.8 services: aig-server: image: aig/ai-infra-guard:1.4.0 container_name: aig-server restart: unless-stopped ports: - 18080:8080 - 18081:8081 volumes: - /opt/aig-guard/data:/var/lib/aig/data - /opt/aig-guard/rules:/etc/aig/rules - /opt/aig-guard/snapshots:/var/lib/aig/snapshots environment: - AIG_DB_PATH/var/lib/aig/data/aig.db - AIG_RULE_DIR/etc/aig/rules - AIG_CALLBACK_ADDR0.0.0.0:8081 - AIG_LOG_LEVELinfo - TZAsia/Shanghai networks: - aig-net networks: aig-net: driver: bridge这里有几个细节点都是踩过坑才明白的TZ 环境变量必须设。不设的话容器时间默认是 UTC扫描任务的时间戳会和本地差 8 个小时你排查问题时看日志会疯掉。AIG_CALLBACK_ADDR 必须监听 0.0.0.0。如果只监听 127.0.0.1回调请求进不来。网络模式用 bridge 而不是 host。host 模式虽然性能损耗小但端口冲突概率大多套工具共存时尤其危险。2.3 健康检查别等容器挂了才发现Docker 自带的 HEALTHCHECK 指令非常实用。AI-Infra-Guard 在 8080 端口提供了一个轻量的/healthz接口返回 200 即存活。我在 compose 文件里加上healthcheck: test: [CMD, curl, -fs, http://localhost:8080/healthz] interval: 30s timeout: 5s retries: 3 start_period: 20s加了这个之后你可以随时用docker inspect --format{{json .State.Health}} aig-server查看容器健康状态。虽然没有它也不影响运行但配合脚本做自愈时非常有用。2.4 日志保留策略AI-Infra-Guard 扫描频率高了以后日志增长很快。默认情况下 Docker 的 json-file 日志驱动不做滚动跑一晚上就能占你几个 GB。建议在/etc/docker/daemon.json里加个全局限制{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }改完执行systemctl restart docker。这一步不做后面追查问题时会发现日志文件被撑爆老记录全被清掉了那就真是叫天天不应。3. 技能扫描的正确打开方式目标注册、任务编排与结果解读部署成功只是第一步真正有技术含量的是把技能扫描配置好。这部分我拿一个实际案例来讲读者可以对照自己的环境改改就能用。3.1 目标注册不是填个 URL 就完事AI-Infra-Guard 的扫描目标Target需要填三类信息名称、类型、端点地址。类型决定了扫描器用什么协议去探测比如openai-compatible表示兼容 OpenAI 的 Chat Completions 接口ollama表示 Ollama 本地服务agent-runtime表示 Agent 执行环境。我第一次注册目标时就犯了个错把所有服务都填成openai-compatible。结果 Ollama 那个目标扫描一直是超时失败因为 Ollama 原生接口的路径和 OpenAI 不完全一致。所以填类型一定要跟实际服务匹配。官方支持的类型和对应端点路径我整理了一下类型端点要求适用场景openai-compatible/v1/chat/completions大多数网关、代理、vLLM 等ollama/api/chat本地 Ollama 服务agent-runtime/agent/invoke自研 Agent 执行器embedding/v1/embeddings向量化服务健康检查注册可以通过控制台 API 完成用 curl 就行curl -X POST http://localhost:18080/api/v1/targets \ -H Content-Type: application/json \ -d { name: deepseek-r1-01, type: openai-compatible, endpoint: http://10.0.0.10:8081/v1, models: [deepseek-r1], timeout: 30, interval: 300 }3.2 技能探测规则的编写逻辑技能扫描的核心是探测任务向目标模型发送一组精心设计的问题然后校验回答是否符合预期。规则文件放在挂载目录的rules/custom下AI-Infra-Guard 会定期加载。举个例子我要验证模型是否还具备代码生成和数学推理两项技能probes: - id: probe-code-001 skill: code-generation prompt: 请用 Python 写一个快速排序函数只输出代码 expect: contains: [def quick_sort, pivot] not_contains: [抱歉, 无法] min_length: 50 - id: probe-math-002 skill: math-reasoning prompt: 17 乘以 23 等于多少只回答数字 expect: regex: ^391$ timeout: 15这里注意expect的匹配规则AI-Infra-Guard 支持三种contains包含匹配、regex正则匹配、similarity语义相似度匹配。建议每种技能至少配 2-3 条探测避免单条探测太容易蒙混过关或者太严格导致误杀。3.3 扫描结果的状态机与打分阈值扫描完成后每个探测任务会落到三种状态pass通过、fail不通过、error执行出错。注意error和fail含义完全不同fail代表模型回答不符合预期多半是技能退化了error代表探测请求本身没送出去或没收到回包往往是网络、超时或端点配置的问题。AI-Infra-Guard 的聚合策略是同一技能下若 fail 数超过 50% 或 error 数超过 30%则判定该目标该技能异常。所以我每次看结果都先看明细而不是只看总体的绿/红状态——因为一个目标的技能 A 挂了技能 B 还是好的总体状态可能仍然显示黄色容易麻痹人。4. 漏报了复盘一次技能扫描全部通过却实际故障的完整排查链路这是本文我最想分享的部分。某天下午业务同事反馈说生产环境的一个对话 Agent 总是答非所问但我打开 AI-Infra-Guard 控制台技能扫描显示近两小时全部绿色——所有目标、所有技能都是 pass。这就尴尬了工具说没事可用户明明在用脚投票。4.1 第一步先确认扫描任务真的执行了我的第一反应是怀疑扫描任务压根没跑。于是查了任务执行记录docker logs aig-server --since 2h | grep scan_task日志显示任务确实在触发时间也对得上每个目标都有执行记录。那就说明不是调度问题问题大概率出在探测目标和真实服务之间出现了偏差。4.2 第二步检查探测请求实际打到了哪里我挑了一个失败概率最高的 Agent 目标手动触发一次即时扫描然后到目标服务侧抓包tcpdump -i eth0 port 8081 -w /tmp/aig-probe.pcap抓包结果让我心里一凉探测请求确实进来了但进来的源 IP 是172.18.0.0/16网段——这是 Docker 内部网络的地址。也就是说从容器发出的探测请求经过我的 Nginx 网关后被路由到了另一台健康实例而不是真正出问题的那个实例。进一步查 Nginx 配置才发现这个 Agent 节点对应的 upstream 配的是一个负载均衡池池里有新旧两个实例。老的实例也就是出问题的那台健康检查失败后Nginx 自动把它摘掉了但 AI-Infra-Guard 注册的目标端点指向的是负载均衡地址而不是具体实例地址。这导致扫描请求全部被转发到健康的实例工具自然永远显示绿色。4.3 第三步验证探测问题本身有没有变味排除了路由问题后我又怀疑另一个可能探测请求到达健康实例但这个实例是个 Mock 服务对所有问题都返回预设答案。于是我手工把探测的 prompt 发到那个健康实例curl -X POST http://healthy-instance:8081/v1/chat/completions \ -H Content-Type: application/json \ -d {model:agent-x,messages:[{role:user,content:17 乘以 23 等于多少只回答数字}]}返回结果是391完美命中预期。再看看真实出问题的老实例同样的问题它返回的是 根据上下文结果可能是 390 或 391。——语义正确但不符合regex: ^391$。这里就暴露了两个问题目标端点指向负载均衡扫描永远只探测到健康节点漏掉真实故障节点探测规则太理想化生产模型输出不像测试环境那样干净带解释性前缀很正常。4.4 第四步挖出静默跳过的隐藏坑排查还没结束。我在规则配置里看到自己写了一段目标匹配逻辑是根据模型名称的正则来选择探测任务的target_filter: model_pattern: agent-x-v[0-9]问题就在这。生产环境的老实例升级后模型名从agent-x-v1变成了agent-x-v2-beta这个-beta后缀没被正则匹配上。AI-Infra-Guard 在过滤规则时的处理逻辑是匹配不到模型时不会报错只会静默跳过该目标的探测。所以扫描看起来一直在执行实际上对这个目标什么都没做。这是我这次漏报最核心的根因——不是工具没能力而是我配置的正则把目标悄悄排除掉了。5. 复盘后的补救动作地址收敛、规则容错与监控兜底找到根因之后我做了四件补救事情。每件都很简单但组合起来能避免下次再踩同样的坑。5.1 目标注册全面收敛到实例粒度把所有扫描目标的端点从负载均衡地址改为具体实例的 IP:Port负载均衡的可用性监控交给另一套探活系统AI-Infra-Guard 只负责逐实例技能验证。这样每个实例都会被真实探测到谁退化一目了然。如果你的实例是动态扩缩容的建议写个脚本在服务注册中心发现新实例时自动调用 AI-Infra-Guard 的 API 注册目标。# 简易脚本发现新实例并注册 for ip in $(curl -s http://consul:8500/v1/health/service/agent-x | jq -r .[].Service.Address); do curl -X POST http://localhost:18080/api/v1/targets \ -H Content-Type: application/json \ -d {\name\:\agent-x-$ip\,\type\:\agent-runtime\,\endpoint\:\http://$ip:8081\} done5.2 探测规则从严格匹配改为分级容错把正则匹配改成多级判断。比如数学类问题不再要求^391$这种完全匹配而是先用contains: 391粗筛再用语义相似度兜底。我把规则改成了这样probes: - id: probe-math-002-rev2 skill: math-reasoning prompt: 17 乘以 23 等于多少只回答数字 expect: contains: [391] fallback: similarity: 0.85 reference: 17乘以23的结果是391 timeout: 15这样既不会因为模型多解释了一句就误报也不会因为模糊匹配放过真正能力退化的模型。建议你把contains里的关键词尽量选成这段回答里必须出现的核心信息而不是整句话必须等于。5.3 加一个零探测告警既然 AI-Infra-Guard 会静默跳过未匹配模型的目标那我就在外面加一道保险扫描任务执行后如果某个已注册目标在 N 个周期内没有任何探测记录立刻触发告警。实现方式很粗暴但有效定时任务读数据库统计每个 target 最近 6 小时的成功探测次数#!/bin/bash # check-silent-targets.sh docker exec aig-server sqlite3 /var/lib/aig/data/aig.db \ SELECT target_id, count(*) FROM probe_records WHERE created_at datetime(now,-6 hours) GROUP BY target_id; \ /tmp/aig-probe-count.txt while read target_id count; do if [ $count -eq 0 ]; then echo target $target_id has zero probes in 6h, alerting... # 接入你的告警通道 fi done /tmp/aig-probe-count.txt从此以后任何以为扫了其实没扫的情况都会在 30 分钟内被我发现不会再等到业务投诉才后知后觉。5.4 把漏报复盘沉淀成配置审查清单最后我把这次教训整理成一份部署后自检清单每次新增目标都按这个过一遍检查项命令/方法通过标准目标端点是否实例级查看 target 配置不含 LB/VIP 地址模型名正则是否覆盖所有版本手工模拟匹配新版本名能命中 pattern探测结果是否有 error 或 skip查询最近任务明细无静默跳过记录规则是否过于严格用真实输出回放匹配成功或触发 fallback容器时间与本地一致date对比时区偏差为 0写在最后这次漏报复盘让我彻底改变了对监控类工具的态度工具只能保证你配置范围内的侦查覆盖不到的地方就是盲区。AI-Infra-Guard 本身是个好工具但我自己配置不当导致扫描目标被静默跳过这个教训很有代表性。如果你也准备在本地环境部署这套工具我的建议是先把第 2 节约部署准备做扎实再按第 3 节的思路注册目标、配规则上线前务必跑一遍第 5 节的自检清单。特别是那个零探测告警脚本千万别省——因为配置错误造成的静默跳过比工具本身的故障难发现得多。后续我还在尝试把 AI-Infra-Guard 的扫描结果接入到 Grafana 做可视化大盘等跑通了我再写一篇分享。如果你在部署或规则调试时遇到什么问题欢迎在评论区聊我会尽量回复。