2191 字
约 7 分钟
14
S06-A22 · 离线构建:为什么"内网机器"上构建总失败

一个项目的构建在开发机上很顺,到了内网的 CI 机器上必然失败在"下载依赖"——因为 CI 机器"能访问内网私服,但访问不了公网",而某个新加的依赖"私服里还没有代理过"。

离线构建的问题不是"要不要联网",而是"依赖从哪里来、什么时候进来"。这个三层的"渐进"很实用:多数团队只需要第一层(代理),少数需要第三层(完全隔离)——而"一开始就上离线包"成本高且维护重。
而它有三个层次:镜像/代理(缓存)、锁定(可复现)、离线包(完全隔离)。


1. 三种"离线"程度

程度 机制
代理缓存(私服) 首次联网取,之后缓存
锁定 + 预取(vendor/镜像) 依赖随代码走
完全离线(离线包) 依赖由"离线包"提供

第一项是"最常见的内网形态":私服代理公网仓库,于是"用过的依赖"能在内网取到——而"新依赖"第一次仍需要"私服能访问公网"。

第三项是"军工/金融隔离网"的形态:依赖被打成"离线包",随版本发布第三项的"维护要点"是"离线包与代码版本一一对应":代码加了新依赖,离线包必须同步重建——否则"用旧包构建新代码"必然失败。
——于是"构建完全不需要网络"。

2. "私服没代理过"的三种表现

表现 原因
404(依赖不存在) 私服没代理过,且不能连公网
401/403 私服需要认证,配置缺失
卡在"下载中" 私服尝试回源,超时

第三项的"误导性"在"日志":报错写的是"连接超时"或"read timed out",于是排查方向变成"网络问题",而不是"依赖不存在"。
第三项最费时间:它在"尝试回源"上耗掉几分钟,然后失败——而"失败信息"往往是"超时",不是"依赖缺失"。

```tex第三项(离线构建缺插件)的"预防"是"把"构建命令"本身也放进预取流程":先 go-offline,再 -o package 跑一次——两次都过才说明"预取完整"。
t
// 三种配置(按"私服能力"选)
私服可回源:只配 mirror(首次自动代理)
私服不可回源:必须"手工导入"或"用离线包"
混合:核心依赖固定版本 + 私服回源兜底
// 判据:CI 机器能否访问公网(哪怕只能访问私服)


## 3. "vendor / 离线包"的三种做法

| 做法 | 特点 |
|---|---|
| Go:`vendor/` 目录 | 依赖随仓库(体积大) |
| Maven:`mvn dependency:go-offline` | 预取到本地仓库 |
| Node:`node_modules` 打包 或 `yarn offline mirror` | 体积大但完全离线 |

第二项很实用:**它把"构建所需"全部下到本地 `.m2`**第二项的"边界"是"**它不覆盖"运行时才下载"的东西**"(如某些插件的"动态解析")——所以"预取完还要在离线环境"试跑一次全量构建"。
,于是"之后构建不需要网络"——而它需要在"能联网的机器"上跑一次。

```bash
// Maven:一次预取,之后离线构建
mvn -q dependency:go-offline            # 在能联网的机器上跑
// 打包 ~/.m2 到离线环境
tar -czf m2-offline.tar.gz -C ~ .m2
// 离线构建(强制离线,缺依赖立刻失败而不是"卡超时")
mvn -o -B clean package
// 关键:-o 让"缺依赖"变成"立刻报错",而不是"卡在下载"

4. "锁定"为什么是离线的前提

有锁文件 无锁文件
依赖版本确定 每次解析结果可能不同
离线包可对应 无法知道"该打哪些包"

这个"同一件事"的解释是"离线包只能按"确定的依赖集"打":没有锁文件,"依赖集"本身是"每次解析的产物"——于是"包"与"构建"对不上。
所以"离线构建"与"可复现构建"是同一件事的两面:没有锁文件,"离线包"就不知道"该包含什么"。

"镜像/代理"的配置要写清"回源策略",否则行为不可预期:

<!-- settings.xml:镜像 + 不可回源的私服 -->
<mirrors>
  <mirror>
    <id>internal</id>
    <mirrorOf>*</mirrorOf>                  <!-- 所有请求都走私服 -->
    <url>http://nexus.internal/repository/maven-public/</url>
    <blocked>false</blocked>
  </mirror>
</mirrors>
<!-- 判据:私服"是否允许回源"决定"新依赖能不能自动取到" -->

5. 三种"离线构建"的失败模式

失败 成因
缺一个传递依赖 预取不完整
版本与在线不同 锁文件未更新
插件缺失 插件也需预取

第三项最容易被忽略:构建插件(如 maven-shade-plugin)本身也是依赖第三项的"典型表现"是"编译过了、打包失败":依赖都在,而"打包插件"没被预取——于是"到最后一个阶段才失败"。
,而"预取主依赖"不一定包括它。

// 预取要覆盖的四类
1 编译依赖(compile)
2 运行依赖(runtime)
3 测试依赖(test)
4 插件与其依赖(plugin + pluginDependencies)
// 缺任一类 → 离线构建在"某个阶段"失败,而阶段不同、报错不同

"离线构建"应当有自己的验收命令,跑在"干净环境"里:

// 三个命令(每个都能独立暴露一类缺失)
mvn -o -B validate             # 依赖解析(缺依赖立刻失败)
mvn -o -B test-compile         # 测试依赖
mvn -o -B package              # 插件(最容易缺)
// 关键:三个都过,才算"离线包完整"

6. 排错:五种"离线构建问题"

现象 成因 处理方向
404 某个依赖 私服未代理 手工导入/加离线包
卡几分钟后超时 私服尝试回源 配 -o 或禁回源
离线构建缺插件 未预取插件 加插件预取
本地能编 CI 不能 本地仓库残留 用干净环境验证
换版本后必失败 离线包未更新 版本与包同步发布

第四项的"验证方式"是"用干净容器跑一次离线构建":docker run -v $PWD:/src -w /src maven:3.9 mvn -o package——本地残留不会带进去。
第四项很常见:本地 ~/.m2 里"历史上取过的"依赖让"离线构建碰巧成功"——而在"干净机器"上就失败。

最后一个"体积"视角:离线包会随依赖数线性增长,于是"要不要打全量"是个成本问题。

// 三种"离线包"范围的取舍
全量(含测试/插件):最稳,体积最大
仅 runtime:体积小,但"测试跑不了"
仅核心依赖:最省,但"新依赖要手工导入"
// 判据:离线环境里的"构建任务"包含哪些(编译/测试/打包)

7. 边界:什么情况不必操心离线

场景 为什么 做法
CI 能访问公网 无离线需求 不操心
私服可回源且稳定 首次失败可接受 只配 mirror
依赖极少(< 20) 失败易定位 不操心
用"构建镜像"(依赖预装) 镜像即离线包 跟随镜像

最后一条与"团队"有关的边界:"离线包"是"团队共享资产",应当有"谁维护、多久重建"的约定——否则它会"半年没更新",而"新依赖"永远进不来。
判断口径一句话:"构建机器能否在"需要新依赖时"访问到来源"。能(公网或可回源私服),只需"镜像/代理";不能(隔离网),就必须"锁文件 + 离线包 + 插件预取"三件齐。


8. 自测

  1. 三种"离线"程度分别是什么机制?为什么"私服代理"在"新依赖"上仍会失败?
  2. "卡在下载中"为什么比"404"更费时间?怎么让它"立刻报错"?
  3. 为什么"锁定"是"离线"的前提?没有锁文件"离线包"会怎样?
  4. 预取要覆盖哪四类?为什么"插件"最容易被忽略?
  5. "本地能编、CI 不能"为什么常见?它说明什么验证缺失?
  6. 判断"是否需要操心离线"的那个问题是什么?
S06-A22 · 离线构建:为什么"内网机器"上构建总失败
https://www.clxhxhhr.top/posts/6617/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。