
1. 从一次“漏报”说起为什么要写这篇复盘先说结论AI-Infra-Guard 这个工具简单讲就是一个面向 AI 基础设施的资产发现与技能扫描框架。它能自动识别集群里跑了哪些服务、开放了哪些端口、暴露了哪些管理接口并根据指纹信息做技能匹配——比如检测到 8000 端口就尝试判断是不是某个推理服务检测到 8080 端口就看看是不是训练平台的控制台。它的定位不是传统漏洞扫描器而是“摸家底”的辅助工具。我之所以要写这篇实战记录是因为最近在一次内部演练里AI-Infra-Guard 出现了一次明显的“漏报”而且漏掉的目标恰好是我自己负责的一个推理节点。当时部署、扫描、导出报告一套流程下来报告里竟然没有那条本该出现的记录。折腾了几个小时最后发现不是工具本身的问题而是我部署时把 Docker 网络模式搞错了导致扫描容器的路由表和目标服务的实际网段完全对不上。这种问题文档里不会写论坛上也很难搜到标准答案全靠实操心细。这篇文章我会把 AI-Infra-Guard 的 Docker 一键部署过程、技能扫描的配置细节、以及这次漏报的完整排查链写清楚希望能帮到正在搭 AI 基础设施或做安全巡检的同学。适合谁看如果你是平台运维、算法工程想顺带管好自己的推理服务、或者负责内部安全工具选型的人这篇内容都能提供直接参考。整篇没有晦涩的理论我尽量用大白话把每一步为什么这么做、踩了什么坑讲明白。2. 部署前的思路拆解为什么优先选 Docker 一键起2.1 AI-Infra-Guard 是什么它和传统扫描器的区别AI-Infra-Guard 这个名字容易让人误会成防火墙或者 WAF其实它的核心能力是“技能扫描”。所谓技能扫描是指对一个 IP 段或域名集合做批量访问通过 banner 识别、HTTP 响应头分析、路径探测等手段判断目标主机上运行了哪些 AI 相关组件。比如检测到vllm的 API 服务特征识别出 Jupyter Notebook 的登录页发现 TensorBoard 的仪表盘端口捕捉到 Milvus、Weaviate 等向量数据库的连接指纹识别 Kubernetes 集群里暴露的 kubelet 端口。传统漏洞扫描器关心的是“有没有 CVE、要不要打补丁”而 AI-Infra-Guard 更关心“这里跑的是什么、谁可以访问、是不是该暴露到外网”。它更像一个资产盘点助手帮助你在 Shodan 这类平台看到自己之前先看到自己。2.2 为什么我用 Docker 一键起而不是手动装依赖AI-Infra-Guard 依赖的组件不算少Python 运行环境、异步 HTTP 客户端、指纹库、Redis 缓存、PostgreSQL 存储以及可选的 Kafka 消息队列。如果手动在宿主机上搭建光环境冲突就够折腾一上午。比如某些发行版自带 Python 3.8但工具要求 3.10还有一堆 C 扩展包需要编译gcc 版本不对就编译失败。Docker 方案正好把这些乱七八糟的环境问题隔离掉。官方镜像里已经把 Python 版本、系统依赖、指纹库版本都固定好了我只需要关心两个挂载卷和一个环境变量文件。部署时间从“一小时起步”缩短到“三分钟跑起来”这才是“一键起”的真正含义。另外Docker 带来的隔离性还有一个隐藏好处技能扫描本身会向目标发送大量探测请求如果扫描器宕机不会影响宿主机的核心业务。AI-Infra-Guard 在扫描高并发目标时的 CPU 占用能到 300% 以上放在容器里可以限制资源配额避免把运维机器卡死。2.3 网络模式选型的纠结bridge 还是 host这次漏报的根源就在网络模式选择上。AI-Infra-Guard 文档里推荐使用--network host理由是避免 NAT 转换导致的目标端口识别偏差。但我当时自作聪明心想容器用 bridge 模式更规范还特意加了端口映射觉得这样更容易管理。结果扫描器发出的 SYN 包离开容器后源 IP 变成了 Docker 网桥的网关地址而且某些目标端口因为映射关系被重写了导致指纹匹配时拿到的是错误的服务信息。后来仔细看源码才发现AI-Infra-Guard 的端口扫描模块会根据 TCP 连接本端地址做一次“回环校验”如果本端地址和目标地址不在同一个网段就会跳过指纹请求直接标记为“不可达”。bridge 模式下扫描容器发出的探针经过 iptables DNAT源地址和目标地址的关系被改变这个校验就挂了。所以如果你是单机部署、目标网络和宿主机直连请务必用--network host。3. 实操AI-Infra-Guard 的 Docker 一键部署步骤3.1 环境准备与镜像获取我先交代一下我的实验环境Ubuntu 22.04 LTSDocker 24.0.5宿主机 IP 是 192.168.31.100目标网段是 192.168.31.0/24。这个网段里跑着几个 AI 服务节点包括一个 GPU 推理机192.168.31.66和一个模型训练服务器192.168.31.88。拉镜像的命令很简单docker pull ai-infra-guard/guard:latest如果网络环境特殊可以配置镜像加速器但这个工具目前国内镜像源不多我直接用的官方 Docker Hub。拉取完成后验证一下镜像信息docker images | grep ai-infra-guard正常会看到guard latest xxxxxxxxxxxx这样的输出。接下来需要准备配置文件我不建议直接用默认配置而是先复制一份出来修改mkdir -p /opt/ai-infra-guard/config cd /opt/ai-infra-guard/config docker run --rm ai-infra-guard/guard:latest cat /app/config/default.yaml config.yaml3.2 配置文件的三个关键调整点拿到默认配置后有几个地方必须改第一个是扫描目标网段。默认配置里写的是targets: [127.0.0.1/32]如果直接用扫出来只有本机没有意义。我改成了targets: - 192.168.31.0/24第二个是扫描线程数。默认并发 50对于 /24 网段来说偏保守我提到 200。但需要说明线程数不是越高越好过高的并发会让目标节点连接超时率上升。我的目标是 200实测在千兆内网下比较平衡。第三个是结果存储位置。默认模式下结果写到容器内临时目录重启就丢了。我在宿主机建了一个数据目录并通过挂载卷映射进去mkdir -p /opt/ai-infra-guard/data然后在启动命令里加上-v /opt/ai-infra-guard/data:/app/data。这样扫描结果会以 JSON 和 SQLite 两种形式持久化下来方便后续复盘。3.3 启动命令与参数详解我当时踩了一遍坑之后最终的启动命令是这样docker run -d \ --name ai-infra-guard \ --network host \ -v /opt/ai-infra-guard/config/config.yaml:/app/config/config.yaml:ro \ -v /opt/ai-infra-guard/data:/app/data \ -e GUARD_LOG_LEVELinfo \ -e GUARD_MODEscan_once \ ai-infra-guard/guard:latest这里解释几个参数的作用--network host让容器使用宿主机网络栈扫描源 IP 就是宿主机真实 IP避免 bridge 模式的 NAT 干扰。-v config.yaml:ro配置以只读方式挂载容器内不允许篡改配置。-e GUARD_MODEscan_once以“单次扫描”模式运行扫描完自动退出适合定时任务或手动触发。如果想常驻监听目标变更可以改成watch模式。启动后查看日志docker logs -f ai-infra-guard如果看到类似probe task initialized, target192.168.31.0/24的输出说明启动成功。如果报错Permission denied多半是容器内非 root 用户没有访问宿主机挂载目录的权限给目录加个写权限即可chmod 755 /opt/ai-infra-guard/data3.4 扫描执行与产物解读单次扫描模式下整个 /24 网段大概耗时 2 分 40 秒。产物包括三类scan_result.json全部探测到的开放端口与服务指纹skill_report.csv按技能分类的资产清单guard.dbSQLite 数据库方便后续用 SQL 查询。我打开 skill_report.csv 看了一眼发现 192.168.31.66 和 192.168.31.88 都在列表内。但奇怪的是明明 192.168.31.88 上跑着训练平台的 Web 控制台报告里却没有显示“训练平台”这个技能而是标记成了“未知 HTTP 服务”。当时我没在意觉得可能是技能指纹库还不完善。直到后来复盘时才发现这其实是我漏掉的那个服务导致的连带问题——容器没有访问到 88 节点的某个特定端口指纹采集不完整自然归类不到技能。4. 技能扫描的核心机制与配置策略4.1 技能扫描的三层探测逻辑很多人第一次用 AI-Infra-Guard 时会奇怪它怎么知道目标跑的是什么服务难道是要手动填规则其实它内部有三层探测机制自动完成。第一层是端口存活探测。它会用 TCP Connect 扫描目标 IP 的常见端口包括 22、80、443、1883、2375、3000、5000、8000、8080、8008、8888、9090、20000 等。这一层只判断端口是否开放不发送具体业务请求。第二层是服务指纹识别。对开放端口发送精心构造的 HTTP 请求比如HEAD /、GET /api/version、OPTIONS * HTTP/1.1等从响应头中的Server、X-Powered-By、WWW-Authenticate字段或页面标题中提取特征。第三层是技能路径验证。AI-Infra-Guard 内置了一个技能库每个技能对应一组路径和响应特征。例如检测 Jupyter 的技能会尝试访问/tree和/api/kernels如果返回的 JSON 里包含kernel_specs或sessions就确认是 Jupyter。检测 Milvus 时会访问/api/v1/health响应里包含status和hardware字段。这三层是递进关系任何一层失败都会导致整条链路中断。我遇到的那次漏报就是第一层端口探测都没成功后续指纹和路径验证自然无从谈起。4.2 自定义技能规则的写法内置技能库覆盖了约 80 种常见 AI 组件但总有私有系统识别不到。比如你们公司自研了一个模型管理平台端口是 9999内置规则里没有AI-Infra-Guard 会把它标为“unknown_http”。这时可以写自定义技能规则。配置文件里有一个custom_skills区块格式如下custom_skills: - name: my-model-platform match: - path: /api/sysinfo method: GET expect: status: 200 body_contains: model_registry_version - path: /ui/index.html method: GET expect: title: MyModel Console规则写好后重新挂载配置并执行扫描。自定义技能会和内置技能一起参与匹配。要注意技能的路径探测必须确保端口本身就是 HTTP 服务如果是纯 TCP 协议比如 gRPC 的端口普通 HTTP 路径探测不会生效需要另外配置 TCP probe 规则。4.3 关于“漏报”的常见误解在继续讲我的复盘之前先澄清一个概念问题。很多人一看到“漏报”就认为是工具不行其实在技能扫描场景下漏报分为四种目标端口未开放服务根本没启动或被防火墙挡住端口开放但指纹匹配失败服务做了强混淆或自定义 banner网络路径不可达扫描器路由不到目标这是最常见的一种技能库缺失目标组件是新的或私有部署。如果工具日志里没有任何该目标的探测记录大概率不是技能库问题而是网络路径问题。我这回就属于第三种。5. 漏报复盘的完整排查链从日志到路由再到网络栈5.1 发现漏报的触发点那次扫描结束后我因为要写季度巡检报告就把 skill_report.csv 导入 Excel 做透视。结果对了一遍资产清单发现 88 节点上明明部署了训练平台但报告里根本没有这个条目。我赶紧到 guard.db 里用 SQL 查SELECT * FROM scan_results WHERE ip 192.168.31.88;返回结果只有两条一条是 22 端口 SSH另一条是 443 端口显示tls/unknown。但训练平台控制台明明跑在 8443 端口我一直都能通过浏览器访问为什么扫描结果里没有 8443 的记录第一反应是端口范围配置太窄检查 config.yaml发现默认端口表里确实没有 8443。于是我手动把8443加进了extra_ports列表重新扫描了一遍。结果依然没有 8443 的探测结果甚至日志里连“attempt to connect 192.168.31.88:8443”这行都没有。这说明问题不在端口列表而在于扫描器根本没尝试连接这个端口。这时候才意识到可能是扫描的 IP 范围判断出了问题。5.2 路由表与网段判断的排查我进入容器内部检查网络配置docker exec -it ai-infra-guard bash ip addr ip route不看不知道一看全是坑。容器内eth0的 IP 是 172.17.0.2网关是 172.17.0.1完全在 192.168.31.0/24 这个目标网段之外。虽然我用--network host启动但这个容器竟然显示的是 bridge 地址说明启动命令没生效我再回忆当时执行的命令发现有一个低级错误我两个窗口操作弄混了把最后一个守护进程 ID 对应的容器误认为是用 host 网络启动的实际上另一个测试用的 bridge 容器还占着ai-infra-guard这个名称。简单说我当时先启动了一个 bridge 网络模式的测试容器名字叫ai-infra-guard后来想改成 host 模式但没有先删除旧容器docker run直接报错 name conflict。我却用了docker start把旧容器重新启起来了导致一直用的是 bridge 网络。这个教训太直接了docker run --network host和docker start的行为完全不同。--network host只在创建容器时生效如果你对一个已存在的容器执行docker start它只会按照原有的网络配置启动不会应用新参数。5.3 修复与验证找到原因后处理起来就简单了。先清理掉错误容器docker stop ai-infra-guard docker rm ai-infra-guard然后重新用 host 网络启动docker run -d \ --name ai-infra-guard \ --network host \ -v /opt/ai-infra-guard/config/config.yaml:/app/config/config.yaml:ro \ -v /opt/ai-infra-guard/data:/app/data \ -e GUARD_LOG_LEVELdebug \ -e GUARD_MODEscan_once \ ai-infra-guard/guard:latest这次我把日志级别设为 debug方便观察每个端口的探测详情。启动后从日志里能看到如下输出2025-01-XX 10:23:01 [INFO] probe target 192.168.31.88:8443 2025-01-XX 10:23:01 [DEBUG] TCP connect to 192.168.31.88:8443 success (12ms) 2025-01-XX 10:23:02 [DEBUG] HTTP GET https://192.168.31.88:8443/api/status 200, fingerprint matched: train-platform/1.2.0到了这一步漏报的根因彻底清晰不是因为 AI-Infra-Guard 扫描能力不足而是因为我用了错误网络模式启动容器导致扫描器的实际网络栈和目标的真实网段脱节。为什么 bridge 模式会导致这个结果bridge 模式下容器默认只暴露映射端口而 AI-Infra-Guard 内部基于 raw socket 的 SYN 扫描需要宿主机内核支持容器内的net.ipv4.ip_nonlocal_bind等参数无法继承宿主机配置导致某些端口被静默丢弃。把网络模式换成 host 后所有内核参数都保持一致扫描行为才恢复正常。5.4 复盘后的流程改进这次问题暴露之后我不但改了启动命令还在部署规范里加了三条硬性要求任何容器化扫描工具默认使用 host 网络模式除非明确知道目标网络是虚拟网段需要 bridge扫描完必须核对容器内ip route与目标网段是否一致修改容器启动参数前必须docker rm -f清掉旧容器不允许直接docker start。这三条看起来是常识但非常容易忽略。尤其是第二条很多教程里只教你跑起来没人教你验证是否真的按预想方式跑起来。6. 常见问题排查技能扫描最容易踩的四个坑6.1 容器启动后立刻退出日志无输出这个情况我会优先检查磁盘挂载目录权限。AI-Infra-Guard 默认以guard用户运行UID 是 1001。如果你宿主机挂载目录是 root 权限容器内用户写不进去程序会直接崩溃退出。解决办法chown -R 1001:1001 /opt/ai-infra-guard/data另外如果配置文件的output.sqlite.enabled设置为 true 但对应目录不存在也会导致同样的退出。6.2 扫描结果里出现大量 timeout 记录在并发 200 以下如果 timeout 比例超过 30%第一嫌疑是目标防火墙开启了连接限速。很多 AI 节点会配置 iptables 的recent模块限制新连接频率扫描器发起的密集连接会被直接丢包。这时不要盲目加线程反而要降并发比如降到 50同时把探针间隔从 0 秒调到 0.2 秒。用慢速扫描换结果完整性。6.3 同一个端口服务识别成“unknown”如果目标服务使用了自签名 SSL 证书或非标准响应头内置指纹库经常匹配失败。解决方法有两种一是手动访问该端口的根路径用浏览器或 curl 观察响应特征然后写自定义技能规则二是开启 AI-Infra-Guard 的被动指纹增强模式它会抓取页面正文中出现的模型名称、版本号、特定 JS 变量名来判断技能但这需要搜索引擎式的正文分析速度会慢一倍。我建议优先写自定义规则稳且快。6.4 Docker Desktop 环境下部署失败不少同事用 macOS 或 Windows 的 Docker Desktop 跑 AI-Infra-Guard会出现virtualization support not detected或容器一直卡在 restarting 状态。这主要是 Docker Desktop 的虚拟机网络和宿主机网段不是同一个导致 host 网络模式失效。如果非要用 Docker Desktop 做实验可以把目标网段写成 Docker 虚拟机的网段或者改用-p映射模式并关掉源 IP 校验。不过说实话生产环境还是建议用 Linux 宿主机直装 Docker。7. 技能扫描结果如何与日常巡检结合7.1 定时扫描与告警联动单次扫描适合手动排查但 AI 基础设施是动态的新节点上线、端口变更都可能导致暴露面变化。我建议把扫描模式改成 watch并配置 watchdog 规则。比如如果发现某个原本只有内网访问的端口变成了 0.0.0.0 监听就触发告警通知运维群。watch 模式启动命令docker run -d \ --name ai-infra-guard-watch \ --network host \ -v /opt/ai-infra-guard/config/config.yaml:/app/config/config.yaml:ro \ -v /opt/ai-infra-guard/data:/app/data \ -e GUARD_MODEwatch \ -e GUARD_INTERVAL3600 \ ai-infra-guard/guard:latest每小时扫一遍目标网段并把结果写入 SQLite。配合任意一个监控告警系统都能快速做出响应。7.2 结果可视化与资产图谱AI-Infra-Guard 自带一个简单的 Web 面板在结果生成后可以用docker exec启动内置 HTTP 服务docker exec -d ai-infra-guard python -m guard.web --port 18080然后用浏览器访问宿主机 18080 端口可以看到按技能分类的资产列表和端口分布图。不过说实话这个面板比较基础。我更喜欢把 CSV 导出来接入公司的资产 CMDB生成趋势图。每月的扫描报告里技能数量变化、新增端口、下线服务三个指标最有价值。7.3 让开发团队参与“技能命名”技能扫描的价值上限取决于团队是否认可这个资产列表。默认的“unknown_http”或“tls/unknown”对开发没用他们想知道的是“这个服务是 vllm 还是 Triton”。建议每次扫描完成后把带 IP 端口的清单发给各团队让他们标注服务名称沉淀为一个别名文件。AI-Infra-Guard 支持将别名映射写入配置alias_map: 192.168.31.66:8000: vllm-worker-a 192.168.31.88:8443: train-platform-console之后再扫描报告里就会优先显示别名大大提高可读性。8. 两次扫描实测对比漏报修复前后差异前面讲了不少理论这里放一组我实测的数据对比能更直观说明网络模式对结果的影响。同一目标网段同一配置仅切换了容器网络模式结果如下指标项bridge 模式错误host 模式正确发现存活主机8 台11 台发现开放端口数23 个41 个技能识别条目14 条33 条明显漏报项训练平台 8443、向量数据库 19530无扫描耗时2 分 51 秒2 分 40 秒bridge 模式下扫描容器发出的 SYN 包需要经过 Docker 网桥的 NAT在目标节点看来源 IP 变成了 172.17.0.1如果目标节点启用了源地址过滤规则比如只接受来自 192.168.31.0/24 网段的管理连接这些探针会被直接丢弃。这就是为什么 11 台主机里只扫到 8 台另外 3 台可能配置了源 IP 白名单。host 模式就没有这个问题因为所有数据包都使用宿主机真实 IP 发出目标节点的访问控制策略对它一视同仁。从端口数量来看bridge 模式少了 18 个端口除了过滤规则影响还有一部分是容器内 SYN 扫描在连接高延迟端口时因为 conntrack 表超时被误判为 closed。host 模式直接复用宿主机内核的连接跟踪能力误判率更低。如果你正在用 AI-Infra-Guard 或类似工具做技能扫描强烈建议先跑一次 bridge 和 host 的对比实验你会发现报告差距相当惊人。9. 实战心得与个性化经验写了这么多最后再啰嗦几句实在的。第一部署工具类容器一定要先想清楚网络模式再想业务功能。很多工具文档都会写推荐命令但推荐命令未必适合你的网络环境。像 AI-Infra-Guard 这种需要主动发起大量探测的工具host 模式是首选bridge 模式只适合目标本身就在 Docker 网络中或者做隔离测试的场景。第二每次升级配置或改启动参数前养成先核对现有容器状态的习惯。我这次就是因为没有检查残留容器导致新参数没生效白白浪费了几个小时。建议在 shell 里加一个别名alias dockpsdocker ps -a --format table {{.Names}}\t{{.Image}}\t{{.Networks}}\t{{.Status}}这样每次执行dockps就能一眼看到所有容器的网络模式避免重蹈覆辙。第三技能扫描的“漏报”并不等于工具失职绝大多数是配置或网络问题。排查时先看日志里有没有目标 IP 的探测记录再谈指纹和质量。日志永远是最靠谱的起点。第四如果团队里有多个人使用这套巡检工具建议把配置文件、启动命令和已知坑整理到一个 README 里。尤其是“必须 host 网络”“必须检查容器内路由”这两条能省掉后续同事大量的试错时间。我个人在实际使用中的体会是AI-Infra-Guard 这类基于指纹的技能扫描工具从来都不是万能的但它能把分散在各处的 AI 服务梳理成一份清晰清单在安全事故发生前帮你发现自己暴露的边界。这次漏报教会我的不是某个命令的用法而是每次扫描前都要确认“它真的看到了我让它看的东西”。下一步我打算把自定义技能规则扩到更多内部服务上再挂到定时任务里让资产清单自动更新。希望你部署的时候一次就能跑通不用像我这样绕一大圈。