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_ADDR=0.0.0.0:8081 - AIG_LOG_LEVEL=info - TZ=Asia/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 做可视化大盘,等跑通了我再写一篇分享。如果你在部署或规则调试时遇到什么问题,欢迎在评论区聊,我会尽量回复。