
周五下午三点我在线上例会开到一半群里突然弹出一张截图data-service 技能三分钟前挂掉调用端超时率直接飘红。这已经不是第一次了我维护的内部AI技能平台已经塞了几十个技能组件——有接业务库的有调模型API的有做知识库检索的每次出新问题都靠人肉翻日志。就是在这种背景下我把AI-Infra-Guard从一个周末练手项目正式变成了平台标配的哨兵。这篇文章聊聊它的Docker一键部署、技能扫描的实战操作以及一次让我印象极深的漏报复盘。如果你也在维护AI基础设施尤其是技能Skill数量一多就管不过来的那种这篇应该能给你不少参考。先说结论AI-Infra-Guard跑起来很简单真正难的永远不是部署而是怎么理解扫描结果以及怎么面对它漏报的时候。1. 事故催生的产物AI技能平台的配置与安全痛点1.1 技能组件多了以后问题不再是“能不能跑”而是“什么时候出错”我在公司内部维护的是一个Agent技能平台形态上和OpenClaw、Dify那套类似每个技能是一个独立服务负责任务执行平台负责统一注册、调度和鉴权。早期只有五六个技能的时候一切靠人肉维护完全够用。技能A挂了看日志改配置重启完事。但数量上到四五十个之后事情就变味了。最典型的问题有三个。一是配置漂移同一个技能部署在不同环境配置项经常不一致生产环境某个超时时间被改过但没人记得二是密钥管理混乱有人图省事把API Key写进配置文件直接跟着镜像走repo一拉全暴露三是依赖关系无法感知技能A依赖数据库连接池数据库迁移IP变了技能A还在连旧地址没人知道。这些问题的共同点是它们在故障发生前几乎没有先兆一旦暴露就是线上事故。我试着用传统的监控工具去盯比如Prometheus加告警但那只能看到“进程挂没挂”“QPS掉没掉”看不到“配置里埋着什么雷”。我需要的是一个能直接扫描技能配置、依赖、权限声明在发布前就把风险挡下来的东西这就是AI-Infra-Guard的来源。1.2 为什么先考虑扫描而不是全链路改造有人可能问你都管不住配置了为什么不搞一套统一配置中心答案是改造代价太大。几十个技能服务有的是Java写的有的是Python FastAPI写的还有两个是历史遗留的Shell脚本加HTTP wrapper。让所有服务都接入统一配置中心工作量以周计关键是要动大量业务代码风险高。AI-Infra-Guard的思路是完全旁路——它不侵入技能服务本身而是定期扫描技能注册表、配置文件、依赖清单和运行环境把扫描结果汇总成报告。相当于给整个平台加了一个“外挂体检医生”不治病但告诉你哪里可能有病。也正是因为这种旁路设计部署它变得非常简单Docker一键起就是基本要求。2. Docker一键部署的全过程编排写法、启动参数与第一次调通2.1 镜像选型和目录规划AI-Infra-Guard我跑在Ubuntu 22.04的服务器上Docker版本是24.0.x。官方镜像发布在ghcr.io名字我叫它ghcr.io/ai-infra-guard/guard我用的版本是v0.4.2。这个版本内置了Metrics Server采集模块、技能注册表扫描器和Web控制台三个功能一个容器全包。拉镜像没什么好说的docker pull ghcr.io/ai-infra-guard/guard:v0.4.2第一次拉大概150MB上下。真正需要考虑的是它在宿主机上要访问哪些东西。默认情况下扫描器需要读取技能服务的配置目录、Docker socket用来发现有哪些技能容器在跑、以及它自己的规则库和日志目录。我习惯在宿主机上建一个独立目录树跟容器做好映射mkdir -p /opt/infraguard/{config,logs,rules,scan-targets} chmod -R 644 /opt/infraguard/configscan-targets这个目录是我的私心设计有些技能服务不在Docker里跑是裸进程我就把它们的关键配置文件软链到这个目录下方便扫描器统一读取。所以说“一键部署”并不是真的有一个命令砸下去就完事你需要提前想清楚扫描源在哪、规则放哪、日志写哪。2.2 docker-compose编排文件写法我强烈建议用docker-compose管理而不是docker run。原因很简单这个工具涉及端口映射、volume挂载、环境变量、重启策略一大堆参数写进compose文件里以后升级、迁移、回滚都方便。我的compose文件长这样services: ai-infra-guard: image: ghcr.io/ai-infra-guard/guard:v0.4.2 container_name: infraguard ports: - 8080:8080 - 8848:8848 volumes: - ./config:/etc/ai-infra-guard - ./logs:/var/log/ai-infra-guard - ./rules:/etc/ai-infra-guard/rules - ./scan-targets:/scan-targets:ro - /var/run/docker.sock:/var/run/docker.sock:ro environment: - GUARD_HOME/etc/ai-infra-guard - GUARD_LOG_LEVELinfo - GUARD_SKILL_REGISTRYfile:///etc/ai-infra-guard/skills.yaml ulimits: nofile: 65535 nproc: 4096 restart: unless-stopped几个参数解释一下。挂载Docker socket用的是只读模式ro安全考虑扫描器只需要枚举容器不需要控制容器。skills.yaml是技能注册表文件里面登记了平台上有哪些技能、各自配置路径、依赖的服务名。Guard通过这个文件知道去扫谁而不是傻乎乎地把宿主机所有目录扫一遍。ulimits限制文件描述符和进程数防止扫描器在技能数量很多时把自己跑爆。restart: unless-stopped保证服务器重启后自动拉起来——这个工具的主要价值在于持续盯梢挂了没人发现就尴尬了。2.3 首次启动与连通性验证配置写好后docker compose up -d拉起然后看启动日志docker compose logs -f ai-infra-guard第一次启动大概十几秒日志里会出现migrate seed rules、load registry、start metrics server几条关键信息。看到guard api server listening on :8080就说明起来了。然后用浏览器开http://服务器IP:8080就是Web控制台。登录控制台之后先在“系统设置”里确认三个东西规则库版本是不是最新的v0.4.2镜像自带的规则库版本我那个是v20240315若需要更新可以用/rules目录挂载新库后续我会专门讲规则更新技能注册表是不是成功加载了Docker socket有没有权限。这三个确认不到位后面的扫描都会出幺蛾子。我第一次部署时就在socket上踩过坑只挂载了socket路径但忘了加ro容器启动倒是正常但扫描器报permission denied while listing containers。因为socket文件属于root:docker组容器内guard进程的UID不够。后来改成挂载时固定用户或者在宿主机上把guard加入docker组问题才解决。这个细节藏得很深不跑一遍日志根本发现不了。3. 技能扫描到底在扫什么三类目标与四维检查3.1 扫描目标的三种类型AI-Infra-Guard的技能扫描目标不是泛泛的“整个服务器”而是有明确划分的三类。第一类是配置类目标。也就是技能服务的配置文件常见的格式包括YAML、JSON、ENV文件、甚至Nginx配置片段。这类目标是扫描的重头戏因为配置里最容易藏风险明文密码、IP白名单写错、token过期策略缺失等等。第二类是运行态目标。通过Docker socket枚举出来的技能容器以及裸进程方式运行的技能服务。扫描器会检查这些服务是不是还活着健康检查接口是否正常资源占用有没有异常。第三类是依赖类目标。每个技能对外部服务的依赖比如MySQL、Redis、模型API网关。AI-Infra-Guard会核对技能配置里声明的依赖地址再去探测这些地址是否可达。这个设计在技能平台里非常实用技能配置里写的数据库IP和实际运行中的数据库IP一旦不一致问题严重程度不亚于密码泄露因为它直接导致服务不可用。3.2 四维检查逻辑确定了目标之后每一个目标都要过四维检查配置基线、依赖健康、权限模型、密钥存储。配置基线检查是把技能配置和预设的基线模板做比对。比如一个标准技能配置里应该有timeout字段、retry字段如果缺失扫描器会给出“配置不完整”的提示。这个维度保证的是规范性。依赖健康检查就是上面的依赖类目标探测扫的是“配置里写的依赖能不能连通”。权限模型检查针对的是技能声明的访问权限。Guard会解析技能签名文件或者服务账号配置判断技能请求的权限范围是否过大。比如一个只应该读数据的技能却声明了写权限扫描器会标记为“权限过宽”。密钥存储检查是最有意思的维度。它专门搜配置里有没有明文密钥、AK/SK、数据库密码规则核心是一个正则集。但这里也是我后来踩大坑的地方后面复盘部分细说。四个维度的检查结果会汇总为三档critical必须修复才能发布、warning建议修复、info仅供记录。扫描完成后控制台上会给出总分和一个按技能分组的风险排行。总分低于80分的新技能发布流程会被卡住。3.3 技能配置文件的上下文解析这里我要单独展开说一下因为这正是后面漏报的伏笔。AI-Infra-Guard并不是简单地逐行读配置文件它有一个“上下文解析器”会先尝试把YAML/JSON解析成结构化的对象再做规则匹配。规则表达式的写法支持直接定位某个字段比如rules: - id: SECRET-MYSQL-PASSWORD level: critical match: field: database.password pattern: (?i)(password|passwd)\\s*[:]\\s*[\]?[^\\\s]这条规则的意思是在配置对象中找database.password字段如果字段值符合“看起来是一个口令”的特征就报critical。实际做的时候字段定位比纯文本正则要准得多因为YAML字段嵌套多层纯文本扫描很容易被注释干扰。问题在于上下文解析器依赖YAML结构如果配置值不是真正的字符串而是${DB_PASSWORD}这种环境变量引用解析器会把这个值当作普通字符串${DB_PASSWORD}去匹配。而规则库里的正则模式没有覆盖“${...}引用”这种形态于是检查结果就是什么都没发生。这就是我遇到的漏报发生的土壤。4. 把扫描跑起来技能注册、规则配置与报告解读4.1 在注册表中登记一个技能技能注册表文件skills.yaml是扫描器的工作清单我维护的系统里长这样skills: - name:>docker exec infraguard guard scan --skill>database: host: 10.20.30.40 port: 3306 user: app_rw password: ${DB_PASSWORD}等等问题看起来不在密码本身哪怕是环境变量引用也没问题——如果DB_PASSWORD这个变量真的存在的话。去运行环境里一查这个环境变量根本没配服务启动时取不到密码就用了空字符串去连数据库认证自然失败。而AI-Infra-Guard扫描的时候password字段的值是${DB_PASSWORD}它不认识这种引用直接当一个普通字符串跳过了。这就是典型的漏报扫描器给了绿灯而真实风险配置引用了未定义的环境变量它根本没发现。5.2 排查链路从扫描日志到规则库漏报出来之后我第一时间不是改业务代码而是查Guard为什么漏。整个排查链路值得记下来以后遇到类似问题可以照着做。第一步查扫描日志。Guard每次扫描都会在/var/log/ai-infra-guard/guard-scan.log里留记录以data-query-skill这次为例日志显示[2025-01-17 14:12:03] scan started for skill>grep -rn DB_PASSWORD /opt/infraguard/scan-targets/data-query-skill/结果文件里确实有引用。Guard扫不出来说明不是文件权限问题是规则匹配问题。第三步看规则库版本和具体规则。当前规则库是v20240315里面“敏感数据”类规则确实有检查password字段的但规则用的是上面提到的字段定位写法只匹配“字段值看起来像密码”的字符串。而${DB_PASSWORD}既不包含真正的密码字符也不是纯文本变量名它的形态让所有密码规则全部哑火。第四步带着debug模式重跑一遍。Guard有--debug参数会打印每条规则的命中评估过程。我看到了关键输出rule SECRET-MYSQL-PASSWORD: field database.password found rule SECRET-MYSQL-PASSWORD: value ${DB_PASSWORD} does NOT match pattern一切都很清晰了——不是没扫描到字段而是规则模式没有覆盖引用形态。5.3 根因定性不是工具坏了是规则覆盖面不够排查到这里根因就清楚了。这不算工具本身的Bug而是规则库的场景覆盖存在盲区。AI-Infra-Guard的规则引擎只负责执行规则规则本身是静态的它假设“密码这种敏感信息只应该以明文形式出现在配置里”没想过配置里会用环境变量引用它。这件事暴露了一个更深的问题扫描规则的编写者默认了“安全配置的形态是唯一的”。而实际工程里安全配置的变形有很多种环境变量引用、密钥管理服务的占位符、带前缀的加密标记——每一种都是合理实践如果规则库不主动认识它们就会把它们当空气。所以漏报归因第一是规则库v20240315缺少“环境变量引用未定义”这一类检测规则第二是我自己过于依赖扫描器发版checklist里没有“人工看一眼扫描结果之外的内容”这一步第三是技能配置本身就有问题引用了一个不存在的环境变量但没有任何质量门禁挡住它。5.4 修复方案补规则加解析更新流程修复分三层。第一层给AI-Infra-Guard补规则。我新写了三条规则核心逻辑是检测配置值是否为${...}形式并且在环境变量解析之后是否为定义状态。规则长这样rules: - id: SECRET-ENVREF-UNDEFINED level: critical match: field: database.password pattern: \\$\\{[A-Z0-9_]\\} check: env_resolve: required description: 配置字段引用了环境变量但该变量未定义或为空这个规则匹配所有${...}形态然后做一次环境变量解析解析不到值就报critical。放在password、api_key、token这几类敏感字段上生效。第二层调整部署配置让扫描器在扫描时额外加载一个“运行环境快照”。Guard支持通过环境变量传入额外映射相当于告诉它“这些技能跑在哪个环境”这样它做env解析时有上下文可查。我在compose文件的environment里加了environment: - GUARD_ENV_FILE/etc/ai-infra-guard/env-snapshot.envenv-snapshot.env由宿主机上一个小脚本定时生成内容就是把技能容器里声明过的环境变量名导出来。Guard扫描时先读这份快照再和${...}引用做比对。第三层改发版流程。AI-Infra-Guard的扫描结果不再只看“critical数量是否为0”还必须看“规则覆盖度”指标是否接近100%以及是否有配置项被“跳过未检查”。“跳过未检查”比“检查后告警”危险一百倍因为你以为安全了其实根本没看到。这三层做完我重新跑了一次data-query-skill的扫描这次直接打出一个criticaldatabase.password引用了未定义环境变量 DB_PASSWORD。然后再把环境变量配上重扫全部通过。那一刻的踏实感跟第一次上线完全不一样。6. 修复之后的技术沉淀扫描规则覆盖面的三条经验6.1 规则库要跟着业务演进不能一份用到老这次漏报给我最大的一课就是扫描工具的规则库有保质期。业务会演进配置方式会演进规则库不跟进就必然出现“工具没坏但已经开始漏”的状态。我现在的习惯是每个月花半天看一遍规则库命中统计重点看两类命中率极高的规则和命中率为0的规则。命中率极高的不用管说明这是持续存在的真风险命中率长期为0的规则要怀疑是不是规则的匹配模式已经被业务绕开了——比如大家都改用环境变量引用了明文密码规则自然就扫不到东西如果不补新的引用检测规则工具就形同虚设。6.2 用“已知故障清单”反推规则覆盖度漏报复盘完之后我整理了一份“已知故障清单”把过去半年线上出过的所有技能相关故障补充整理进去一共23条。然后对每一条问两个问题当时的故障现象还能不能复现如果复现了AI-Infra-Guard能不能在故障发钱前给出告警结论很扎心23条里只有16条是规则库能主动发现的剩下7条要么依赖检查缺失要么规则没覆盖到。后面我给这7条逐一补了规则然后把这份清单加进了每季度的规则评审会议。用真实发生过的事故去检验扫描能力比凭空想“应该有什么风险”扎实得多。6.3 自动化再强也要保留人工抽检的最后一道闸说来矛盾我搞了这么多自动化扫描最后得到的结论反而是不能完全信自动化。AI-Infra-Guard的价值在于扩大人工检查的覆盖面、降低检查成本但它的盲区恰恰是那些“看起来合理”的配置模式。环境变量引用是合理的所以它没想到要验证引用是否有效服务自动发现是合理的所以它没有检查配置里有没有写错依赖地址。我现在保留了最后一手每个新技能发版前除了看扫描报告还要看一眼报告里的“未检查项”。这不是反自动化而是承认任何工具都有它看不见的地方人工要做的正是盯住那些地方。7. 常被问到的部署细节和注意事项7.1 资源占用与性能有朋友问AI-Infra-Guard吃资源吗我实测下来单个扫描任务比如扫一个技能CPU占用在3%8%之间内存稳定在250MB左右全量扫描40个技能大约耗时3分半。扫描器默认串行执行如果你嫌慢可以加--parallel 4参数提高并发但要注意Docker socket API有限流并发数太高会被拒。7.2 升级镜像时的规则库兼容性Guard升级镜像后规则库格式不一定兼容尤其是我自定义规则用的字段定位语法在新版本里可能变化。我的建议是升级前先备份rules目录升级后进行一轮对比扫描确认新旧版本对同一份配置的检查结论一致再切流量。这个谨慎步骤能帮你避免“升级后规则全部失效”的隐蔽坑。7.3 扫描结果怎么接入告警通知Guard的Web控制台自带的告警通知只支持邮件我们对它的要求比较高critical必须推到内部IM群。做法是拉一个简单的脚本轮询Guard API的/api/v1/scan/reports/latest发现有new critical就通过webhook转发。脚本大概40行部署在宿主机上这比在Guard里做一堆定制开发省事得多。7.4 多环境管理我目前有两个环境需要同时扫描开发环境和生产环境。做法是在同一台服务器上跑两个Guard容器端口错开分别挂接不同的skills.yaml和env-snapshot.env。容器是独立的规则库可以各自更新不会互相干扰。但要注意两个容器不要同时挂同一个/var/run/docker.sock扫描全量容器否则两个扫描器会重复计算告警也会重复推送。我给每个容器加了GUARD_SKILL_REGISTRY前置过滤让它们各自只扫自己负责的技能清单。最后分享一点我在这次实战中的体会AI-Infra-Guard让我重新理解了“安全扫描”这件事它不是在发现风险而是在界定风险的可见边界。一次漏报未必是工具坏了更大的可能是你还没告诉工具某个新出现的配置风险也该被当成风险。这也是为什么我在标题里强调“漏报复盘”——工具可以升级规则可以补全但作为维护者永远要给自己留一个问题如果它这次没扫出来下一次它还会漏什么从那之后我的发版清单里多了一条细项发布前打开扫描报告滚动到最底部把所有“未检查项”逐个过目。如果你也在用类似的扫描工具我建议你把这个动作变成习惯。自动化负责把网撒下去但哪些地方有鱼最终还是要靠你的经验去判断。