气隙环境部署 AI 系统不只是断网,标准栈中约半数组件依赖外部网络;本文清单化包管理镜像、模型下载、容器镜像、许可证校验等各环节的离线替代方案。
air-gapped AI 部署不是简单地"把网络关掉"的普通部署。一个标准技术栈中,大约有一半组件默认存在某种出站连接——软件包索引、模型市场、许可证检查、遥测端点、证书吊销查询——每一项都需要被替换,而不是删除。本文就是这些依赖的清单和传输协议。
首先要列出清单,因为这个列表比预期的要长。一项一项地排查会把一个两周的项目变成两个月的项目。
找到剩余项的最可靠方法是:在一台没有互联网路由的主机上构建并运行整个系统,然后观察哪里失败。防火墙规则返回拒绝消息不算同样的测试——许多客户端对"超时黑洞"和"即时重置"的行为完全不同,而真正的隔离缺口就是第一个场景。
所有穿越隔离带的内容都应该通过同一种方式——同一个流程——这样控制措施才能均匀施加。
在一台有网络访问权限的构建主机上组装。bundle 通过可复现的流水线产生,而不是由人手动复制文件。
在打包前验证上游真实性。检查发布者签名和校验和——在有网络的一侧验证,因为那里可以访问发布者的公钥。一旦进入隔离区,就只能根据自己带进去的东西来验证。
生成清单。每一个文件、它的大小、SHA-256 值、上游来源及其许可证。这是审计员阅读的制品,也是接收方核对的依据。
用一把公钥已在隔离区内的私钥为清单签名。这才使传输变得可信,而不仅仅是完整——校验和证明字节没有改变,签名证明是谁组装了它们。
扫描。在有网络的一侧进行恶意软件扫描,并生成软件物料清单(SBOM),因为接收方事后无法查询漏洞数据库。
通过站点允许的方式传输:可移除介质、数据二极管、或经过审查的单向网关。
在隔离区内验证。顺序很重要:先验证签名,然后按清单逐文件核对哈希。任何一项不匹配则整个 bundle 失败——不要只安装匹配的文件。
记录导入过程。导入了什么、什么时候、由谁、哪个清单哈希。这份日志是六个月后回答"这个二进制文件来自哪里?"的答案,在隔离区内没有其他方式可以回答。
# 在有网络一侧生成清单
find bundle/ -type f -print0 \
| sort -z \
| xargs -0 sha256sum > bundle/MANIFEST.sha256
# 添加校验和无法携带的来源信息
cat > bundle/MANIFEST.json <<'JSON'
{
"bundle_id": "2026-08-04-r3",
"created_utc": "2026-08-04T11:02:00Z",
"contents": [
{"path": "models/my-model/", "digest": "sha256-3f9a1c...",
"source": "<upstream url>", "licence": "<spdx id>"},
{"path": "wheels/", "count": 214, "source": "internal mirror snapshot"},
{"path": "images/inference-2026.08.04.tar", "digest": "sha256-91be..."}
]
}
JSON
gpg --detach-sign --armor bundle/MANIFEST.sha256
# 在隔离区内验证。顺序很重要:先签名,再哈希
gpg --verify MANIFEST.sha256.asc MANIFEST.sha256 || exit 1
sha256sum -c MANIFEST.sha256 --quiet || exit 1
echo "bundle verified"
容器镜像用归档格式。用 docker save 导出,用 docker load 导入,或者用专门工具做 registry→归档→registry 的复制。包括每一个基础镜像,不只是你自己构建的。
语言包用文件形式。对于 Python,在与目标平台和 Python 版本相同的主机上运行 pip download -r requirements.txt -d wheels/,然后在隔离区内用 pip install --no-index --find-links=wheels/ 安装。平台匹配很重要:wheel 是平台特定的,不匹配的下载会静默回退到源码构建,而在隔离区内没有编译器会导致失败。
模型权重,连同 CI/CD 中模型权重的清单摘要,以及 tokenizer、配置文件和加载器单独获取的任何其他文件。在交付前离线测试加载过程;最容易忘记的总是小文件。
每个模型和每个包的许可证文本。一些开放权重许可证附带再分发和使用条件,在隔离区内没有办法去查阅它们。"开放权重"与"开源"的区别在实践中意味着什么。
操作系统包和驱动程序,包括 GPU 驱动和容器工具包,要与隔离区内的内核版本匹配。驱动不匹配是 air-gapped GPU 安装失败最常见的单一原因。
文档和操作手册,因为在隔离区内发生故障时没人能在厂商的文档网站上查阅。把需要的页面离线带上。
盲目运行不可接受,但建立通用出站通道也不可行。解决方案是:出站数据是经过审查的导出物,而不是一个流。
在内部聚合。在隔离区内运行完整的指标和日志栈。没有任何原始数据以原始形式离开。
定义一个只包含聚合数据的导出模式:计数、百分位数、错误类别频率、资源利用率。不包括 prompt、不包括 completions、不包括标识符、不包括自由文本。自由文本是泄密的源头——异常消息可能包含客户记录。
发布前审查。可以是人,或者是一个拒绝任何不符合模式内容的验证器,或两者结合。自动化的模式验证才是可以规模化的部分。
按计划通过与入站相同的受控路径导出,方向相反,有自己的清单和日志。
两个值得提前规划的后果。无法看到示例输入时调试要困难得多,所以要在聚合数据上投资复现故障——错误分类、结构化错误代码、按原因而非消息作为 key 的计数器。此外,任何厂商支持协议都需要预先商定可以向他们发送什么内容,在合同阶段决定,而不是在故障期间临时商议。
air-gapped 操作的真正难点不是初始安装,而是第九个月。某个依赖发布了漏洞;在隔离区内什么都察觉不到,因为那个察觉漏洞的东西是一个需要网络连接的扫描器。
把 SBOM 留在外面。每个 bundle 的物料清单留在有网络的一侧,在那里持续扫描。这才把"我们的 air-gapped 系统有漏洞吗?"变成一个查询而不是一次调查。
按计划交付 bundle,不要等事件触发。月度或季度节奏意味着传输流程是被练习过的。半年才用一次的流程在紧急需要时往往会失败。
有一条预先定义好的紧急路径。谁批准计划外的 bundle、如何紧急处理、哪些验证不能跳过。在事件期间做这些决定正是控制被绕过的原因。
版本锁定一切并记录已安装的内容。在隔离区内,"正在运行哪个版本?"必须能从导入日志回答,而不是检查运行中的系统。
"air-gapped"被用于几种截然不同的场景,它们之间的设计差异很大。问清楚实际需要的是哪一种,因为团队通常在只需要较弱版本时构建了最严格的版本。
对于后两种情况(译者注:指较弱版本),带有严格出口控制的本地部署以很小一部分工作量获得了大部分保证——本地 LLM 部署涵盖了这种形态,AI 主权涵盖了组织最终走到这一步的原因。如果需求确实是第一种,从一开始就要为上述更新节奏做预算:这是持续性成本,是最容易被遗忘在计划之外的那个。