☰
从Docker部署到漏报复盘:AI-Infra-Guard技能扫描实践
2026/9/29 5:33:52 网站建设 项目流程

前阵子公司AI平台组的GPU服务器从两台扩到十几台,模型服务也从一个变成七八个,运维靠人肉巡检已经明显忙不过来了。我当时的想法很简单:能不能找一个能自动盘点AI基础设施、定期做体检的工具,把所有集群节点、推理服务、向量库、依赖库版本一次性扫清楚,并且能出带风险等级的报表?这就是我接触AI-Infra-Guard的初衷。

AI-Infra-Guard本身是个面向AI基础设施的扫描与守护工具,核心功能是技能扫描——它不扫漏洞特征库,而是对AI底座的能力项做体检,比如GPU驱动与CUDA是否匹配、推理框架版本是否合规、模型服务是否真的能响应推理请求、向量数据库连接池是否健康、API密钥权限是否越界等等。部署方式走Docker一键起,配置都在一个YAML里,拉完镜像起来就能用。这篇就从头到尾讲一遍部署过程,再复盘一次让我印象深刻的漏报事故。

1. 我为什么需要“技能扫描”,而不是继续写Shell脚本

1.1 AI基础设施体检,和传统安全扫描不是一回事

先说说我遇到的具体场景。每周都要维护GPU集群的依赖清单,包括NVIDIA驱动、CUDA版本、cuDNN、PyTorch、vLLM、DeepSpeed、向量数据库实例、Ray集群状态等。过去我用Shell脚本加定时任务,每个节点放一个脚本,结果发现三个问题:

第一个问题是脚本碎片化。GPU的脚本只查nvidia-smi,模型服务的脚本只测TCP端口,两个脚本各扫各的,没人把它们汇总成一份全局风险列表。出了事你得一个个脚本去看,排查链路特别长。

第二个问题是覆盖滞后。新增的模型服务不会自动进扫描范围,得手动改脚本参数,漏掉是常态。尤其现在AI服务迭代这么快,今天上一个推理服务,明天换一个向量库,脚本根本追不上资产变更的速度。

第三个问题是判定太浅。很多检查停留在“进程活着”“端口能通”,但进程活着不代表功能可用。比如Triton Inference Server的端口能通,但模型可能还在加载中,这时候接流量必炸;再比如Redis容器起来了,但主从同步断了,表面看端口通、进程在,实际已经失去高可用能力。

AI-Infra-Guard解决的正是这三个问题。它的技能扫描思路是:先把资产发现出来(节点、容器、服务),再对每个资产生成一份“技能清单”(这个节点该具备哪些能力),然后逐项探测能力是否真的可用。这跟传统漏洞扫描的“特征库匹配”思路是两条路线,前者关注基础设施的底座能力,后者关注已知漏洞,两者互补但不可互相替代。

1.2 为什么最后选了Docker方式部署

其实一开始考虑过裸机安装,直接pip install一份Python写的Agent。但第一批要管理的节点就有Linux、Windows混合环境,还涉及不同的Python版本和依赖冲突。Docker在这个场景下的优势非常明显:镜像把Python解释器、依赖库版本、探针脚本全封装好,宿主机只需要有Docker引擎和GPU驱动(如果是GPU节点)。我在云服务器、物理机、GPU卡机上都用同一个镜像跑通了,完全不用管宿主机上是Python 3.8还是3.10,这是裸机部署比不了的。

另外一个关键点是:扫描任务需要定时执行和结果持久化,数据要落库、出报表。我把PostgreSQL和Redis作为依赖服务一起放进docker-compose里,一次起三个容器,编排、重启、日志查看都比手动管理进程省心太多。成本也很低,一台2C4G的轻量云服务器就能跑管理端,扫描任务分发到各节点所在的网络去执行,控制面和数据面分离得很干净。

1.3 技能扫描的数据模型:资产清单与技能基线

部署之前有必要理解AI-Infra-Guard内部怎么组织数据,因为后面配规则、看漏报都跟这个模型直接相关。工具维护了两类核心数据:

  • 资产清单:扫描代码自动发现的节点、Docker容器、模型服务、数据库实例,每条资产有唯一ID。它也可以对接静态文件里的IP列表做补充发现。
  • 技能基线:每种资产对应一组“技能项”,比如GPU节点必须具备“驱动可加载”“CUDA上下文可创建”“显存余量充足”三项;模型服务必须具备“HTTP接口可访问”“推理请求200返回”“模型加载完成”三项。

扫描引擎做的就是“资产清单 × 技能基线”的匹配,逐项执行探针。这一步在配规则的时候怎么强调都不过分:漏报往往不是探针写错了,而是资产清单漏了,或者基线技能项定义得太浅。我在后续复盘漏报时,两个坑都踩到了。

2. Docker部署实操:从准备环境到跑通首次扫描

2.1 环境准备与前置检查清单

我部署的这台机器是Ubuntu 22.04 LTS,内存32GB,磁盘200GB,没装GPU,所以这台只负责调度和扫描,GPU节点通过SSH远程探测的方式纳管。

先确认Docker环境:

docker --version docker compose version

如果机器上Docker没装好,工具起不来,后续都是空谈。我建议先跑一遍hello-world确认引擎正常:

docker run --rm hello-world

再确认两个端口没被占用:8890(Web控制台)、8891(API)。

我把整个工具的数据目录规划成这样:

/opt/ai-infra-guard/ ├── config/ │ ├── guard.yaml # 主配置文件 │ ├── skills/ │ │ ├── gpu-basics.yaml │ │ ├── model-service.yaml │ │ └── vector-db.yaml │ └── assets/ │ └── static-assets.txt ├── data/ # PostgreSQL数据持久化 └── logs/ # 扫描日志、审计日志

数据目录必须挂载到宿主机,否则容器一重建,历史扫描结果就没了,这个坑我踩过一次,后面详说。

2.2 docker-compose配置的三个关键点

我用docker-compose管理三个服务:ai-infra-guard主程序、PostgreSQL(结果库)、Redis(任务队列)。下面是我最终跑通的完整配置:

version: "3.8" services: postgres: image: postgres:15-alpine container_name: aig-postgres environment: POSTGRES_USER: aig POSTGRES_PASSWORD: change-me POSTGRES_DB: aig volumes: - /opt/ai-infra-guard/data/pg:/var/lib/postgresql/data networks: - aig-net restart: unless-stopped redis: image: redis:7-alpine container_name: aig-redis command: ["redis-server", "--appendonly", "yes"] volumes: - /opt/ai-infra-guard/data/redis:/data networks: - aig-net restart: unless-stopped guard: image: ai-infra-guard:2.1.0 container_name: ai-infra-guard ports: - "8890:8890" - "8891:8891" volumes: - /opt/ai-infra-guard/config:/app/config:ro - /opt/ai-infra-guard/logs:/app/logs - /var/run/docker.sock:/var/run/docker.sock:ro environment: DB_HOST: postgres DB_PORT: 5432 DB_USER: aig DB_PASSWORD: change-me DB_NAME: aig REDIS_HOST: redis REDIS_PORT: 6379 SCAN_INTERVAL_MINUTES: "360" ASSET_SCAN_INTERVAL_MINUTES: "1440" SKILL_SCAN_INTERVAL_MINUTES: "60" depends_on: - postgres - redis networks: - aig-net restart: unless-stopped

三个关键点:

第一,PostgreSQL密码我用了环境变量注入,不要写死在镜像里。生产上建议用Docker Secrets或者至少改一个随机强密码,默认密码在公网环境分分钟被扫。

第二,挂载/var/run/docker.sock是让工具能自动发现宿主机上的Docker容器资产。这个权限比较大,如果你对安全要求高,可以让工具只走SSH探活,不挂sock。我为了省事选择了挂载,但网络出口要限制好。

第三,三个扫描间隔要按场景分开:资产发现一天一次就够了,新服务上线不会每天发生;技能扫描一小时一次,资源水位变化敏感;而安全基线的技能项可以更频繁。间隔分开的好处是避免无谓的扫描对生产服务造成打扰。

2.3 启动、验证与首次扫描

启动:

cd /opt/ai-infra-guard docker compose up -d

启动后先看日志,确认三个服务都正常:

docker compose logs -f guard

正常情况应该能看到类似这样的输出:

[INFO] Connected to database: aig@postgres:5432 [INFO] Connected to redis: redis:6379 [INFO] Asset discovery worker started (interval: 1440m) [INFO] Skill scan worker started (interval: 60m) [INFO] REST API listening on 0.0.0.0:8891 [INFO] Web console listening on 0.0.0.0:8890

访问 http://宿主机IP:8890,首次登录会要求初始化管理员账号。我建议一开始把静态资产清单配好,再触发一次手动扫描,验证链路。

静态资产文件 /opt/ai-infra-guard/config/assets/static-assets.txt 长这样:

# 每行一个资产,格式:ip|端口|资产类型 192.168.10.11|22|gpu-node 192.168.10.12|22|gpu-node 192.168.10.20|8000|model-service:vllm 192.168.10.21|19530|vector-db:milvus

手动触发全量扫描:

curl -X POST http://127.0.0.1:8891/api/v1/scans -d '{"type": "full"}'

等待几十秒后查任务状态:

curl http://127.0.0.1:8891/api/v1/scans/latest

这一步能跑通,部署就完成了一大半。接下来才是重头戏:看它到底扫出了什么。

3. 技能扫描规则解析:探针设计决定了扫描的深度

3.1 四类探针与我的自定义技能清单

第一轮测试我只用了内置的默认规则,结果发现内置规则偏向于“存在性检查”:进程在不在、端口通不通、版本能不能匹配上。这些规则跑起来很快,但信息量一般。后面我陆续补了自定义技能项,才真正体会到这个工具的价值。

AI-Infra-Guard的探针大致分四类,列个表格看得更清楚:

探针类型检测方式典型技能项失败代表什么
命令探针远程执行命令(SSH/容器exec)GPU驱动版本、CUDA路径、显存剩余量组件缺失或版本错配
HTTP探针发起GET/POST请求,检查状态码与响应体模型服务健康、接口延迟、模型是否加载完成服务不可用或功能异常
配置探针读取YAML/JSON配置文件,校验字段Ray集群Scaling配置、Milvus索引参数、API密钥权限配置不符合基线
依赖探针对比版本清单Python包版本、CUDA Toolkit版本、cuDNN版本依赖不满足最低要求

举个例子,我写的GPU显存水位技能项是这样的:

skills: - id: gpu-mem-watermark asset_type: gpu-node name: 显存水位检查 probe_type: command command: "nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader,nounits" parse: columns: [memory.used_mb, memory.total_mb] expression: "memory.used_mb / memory.total_mb < 0.90" severity: warning

这个技能项的逻辑是:远程在GPU节点上执行nvidia-smi,解析出已用显存和总显存,比值超过90%就报警。阈值设90%是因为Triton和vLLM这类推理服务往往会预留显存,过高的水位会显著增加显存分配失败的概率。低于这个水位一般不用干预,超过就得看看是哪个Worker在吃显存了。

3.2 资产发现方式:动态发现与静态清单配合

AI-Infra-Guard支持两种资产发现:静态清单和动态发现。动态发现通过挂载的docker.sock枚举宿主机的容器,再通过SSH探测远程节点。我混合使用,效果如下:

  • 本机容器:docker.sock直接发现,包括PostgreSQL、Redis、vLLM容器。
  • 远程GPU节点:通过静态清单维护IP、SSH端口和密钥文件。
  • 模型服务:通过HTTP探活,识别到8000端口上有vLLM的响应头,自动标记为model-service:vllm。

有一个细节特别值得注意:动态发现的资产,必须等下一轮资产扫描(我设的24小时)才会更新。也就是说,你新起一个模型服务,最长要等24小时才会出现在扫描范围里。这就是我后来漏报的一个重要伏笔。

3.3 报告输出与分级处理

扫描完成后,结果会写到PostgreSQL,同时汇总成页面报表。分级标准按严重程度分为critical、warning、info三级。critical代表服务不可用,warning代表存在性能或配置隐患,info是不计入风险的信息项。

我一般最关注warning以上的项目。第一次全量扫描下来,十几台节点扫出了一堆warning,大多是“显存水位超过80%”和“模型服务响应时间P99超过500ms”,还有一条critical:某台GPU节点的GPU ECC错误计数非零。当时没太在意这条critical,结果它就是后面那个雷的引子。

4. 漏报复盘:扫描全绿的那一天,生产服务正在“带病运行”

4.1 现象:全绿报告与生产告警同时出现

事情发生在上线AI-Infra-Guard后的第三周。周五中午,监控平台的告警响了:生产环境里一个vLLM推理服务的成功率从99.9%掉到94%,持续了大概十分钟。我打开AI-Infra-Guard看当天凌晨的扫描结果,发现对这台GPU节点的技能扫描全部通过,模型服务的探针也显示HTTP 200、响应正常。

当时的第一反应是:工具扫偏了。因为它扫的时点是凌晨,而问题是中午出现的,一个是历史快照一个是实时异常,时间对不上。我承认这一点,但这并不能解释“为什么凌晨扫描对显存水位的判断是健康的,而中午就爆了”。

4.2 排查过程:从日志、指标到规则逐层拆

我先把vLLM的日志翻出来,发现推理进程反复出现NCCL超时相关的日志,而且伴随着显存分配失败的错误。手动执行nvidia-smi查了一下,发现GPU显存占用已经到97%,缓存调度器的显存碎片在持续累积,请求结束后没有正常回收。

这时候我回头翻AI-Infra-Guard的规则,发现内置规则的检查项只有“进程存在+版本匹配”这类逻辑,压根没有显存水位探针。也就是说,AI-Infra-Guard认为“GPU这块没毛病”,因为驱动、CUDA、vLLM版本全都在基线里,而它没有看显存还剩多少。

第二个漏报更隐蔽。生产环境里的模型服务走的是K8s部署,对外入口IP是固定的,但某次服务重建后入口IP变了,我没有同步到静态资产清单里。技能扫描探针对着旧IP发HTTP请求,探针“成功”,是因为旧IP上还跑着一个忘了下线的旧版本Triton实例,端口照常返回200。换句话说,探针扫的根本不是生产实例,而是一个影子服务。

4.3 根因分析:两个漏报点叠加

复盘下来,那次漏报是两层问题叠加:

第一,技能项覆盖不足。内置规则对GPU节点只查“驱动可加载、CUDA版本、推理框架版本”,没有覆盖“显存水位”“ECC错误增量”“CUDA上下文可创建”这类功能级指标。探针停留在“存在性”,没上升到“可用性”。

第二,资产清单漂移。动态发现的周期是24小时,静态清单又是手工维护的,模型服务入口IP的变化发生在两次资产扫描之间,等于扫描范围里根本没有生产实例。

这两个漏报点单独看都不是大问题,叠加起来就很危险:扫描报告显示全绿,实际上生产服务正在带病运行。

4.4 修复与规则补强

修复分三步走。

第一步,给GPU节点技能项补充两条功能级探针:

skills: - id: gpu-memory-watermark asset_type: gpu-node name: 显存水位检查 probe_type: command command: "nvidia-smi --query-gpu=memory.used,memory.total --format=csv,noheader,nounits" parse: columns: [memory.used_mb, memory.total_mb] expression: "memory.used_mb / memory.total_mb < 0.90" severity: warning - id: gpu-cuda-context asset_type: gpu-node name: CUDA上下文创建检测 probe_type: command command: "python3 -c \"import torch; assert torch.cuda.is_available(); torch.zeros(1, device='cuda')\"" severity: critical

显存水位是warning级别,因为高水位未必立刻出问题;CUDA上下文创建失败直接是critical,因为这意味着GPU对上层服务来说已经不可用了。我还加了一条ECC错误增量统计,用两次采集的nvidia-smi输出算差值,增量超过阈值就告警。

第二步,把资产发现的间隔从24小时改小,并且允许扫描前动态刷新资产列表。配置里把ASSET_SCAN_INTERVAL_MINUTES改成360,同时在guard.yaml里打开dynamic_refresh开关。

第三步,给模型服务的探针从“TCP端口可达”升级到“HTTP推理请求可用”。新技能项会发起一个最小推理请求,比如输入一个简单请求,要求服务返回正常响应、状态码200且延迟低于阈值:

skills: - id: model-infer-available asset_type: model-service name: 推理可用性检测 probe_type: http method: POST path: /v1/chat/completions body: '{"model": "default", "messages": [{"role": "user", "content": "ping"}]}' expect_status: 200 max_latency_ms: 2000 severity: critical

这样改造之后,再遇到“进程活着但功能已废”的情况,扫描就能抓得住。

5. 避坑清单与实操心得

5.1 部署阶段最常踩的五个坑

写这篇复盘之前,我特意把部署和第一次跑通扫描过程中遇到的坑整理了一下,五个最常见:

  1. 数据目录没挂载好,容器重建后历史扫描结果全丢。这个真踩过,重启一下容器,上个月的报表全没了,当时心态是崩的。建目录时就把三个volumes全部规划好,不要图省事。
  2. docker-compose里忘记给PostgreSQL配volume,导致同样的问题。建议部署前用docker volume ls确认一下持久化卷都创建成功。
  3. 宿主机Docker版本太旧,compose v2语法不识别。Ubuntu 22.04自带的Docker如果太久不升级,可能报compose语法错误。部署前先把docker和docker compose plugin升到最新稳定版。
  4. 挂载docker.sock后,工具虽然能发现容器,但探针有触碰宿主机Docker API的能力。生产上要限制工具服务容器的权限,至少不要给privileged。
  5. 扫描SSH远程节点时用密码而不是密钥,导致定时任务挂在交互式输入上。远程探活一定要用SSH密钥,而且要用专用账号,不要用root直接登录。

5.2 探针开发的三个原则

经历了漏报复盘之后,我给自己定下了探针开发的三条原则,现在写规则几乎不踩坑:

第一,探针必须能区分“存在”和“可用”。进程在、端口通、版本匹配,都只是存在,不代表可用。判断可用要做功能级验证,哪怕是发一个最小推理请求。

第二,规则不要一次写太复杂。先写一条能跑的最小探针,确认能正常采集,再叠加解析逻辑和告警阈值。否则排查问题的时候,你分不清是探针写错了还是资产真有问题。

第三,阈值要和业务侧对齐。显存水位80%还是90%才算告警,不能拍脑袋定,要跟算法团队沟通。他们知道模型在什么水位会OOM,知道了再定阈值,告警才有意义。

5.3 扫描可信度评估:三条自检问题

最后分享一个自检方法,每次看扫描报告之前,先问自己三个问题:

第一,资产清单是最新的吗?有没有新增、下线或变更IP的服务被漏掉? 第二,技能项覆盖的是“存在性”还是“可用性”?有没有哪个关键能力只测了端口没测功能? 第三,上一次漏报的复盘结论,有没有真正落成新的探针或规则?

这三个问题都回答“是”,这份扫描报告才值得信赖。否则你看到的全绿,跟我那次一样,只是“看起来安全”。

我个人在实际操作中的体会是,AI基础设施的巡检,最大的敌人不是扫描工具的缺陷,而是运维侧对扫描结果的盲目信任。工具能帮你把重复劳动自动化,但资产清单的维护、技能项的持续补充、阈值和业务的对齐,这些活儿永远得有人盯。现在我每周都会看一眼AI-Infra-Guard的规则列表,像维护代码一样维护它,新增服务上线那天,顺手把资产清单和技能基线更新掉。这条路没有终点,但至少比人肉巡检睡得安稳。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询